Browse Source

설정파일 수정

main
최승민 1 month ago
parent
commit
c9fd5cf0b9
  1. 4
      .agents/AGENTS.md
  2. 82
      DB/dbo.EOL_Schema.sql
  3. 38
      leak_test_project/App.xaml.cs
  4. 24
      leak_test_project/Database.ini
  5. 4
      leak_test_project/Infrastructure/CommunicationBase.cs
  6. 26
      leak_test_project/Manual/.obsidian/workspace.json
  7. 19
      leak_test_project/Manual/01_시스템_개요.md
  8. 47
      leak_test_project/Manual/02_초기_설정.md
  9. 60
      leak_test_project/Manual/03_메인_화면.md
  10. 13
      leak_test_project/Manual/04_통신_설정.md
  11. 9
      leak_test_project/Manual/05_시험_설정.md
  12. 99
      leak_test_project/Manual/06_데이터_조회.md
  13. 99
      leak_test_project/Manual/07_데이터_조회.md
  14. 44
      leak_test_project/Manual/07_시험_프로세스.md
  15. 219
      leak_test_project/Manual/08_로그_관리.md
  16. 150
      leak_test_project/Manual/09_로그_관리.md
  17. 21
      leak_test_project/Manual/09_오류_대응.md
  18. BIN
      leak_test_project/Manual/Pasted image 20260624104139.png
  19. BIN
      leak_test_project/Manual/Pasted image 20260624104208.png
  20. BIN
      leak_test_project/Manual/Pasted image 20260624104256.png
  21. BIN
      leak_test_project/Manual/Pasted image 20260624104353.png
  22. BIN
      leak_test_project/Manual/Pasted image 20260624104445.png
  23. BIN
      leak_test_project/Manual/Pasted image 20260624104506.png
  24. BIN
      leak_test_project/Manual/Pasted image 20260624104531.png
  25. BIN
      leak_test_project/Manual/Pasted image 20260624104558.png
  26. 16
      leak_test_project/Manual/사용자_메뉴얼.md
  27. 21
      leak_test_project/Models/ProjectModels.cs
  28. 61
      leak_test_project/Services/Board4251.cs
  29. 122
      leak_test_project/Services/DatabaseSettings.cs
  30. 82
      leak_test_project/Services/EolDatabaseService.cs
  31. 2
      leak_test_project/Services/IIdSensorService.cs
  32. 99
      leak_test_project/Services/LoginAccountRepository.cs
  33. 147
      leak_test_project/Services/LoginHistoryStore.cs
  34. 295
      leak_test_project/Services/LoginServicesStub.cs
  35. 82
      leak_test_project/Services/LoginSettings.cs
  36. 170
      leak_test_project/Services/TestProcessService.cs
  37. 36
      leak_test_project/Utils/ConfigHelper.cs
  38. 23
      leak_test_project/Utils/DialogService.cs
  39. 355
      leak_test_project/Utils/LogUtils.cs
  40. 31
      leak_test_project/ViewModels/DataViewModel.cs
  41. 14
      leak_test_project/ViewModels/HomeViewModel.cs
  42. 142
      leak_test_project/Views/DataView.xaml
  43. 8
      leak_test_project/Views/StartupLoginWindow.xaml
  44. 2
      leak_test_project/Views/StartupLoginWindow.xaml.cs
  45. 24
      leak_test_project/bin/Debug/Database.ini
  46. BIN
      leak_test_project/bin/Debug/app.publish/leak_test_project.exe
  47. 2
      leak_test_project/bin/Debug/leak_test_project.application
  48. BIN
      leak_test_project/bin/Debug/leak_test_project.exe
  49. 4
      leak_test_project/bin/Debug/leak_test_project.exe.manifest
  50. BIN
      leak_test_project/bin/Debug/leak_test_project.pdb
  51. 24
      leak_test_project/bin/Release/Database.ini
  52. 2
      leak_test_project/bin/Release/Logs/2026-06-18_system.log
  53. 8
      leak_test_project/bin/Release/Logs/2026-06-25_system.log
  54. BIN
      leak_test_project/bin/Release/app.publish/leak_test_project.exe
  55. 3
      leak_test_project/bin/Release/config.xml
  56. 2
      leak_test_project/bin/Release/leak_test_project.application
  57. BIN
      leak_test_project/bin/Release/leak_test_project.exe
  58. 4
      leak_test_project/bin/Release/leak_test_project.exe.manifest
  59. BIN
      leak_test_project/bin/Release/leak_test_project.pdb
  60. 12
      leak_test_project/leak_test_project.csproj
  61. BIN
      leak_test_project/obj/Debug/App.baml
  62. BIN
      leak_test_project/obj/Debug/MainWindow.baml
  63. BIN
      leak_test_project/obj/Debug/Views/CommunicationWindow.baml
  64. BIN
      leak_test_project/obj/Debug/Views/DataView.baml
  65. 6
      leak_test_project/obj/Debug/Views/DataView.g.cs
  66. BIN
      leak_test_project/obj/Debug/Views/HomeView.baml
  67. BIN
      leak_test_project/obj/Debug/Views/ParametersWindow.baml
  68. BIN
      leak_test_project/obj/Debug/Views/StartupLoginWindow.baml
  69. 22
      leak_test_project/obj/Debug/Views/StartupLoginWindow.g.cs
  70. 2
      leak_test_project/obj/Debug/leak_test_project.application
  71. 2
      leak_test_project/obj/Debug/leak_test_project.csproj.CoreCompileInputs.cache
  72. 1
      leak_test_project/obj/Debug/leak_test_project.csproj.FileListAbsolute.txt
  73. BIN
      leak_test_project/obj/Debug/leak_test_project.exe
  74. 4
      leak_test_project/obj/Debug/leak_test_project.exe.manifest
  75. BIN
      leak_test_project/obj/Debug/leak_test_project.g.resources
  76. BIN
      leak_test_project/obj/Debug/leak_test_project.pdb
  77. 8
      leak_test_project/obj/Debug/leak_test_project_MarkupCompile.cache
  78. BIN
      leak_test_project/obj/Release/App.baml
  79. BIN
      leak_test_project/obj/Release/DesignTimeResolveAssemblyReferencesInput.cache
  80. 62
      leak_test_project/obj/Release/GeneratedInternalTypeHelper.g.cs
  81. BIN
      leak_test_project/obj/Release/MainWindow.baml
  82. BIN
      leak_test_project/obj/Release/Views/CommunicationWindow.baml
  83. BIN
      leak_test_project/obj/Release/Views/DataView.baml
  84. 6
      leak_test_project/obj/Release/Views/DataView.g.cs
  85. 6
      leak_test_project/obj/Release/Views/DataView.g.i.cs
  86. BIN
      leak_test_project/obj/Release/Views/HomeView.baml
  87. BIN
      leak_test_project/obj/Release/Views/ParametersWindow.baml
  88. BIN
      leak_test_project/obj/Release/Views/StartupLoginWindow.baml
  89. 22
      leak_test_project/obj/Release/Views/StartupLoginWindow.g.cs
  90. 22
      leak_test_project/obj/Release/Views/StartupLoginWindow.g.i.cs
  91. 2
      leak_test_project/obj/Release/leak_test_project.application
  92. BIN
      leak_test_project/obj/Release/leak_test_project.csproj.AssemblyReference.cache
  93. 2
      leak_test_project/obj/Release/leak_test_project.csproj.CoreCompileInputs.cache
  94. 1
      leak_test_project/obj/Release/leak_test_project.csproj.FileListAbsolute.txt
  95. BIN
      leak_test_project/obj/Release/leak_test_project.exe
  96. 4
      leak_test_project/obj/Release/leak_test_project.exe.manifest
  97. BIN
      leak_test_project/obj/Release/leak_test_project.g.resources
  98. BIN
      leak_test_project/obj/Release/leak_test_project.pdb
  99. 8
      leak_test_project/obj/Release/leak_test_project_MarkupCompile.cache
  100. 2
      leak_test_project/obj/Release/leak_test_project_MarkupCompile.lref

4
.agents/AGENTS.md

@ -0,0 +1,4 @@
# 프로젝트 개발 지침
- **개발 전 단계**: 작업이나 구현 계획을 세우기 전, 반드시 `ob/` 폴더 내에 있는 시스템 설계 문서 및 관련 문서들을 상세히 읽어 요구사항과 설계 방향을 확인해야 합니다.
- **개발 후 단계**: 코드 변경 또는 신규 기능 개발 완료 후, 작업 결과와 관련 설계 변경점을 `ob/` 폴더 내의 해당 문서에 성실히 업데이트하고 기록해야 합니다.

82
DB/dbo.EOL_Schema.sql

@ -1,10 +1,11 @@
-- ============================================================================
-- Project: NE1aW 온압센서
-- Document: dbo.EOL Table Schema (Simplified Version)
-- Document: dbo.EOL Table Schema (Latest Version)
-- Author: Antigravity AI
-- Date: 2026-05-28
-- Description: 사용자 요구사항(ID, 좌/우, SPEC UL, SPEC LL, 누설값, 판정)에 맞추어 단순화된 스키마.
-- 재검사 이력 추적을 위해 Seq(PK) 및 InspectDateTime(일시) 컬럼 추가.
-- Date: 2026-06-24
-- Description: 개편된 로그인 사양 및 16개 컬럼 결과 저장 구조에 맞춘 스키마.
-- 제품 일련번호(IC_SN)를 주 식별자로 사용하며,
-- 동일 제품 재검사 시 MERGE 문을 이용해 UPSERT 처리됩니다.
-- ============================================================================
-- 1. 데이터베이스 존재 여부 확인 및 자동 생성
@ -26,57 +27,78 @@ BEGIN
END
GO
-- 3. 단순화된 테이블 생성
-- 3. 개편된 테이블 생성
CREATE TABLE [dbo].[EOL] (
[InspectDate] NVARCHAR(10) NOT NULL, -- 검사 날짜 (YYYY-MM-DD)
[InspectTime] NVARCHAR(8) NOT NULL, -- 검사 시간 (HH:MM:SS)
[ID] NVARCHAR(30) NOT NULL, -- 센서 ID (ProductId) (기본키)
[Channel] NVARCHAR(10) NOT NULL, -- 좌 / 우 (LEFT / RIGHT)
[SpecUL] NVARCHAR(10) NOT NULL, -- SPEC UL
[SpecLL] NVARCHAR(10) NOT NULL, -- SPEC LL
[LeakValue] NVARCHAR(20) NOT NULL, -- 누설 측정값 (MeasuredValue)
[Result] NVARCHAR(5) NOT NULL, -- 판정 결과 (OK / NG)
[IC_SN] NVARCHAR(10) NOT NULL, -- 제품의 최종 조합 ID (ProductId) (기본키)
[PCB_Barcode] NVARCHAR(10) NOT NULL, -- 고유 식별용 바코드 정보 (UK)
[Maker] NVARCHAR(10) NULL, -- 로그인 메이커 정보
[Model] NVARCHAR(10) NULL, -- 로그인 모델 정보
[Variant_1] NVARCHAR(10) NULL, -- 로그인 변형1 정보
[Variant_2] NVARCHAR(10) NULL, -- 로그인 변형2 정보
[Operator] NVARCHAR(10) NOT NULL, -- 로그인 검사자 ID
[Production_Date] DATETIME2(0) NOT NULL DEFAULT GETDATE(), -- 검사 일시
[Line] NVARCHAR(5) NULL, -- 로그인 작업 라인
[Lot_No] NVARCHAR(8) NULL, -- 로그인 Lot 번호
[Jig_No] NVARCHAR(10) NULL, -- 로그인 지그 번호
[Channel] NVARCHAR(5) NOT NULL, -- 검증 수행 채널 (LEFT / RIGHT)
[Spec_UL] DECIMAL(3,2) NOT NULL, -- 스펙 상한값
[Spec_LL] DECIMAL(3,2) NOT NULL, -- 스펙 하한값
[Leak_Value] DECIMAL(5,4) NOT NULL, -- 측정된 누설량
[Result] NVARCHAR(5) NOT NULL, -- 합격 여부 판정 (OK / NG)
CONSTRAINT [PK_EOL_ID] PRIMARY KEY CLUSTERED ([ID] ASC)
CONSTRAINT [PK_EOL_IC_SN] PRIMARY KEY CLUSTERED ([IC_SN] ASC),
CONSTRAINT [UQ_EOL_PCB_Barcode] UNIQUE ([PCB_Barcode] ASC)
);
GO
PRINT 'Table [dbo].[EOL] created successfully with simplified columns and ID as Primary Key.';
PRINT 'Table [dbo].[EOL] created successfully with latest 16 columns and IC_SN as Primary Key.';
GO
-- ============================================================================
-- [부록] 테스트 시나리오 SQL 예시 (단순화 버전)
-- [부록] 테스트 시나리오 SQL 예시 (신규 개편 버전)
-- ============================================================================
/*
-- 1. 테스트용 기본 데이터 INSERT (정상 삽입)
INSERT INTO [dbo].[EOL] (
[InspectDate], [InspectTime], [ID], [Channel], [SpecUL], [SpecLL], [LeakValue], [Result]
[IC_SN], [PCB_Barcode], [Maker], [Model], [Variant_1], [Variant_2],
[Operator], [Production_Date], [Line], [Lot_No], [Jig_No], [Channel],
[Spec_UL], [Spec_LL], [Leak_Value], [Result]
)
VALUES (
'2026-06-02', '14:48:51', 'IC_SIMPLE_0001', 'LEFT', '1.00', '-1.00', '0.125', 'OK'
'SN00000001', 'SN00000001', 'MAKER_A', 'MODEL_X', 'V1', 'V2',
'OP_01', '2026-06-24 11:00:00', 'L1', 'LOT00001', 'JIG01', 'LEFT',
1.00, -1.00, 0.1250, 'OK'
);
-- 데이터 확인
SELECT * FROM [dbo].[EOL];
-- 2. 동일 ID 재검사 시 MERGE 문을 이용한 UPSERT 테스트 (정상 업데이트 확인)
-- 예상 결과: 기존 'IC_SIMPLE_0001' 레코드의 LeakValue가 '0.098'로 업데이트
-- 2. 동일 IC_SN 재검사 시 MERGE 문을 이용한 UPSERT 테스트 (정상 업데이트 확인)
-- 예상 결과: 기존 'SN00000001' 레코드의 Leak_Value가 '0.0980'으로 업데이트되고, Production_Date 등이 갱신
MERGE INTO [dbo].[EOL] AS Target
USING (SELECT 'IC_SIMPLE_0001' AS ID) AS Source
ON (Target.ID = Source.ID)
USING (SELECT 'SN00000001' AS IC_SN) AS Source
ON (Target.IC_SN = Source.IC_SN)
WHEN MATCHED THEN
UPDATE SET
Target.PCB_Barcode = 'SN00000001',
Target.Maker = 'MAKER_A',
Target.Model = 'MODEL_X',
Target.Variant_1 = 'V1',
Target.Variant_2 = 'V2',
Target.Operator = 'OP_01',
Target.Production_Date = '2026-06-24 11:05:00',
Target.Line = 'L1',
Target.Lot_No = 'LOT00001',
Target.Jig_No = 'JIG01',
Target.Channel = 'LEFT',
Target.SpecUL = '1.00',
Target.SpecLL = '-1.00',
Target.LeakValue = '0.098', -- 업데이트될 새 값
Target.Result = 'OK',
Target.InspectDate = '2026-06-02',
Target.InspectTime = '14:48:00'
Target.Spec_UL = 1.00,
Target.Spec_LL = -1.00,
Target.Leak_Value = 0.0980, -- 업데이트될 새 값
Target.Result = 'OK'
WHEN NOT MATCHED THEN
INSERT ([ID], [Channel], [SpecUL], [SpecLL], [LeakValue], [Result], [InspectDate], [InspectTime])
VALUES ('IC_SIMPLE_0001', 'LEFT', '1.00', '-1.00', '0.098', 'OK', '2026-06-02', '14:48:00');
INSERT (IC_SN, PCB_Barcode, Maker, Model, Variant_1, Variant_2, Operator, Production_Date, Line, Lot_No, Jig_No, Channel, Spec_UL, Spec_LL, Leak_Value, Result)
VALUES ('SN00000001', 'SN00000001', 'MAKER_A', 'MODEL_X', 'V1', 'V2', 'OP_01', '2026-06-24 11:05:00', 'L1', 'LOT00001', 'JIG01', 'LEFT', 1.00, -1.00, 0.0980, 'OK');
-- 데이터 확인 (값 업데이트 확인)
SELECT * FROM [dbo].[EOL];

38
leak_test_project/App.xaml.cs

@ -19,6 +19,44 @@ namespace leak_test_project
{
base.OnStartup(e);
// Database.ini 파일이 없는 경우 기본 템플릿 생성
try
{
string dbIniPath = System.IO.Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "Database.ini");
if (!System.IO.File.Exists(dbIniPath))
{
string defaultContent =
"; =====================================================================\r\n" +
"; Mobi Leak Test System - 데이터베이스 및 로그인 설정 파일 (Database.ini)\r\n" +
"; =====================================================================\r\n" +
"\r\n" +
"; [데이터베이스 접속 상세 설정]\r\n" +
"; Db_Server : 중앙 MSSQL 데이터베이스 서버의 IP 주소 또는 호스트명\r\n" +
"; Db_Name : 검사 데이터를 저장할 대상 데이터베이스 이름\r\n" +
"; Db_User : 데이터베이스 접속 계정 ID (예: sa)\r\n" +
"; Db_Password: 데이터베이스 접속 계정의 비밀번호\r\n" +
"Db_Server=192.168.0.20\r\n" +
"Db_Name=NE1aW_PT_SENSOR\r\n" +
"Db_User=sa\r\n" +
"Db_Password=sa1234\r\n" +
"\r\n" +
"[Login]\r\n" +
"; Procedure: 로그인 검증에 사용할 DB 저장 프로시저명입니다. (공란일 경우 기본 테이블 조회 쿼리 작동)\r\n" +
"Procedure=dbo.CheckOperator\r\n" +
"\r\n" +
"; ProcessName: 로그인 검증 시 전달할 공정 이름입니다.\r\n" +
"ProcessName=EOL\r\n" +
"\r\n" +
"; OfflinePreview: 오프라인 모드 활성화 여부 (true = DB 연결 없이 로그인 통과, false = 실제 DB 인증)\r\n" +
"OfflinePreview=false\r\n";
System.IO.File.WriteAllText(dbIniPath, defaultContent, System.Text.Encoding.UTF8);
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[App Database.ini Create Error] {ex.Message}");
}
// 메인 윈도우 생성 및 즉시 표시
var mainWindow = new MainWindow();
this.MainWindow = mainWindow;

24
leak_test_project/Database.ini

@ -0,0 +1,24 @@
; =====================================================================
; Mobi Leak Test System - 데이터베이스 및 로그인 설정 파일 (Database.ini)
; =====================================================================
; [데이터베이스 접속 상세 설정]
; Db_Server : 중앙 MSSQL 데이터베이스 서버의 IP 주소 또는 호스트명
; Db_Name : 검사 데이터를 저장할 대상 데이터베이스 이름
; Db_User : 데이터베이스 접속 계정 ID (예: sa)
; Db_Password: 데이터베이스 접속 계정의 비밀번호
Db_Server=192.168.0.20
Db_Name=NE1aW_PT_SENSOR
Db_User=sa
Db_Password=sa1234
[Login]
; Procedure: 로그인 검증에 사용할 DB 저장 프로시저명입니다. (공란일 경우 기본 테이블 조회 쿼리 작동)
Procedure=dbo.CheckOperator
; ProcessName: 로그인 검증 시 전달할 공정 이름입니다.
ProcessName=EOL
; OfflinePreview: 오프라인 모드 활성화 여부 (true = DB 연결 없이 로그인 통과, false = 실제 DB 인증)
OfflinePreview=false

4
leak_test_project/Infrastructure/CommunicationBase.cs

@ -122,7 +122,9 @@ namespace leak_test_project.Infrastructure
_serialPort.DiscardInBuffer();
_serialPort.DiscardOutBuffer();
_serialPort.ReadExisting();
} catch { }
} catch (Exception ex) {
System.Diagnostics.Debug.WriteLine($"[CommunicationBase ClearBuffer Error] {ex.Message}");
}
}
}

26
leak_test_project/Manual/.obsidian/workspace.json

@ -13,12 +13,12 @@
"state": {
"type": "markdown",
"state": {
"file": "사용자_메뉴얼.md",
"mode": "preview",
"file": "07_시험_프로세스.md",
"mode": "source",
"source": false
},
"icon": "lucide-file",
"title": "사용자_메뉴얼"
"title": "07_시험_프로세스"
}
}
]
@ -185,11 +185,25 @@
},
"active": "7fc461cd97381c41",
"lastOpenFiles": [
"10_오류_대응.md",
"09_로그_관리.md",
"06_데이터_조회.md",
"Pasted image 20260624104558.png",
"05_시험_설정.md",
"Pasted image 20260624104531.png",
"04_통신_설정.md",
"Pasted image 20260624104506.png",
"03_메인_화면.md",
"Pasted image 20260624104445.png",
"Pasted image 20260624104353.png",
"Pasted image 20260624104256.png",
"Pasted image 20260624104208.png",
"Pasted image 20260624104139.png",
"02_초기_설정.md",
"01_시스템_개요.md",
"사용자_메뉴얼.md"
"08_로그_관리.md",
"09_오류_대응.md",
"사용자_메뉴얼.md",
"07_시험_프로세스.md",
"10_오류_대응.md",
"09_로그_관리.md"
]
}

19
leak_test_project/Manual/01_시스템_개요.md

@ -17,7 +17,6 @@
| **Sentinel C28 연동** | RS-232 시리얼 통신으로 누설 센서의 실시간 측정값을 수신합니다 |
| **4251 보드 연동** | 제품 ID(ZMDI 센서) 판독을 위한 시리얼 통신을 수행합니다 |
| **PCI-7432 DIO 보드** | 디지털 입출력 제어 (시작 신호 수신, OK/NG 출력) |
| **실시간 I/O 모니터링** | 디지털 입출력 상태를 500ms 주기로 실시간 감시합니다 |
| **검사 기록 관리** | 날짜/판정/ID별 필터링 검색 및 CSV 파일 내보내기를 지원합니다 |
| **SPEC 교차 검증** | 프로그램 판정과 센서 자체 판정을 비교하여 불일치 시 경고합니다 |
| **순차 실행 보장** | 세마포어 잠금으로 좌/우 채널 동시 시험을 방지합니다 |
@ -98,23 +97,23 @@
```
┌────────────────────────────────────────────────┐
│ 상단 타이틀 바 (프로그램명, 시간, 창 제어 버튼)
│ 상단 타이틀 바 (프로그램명, 시간, 배지, 창 제어)│
├────────────────────────────────────────────────┤
│ │
│ 메인 콘텐츠 영역 │
│ (Home / I/O Monitor / Data View 전환) │
(Home / Data View 전환)
│ │
├────────────────────────────────────────────────┤
│ 하단 메뉴 바 (I/O Monitor, Data, Parameters,
Comm. Settings, ID Test L/R) │
│ 하단 메뉴 바 (Data, Parameters, Comm.Settings,
│ ID Test L/R)
└────────────────────────────────────────────────┘
```
| 영역 | 높이 | 설명 |
|------|------|------|
| 상단 타이틀 바 | 55px | 프로그램 제목, 현재 시간, 버전 정보, 창 제어 버튼 |
| 메인 콘텐츠 | 가변 | 선택된 화면(Home/I-O/Data)이 표시되는 영역 |
| 하단 메뉴 바 | 75px | 화면 전환 및 설정 팝업 버튼 |
| 영역 | 설명 |
|------|------|
| 상단 타이틀 바 | 프로그램 제목, 실시간 날짜/시간, 현재 로그인된 작업자/모델/라인 정보 배지, 세션 로그아웃 버튼, 시스템 창 제어 버튼 배치 |
| 메인 콘텐츠 영역 | 선택된 기능 화면(Home 검사 모니터 또는 Data 데이터 조회)이 표시되는 주 콘텐츠 영역 |
| 하단 메뉴 바 | 각 화면 전환 및 설정을 위한 조작 버튼 영역 |
---

47
leak_test_project/Manual/02_초기_설정.md

@ -53,7 +53,50 @@
---
## 2.3 최초 설정 순서 (Step-by-Step)
## 2.3 프로그램 로그인 및 오프라인 모드 설정
프로그램 실행이 완료되면 메인 화면이 활성화되기 전, **작업 정보 입력 및 로그인 창**이 모달로 표시됩니다. 로그인을 성공적으로 마쳐야 메인 화면을 조작할 수 있습니다.
### 로그인 필드 구성
| 입력 필드 | 예시 | 설명 |
|-----------|------|------|
| **Maker** | `MOBIS` | 제조사 정보 |
| **Model** | `C28_TEST` | 검사 모델명 |
| **Variant 1** | `V1` | 세부 파생 모델 정보 1 |
| **Variant 2** | `V2` | 세부 파생 모델 정보 2 |
| **ID (Operator)** | `sa` | 작업자 ID |
| **PW (Password)** | `****` | 비밀번호 (DB 조회용) |
| **Line** | `EOL_01` | 생산 라인 번호 |
| **Lot No** | `LOT001` | 로트 번호 |
| **Jig No** | `JIG_L` | 지그 번호 |
> **참고 (자동완성)**: 각 필드에 마우스를 클릭하거나 포커스를 두면, 이전 로그인 이력이 팝업 메뉴 형식으로 나열되어 간편하게 자동 완성을 사용할 수 있습니다.
### 로그인 방식 설정 (Database.ini)
프로그램 실행 폴더에 위치한 `Database.ini` 파일 설정에 따라 로그인 인증 방식이 달라집니다. (해당 파일이 누락된 경우 프로그램 실행 시 기본 템플릿 정보와 함께 자동으로 생성됩니다.)
```ini
; [데이터베이스 접속 상세 설정]
Db_Server=192.168.0.20
Db_Name=NE1aW_PT_SENSOR
Db_User=sa
Db_Password=sa1234
[Login]
Procedure=dbo.CheckOperator
ProcessName=EOL
OfflinePreview=false
```
- **온라인 인증 모드 (`OfflinePreview=false`)**: 지정된 개별 데이터베이스 접속 정보(`Db_Server`, `Db_Name`, `Db_User`, `Db_Password`)를 기반으로 데이터베이스에 접속하여 Stored Procedure(`Procedure`)로 ID/PW 존재 여부를 직접 확인합니다. DB 서버 연결 실패나 미등록 사용자일 경우 로그인이 거부됩니다.
- **오프라인 테스트 모드 (`OfflinePreview=true`)**: 데이터베이스를 거치지 않고, 필수 입력 값을 채우면 즉시 로그인이 허용됩니다. 네트워크 연결이 안 된 시뮬레이션 및 테스트 환경에서 사용합니다.
---
## 2.4 최초 설정 순서 (Step-by-Step)
프로그램을 처음 사용할 때는 반드시 아래 순서로 설정을 진행하세요.
@ -122,7 +165,7 @@
---
## 2.4 설정 변경 시 주의사항
## 2.5 설정 변경 시 주의사항
| 상황 | 동작 | 주의 |
|------|------|------|

60
leak_test_project/Manual/03_메인_화면.md

@ -7,14 +7,15 @@
프로그램 실행 시 기본으로 표시되는 화면입니다. 좌/우 채널의 누설 시험 결과를 실시간으로 모니터링합니다.
## 3.1 전체 화면 레이아웃
![[Pasted image 20260624104208.png]]
```
┌────────────────────────────────────────────────────────────────┐
│ Pressure Leak Inspect System 2026-05-08 [목] 10:30:00 ─□X │ ← 상단 타이틀 바
│ Pressure Leak v1.0.0 2026-06-23 [화] 10:30:00 [작업자][모델][라인] [로그아웃] ─□X│ ← 타이틀 바
├─────────────────────────────┬──────────────────────────────────┤
│ LEFT LEAK TESTER │ RIGHT LEAK TESTER │
├─────────────────────────────┼──────────────────────────────────┤
│ 시작 시간 │ 2026-05-08 ... │ 시작 시간 │ 2026-05-08 ... │
│ 시작 시간 │ 2026-06-23 ... │ 시작 시간 │ 2026-06-23 ... │
│ ID/LOW ID │ ABC123 / 00FF │ ID/LOW ID │ DEF456 / 01AA │
│ 측정값 │ 0.5234 sccm │ 측정값 │ 0.3124 sccm │
│ │ SPEC UL: 1.00 │ │ SPEC UL: 1.00 │
@ -31,14 +32,16 @@
## 3.2 상단 타이틀 바
프로그램 최상단에 위치한 짙은 남색(#2C3E50) 배경의 바입니다.
프로그램 최상단에 위치한 짙은 블랙 그레이 배경의 바입니다.
![[Pasted image 20260624104256.png]]
| 위치 | 요소 | 설명 |
|------|------|------|
| 좌측 | **프로그램 제목** | "Pressure Leak Inspect System" (백색, 24pt 볼드) |
| 중앙~우측 | **현재 날짜/시간** | `2026-05-08 [목] 10:30:00` 형식, 1초 주기로 실시간 갱신 |
| 우측 | **버전 정보** | `Ver. 1.0.0 | Updated 2026-04-03` (회색 텍스트) |
| 최우측 | **창 제어 버튼** | 최소화(─) / 최대화·복원(□) / 종료(X, 빨간색) |
| 좌측 | **프로그램 제목 & 버전** | "Pressure Leak" (백색 볼드) 및 `v1.0.0 (Updated: 2026-06-05)` 표시 |
| 중앙 | **현재 날짜/시간** | `yyyy-MM-dd [요일] HH:mm:ss` 형식, 1초 주기로 실시간 갱신 |
| 우측 | **작업 정보 배지** | 현재 로그인 상태에 따른 `작업자 ID`, `검사 모델`, `생산 라인` 정보를 배지로 시각화하여 현재 작업 상태 인지 |
| 우측 | **로그아웃 버튼** | 현재 세션을 로그아웃하고, 다시 로그인 모달 창을 띄웁니다 |
| 최우측 | **창 제어 버튼** | 최소화(─) / 최대화·복원(□) / 종료(X)를 수행하는 윈도우 창 제어 아이콘 배치 |
### 타이틀 바 조작
@ -55,13 +58,13 @@
## 3.3 메인 영역 - 채널 모니터링
화면은 좌측(`LEFT LEAK TESTER`)과 우측(`RIGHT LEAK TESTER`)으로 균등 분할되며, 각 채널은 동일한 구조입니다.
![[Pasted image 20260624104353.png]]
### 각 채널의 구성 요소 (위에서 아래 순서)
#### ① 채널 타이틀 (Row 0)
- **LEFT LEAK TESTER** 또는 **RIGHT LEAK TESTER**
- 짙은 남색(#34495E) 배경에 백색 볼드 텍스트 (20pt)
- 높이: 50px
- 짙은 남색 배경에 백색 볼드 텍스트로 채널 구분을 명시합니다.
#### ② 시작 시간 (Row 1)
| 항목 | 설명 |
@ -69,7 +72,6 @@
| 라벨 | `시작 시간` (회색 헤더 배경) |
| 값 | `yyyy-MM-dd HH:mm:ss` 형식 (예: `2026-05-08 10:30:15`) |
| 갱신 시점 | 시험이 시작될 때마다 현재 시각으로 자동 갱신 |
| 높이 | 42px |
#### ③ ID / LOW ID (Row 2)
| 항목 | 설명 |
@ -78,7 +80,6 @@
| 값 | `{제품ID} / {Low ID}` 형식으로 조합 표시 |
| 데이터 소스 | 4251 보드를 통해 ZMDI 센서에서 읽은 값 |
| 갱신 시점 | 시험 시작 시 초기화, 센서 판독 완료 시 표시 |
| 높이 | 42px |
#### ④ 측정값 + SPEC (Row 3) - **가장 큰 영역**
좌측에 측정값, 우측에 SPEC 규격이 함께 표시됩니다.
@ -87,29 +88,29 @@
| 항목 | 설명 |
|------|------|
| 라벨 | `측정값` (18pt 볼드) |
| 라벨 | `측정값` (볼드) |
| 값 | Sentinel C28에서 수신한 누설량 (소수점 4자리, 예: `0.5234`) |
| 단위 | `sccm` (우측 하단에 표시) |
| 폰트 크기 | **84pt** (매우 크게 표시하여 원거리에서도 확인 가능) |
| 색상 | 파란색(#2980B9) 볼드 |
| 폰트 크기 | 원거리에서도 쉽게 식별할 수 있도록 대형 폰트로 강조 표시 |
| 색상 | 파란색 볼드 |
| 갱신 시점 | C28 센서 스트리밍 데이터 수신 시 실시간 갱신 |
**SPEC 영역 (우측 210px):**
**SPEC 영역 (우측):**
| 항목 | 설명 |
|------|------|
| `UL` | 상한 규격값 (예: `1.00`), 26pt 볼드 |
| `LL` | 하한 규격값 (예: `-1.00`), 26pt 볼드 |
| `UL` | 상한 규격값 (예: `1.00`) 볼드 |
| `LL` | 하한 규격값 (예: `-1.00`) 볼드 |
| 갱신 시점 | Parameters에서 저장 시 즉시 반영 |
#### ⑤ 판정 (Row 4) - **두 번째로 큰 영역**
| 항목 | 설명 |
|------|------|
| 라벨 | `판정` (18pt 볼드) |
| 값 | **OK** 또는 **NG** (96pt, ExtraBold) |
| OK 표시 | 녹색 텍스트(#27AE60) + 연녹색 배경(#D4EFDF) |
| NG 표시 | 빨간색 텍스트(#C0392B) + 연빨간색 배경(#FADBD8) |
| 라벨 | `판정` (볼드) |
| 값 | **OK** 또는 **NG** (대형 볼드) |
| OK 표시 | 녹색 텍스트 + 연녹색 배경으로 가시성 극대화 |
| NG 표시 | 빨간색 텍스트 + 연빨간색 배경으로 가시성 극대화 |
> **판정 기준**: `SPEC LL ≤ 측정값 ≤ SPEC UL`**OK**, 그 외 → **NG**
@ -118,8 +119,7 @@
| 항목 | 설명 |
|------|------|
| 라벨 | `진행 메시지` (연파랑 배경) |
| 값 | 현재 시험 단계를 나타내는 텍스트 (22pt 볼드, 파란색) |
| 높이 | 60px |
| 값 | 현재 시험 단계를 나타내는 파란색 볼드 텍스트 |
표시되는 메시지 목록:
@ -140,7 +140,7 @@
| 항목 | 설명 |
|------|------|
| 라벨 | `오류 메시지` (연주황 배경, 갈색 텍스트) |
| 값 | 오류 내용 (빨간색, 18pt 볼드). 스크롤 가능 |
| 값 | 오류 내용 (빨간색 볼드). 스크롤 가능 |
| 특징 | 읽기 전용 텍스트 박스, 텍스트 줄바꿈 자동, 세로 스크롤바 |
| 초기화 시점 | 새 시험 시작 시 자동 초기화 |
@ -159,9 +159,10 @@
## 3.5 하단 메뉴 바
프로그램 최하단의 연회색(#D5DBDB) 배경 영역입니다. 5개의 버튼이 좌측부터 수평으로 배치됩니다.
프로그램 최하단의 연회색 배경 영역입니다. 5개의 버튼이 좌측부터 수평으로 배치됩니다.
![[Pasted image 20260624104445.png]]
| 순서 | 버튼 텍스트 | 부제 | 동작 | 동작 방식 |
| 순서 | BUTTON | 부제 | 동작 | 동작 방식 |
|------|------------|------|------|----------|
| 1 | **Data** | (측정 데이터) | 검사 기록 조회 화면 전환 | 토글 (다시 클릭 시 Home으로 복귀) |
| 2 | **Parameters** | (시험 설정) | 시험 설정 팝업 창 열기 | 모달 팝업 (설정 후 자동 닫힘) |
@ -170,10 +171,9 @@
| 5 | **ID Test (R)** | (우측 ID 테스트) | 우측 채널 센서 ID 수동 판독 | 즉시 실행 (파란색 텍스트) |
### 버튼 스타일
- 크기: 140px × 55px
- 배경: 연회색(#F2F4F4), 마우스 오버 시 약간 어두워짐(#E5E8E8)
- 테두리: 회색(#85929E), 1.5px
- 텍스트: 14pt 볼드 (부제는 10pt 일반)
- 배경: 연회색, 마우스 오버 시 시각적 피드백을 위해 약간 어둡게 변함
- 테두리: 회색
- 텍스트: 볼드 (부제는 일반 텍스트)
---

13
leak_test_project/Manual/04_통신_설정.md

@ -5,7 +5,7 @@
---
하단 메뉴에서 **Comm. Settings (통신 설정)** 버튼 클릭 시 열리는 **모달 팝업 창**입니다.
![[Pasted image 20260624104506.png]]
## 4.1 창 구성
```
@ -24,9 +24,8 @@
| 속성 | 값 |
|------|-----|
| 창 크기 | 750px × 자동 높이 (최대 700px) |
| 크기 변경 | 불가 |
| 위치 | 메인 창 중앙 |
| 창 크기 | 고정 (크기 변경 불가) |
| 위치 | 메인 창 중앙에 정렬되어 표시 |
| 타입 | 모달 (이 창이 열려있으면 메인 창 조작 불가) |
## 4.2 4251 보드 설정 (좌측 영역)
@ -45,10 +44,10 @@
## 4.4 버튼 설명
| 버튼 | 색상 | 동작 |
| 버튼 | 특징 | 동작 |
|------|------|------|
| **설정 저장** | 연녹색 배경, 녹색 테두리 | config.xml에 저장 → 기존 연결 해제 → 새 설정으로 재연결 → "통신 설정이 저장되었습니다." 메시지 → 창 닫힘 |
| **닫기** | 기본 회색 | 변경 사항 저장하지 않고 창 닫기 |
| **설정 저장** | 녹색 계열 | config.xml에 저장 → 기존 연결 해제 → 새 설정으로 재연결 → "통신 설정이 저장되었습니다." 메시지 → 창 닫힘 |
| **닫기** | 회색 계열 | 변경 사항 저장하지 않고 창 닫기 |
## 4.5 저장 시 내부 동작 순서

9
leak_test_project/Manual/05_시험_설정.md

@ -5,7 +5,7 @@
---
하단 메뉴에서 **Parameters (시험 설정)** 버튼 클릭 시 열리는 **모달 팝업 창**입니다.
![[Pasted image 20260624104531.png]]
## 5.1 창 구성
```
@ -25,9 +25,8 @@
| 속성 | 값 |
|------|-----|
| 창 크기 | 550px × 자동 높이 (최대 600px) |
| 크기 변경 | 불가 |
| 위치 | 메인 창 중앙 |
| 창 크기 | 고정 (크기 변경 불가) |
| 위치 | 메인 창 중앙에 정렬되어 표시 |
| 타입 | 모달 |
## 5.2 설정 항목
@ -67,4 +66,4 @@
---
[← 이전: 통신 설정](04_통신_설정.md) | [다음: I/O Monitor →](06_IO_모니터.md)
[← 이전: 통신 설정](04_통신_설정.md) | [다음: 측정 데이터 조회 →](06_데이터_조회.md)

99
leak_test_project/Manual/06_데이터_조회.md

@ -0,0 +1,99 @@
# 6. 측정 데이터 조회 (Data View)
[← 메뉴얼 목차로 돌아가기](사용자_메뉴얼.md)
---
하단 메뉴에서 **Data (측정 데이터)** 버튼을 클릭하면 표시됩니다. 다시 클릭하면 메인 화면으로 복귀합니다.
![[Pasted image 20260624104558.png]]
## 6.1 화면 구성
```
┌───────────────────────────────────────────────────────────────────────────────────────────┐
│ INSPECT DATA LIST (측정 기록 조회) │ ← 타이틀 바
├───────────────────────────────────────────────────────────────────────────────────────────┤
│ [조회기간] 2026-05-01 ~ 2026-05-08 [판정 결과] 전체 │
│ [ID 검색] [작업자] [Lot No] [모델] [Search] [CSV] │ ← 2행 필터 영역
├──────┬──────┬───────┬────────┬──────┬─────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┼
│검사일│검사시│ IC SN │ PCB │메이커│모델 │변형1 │변형2 │작업자│라인 │Lot No│지그 │... │ ← 데이터 그리드
├──────┼──────┼───────┼────────┼──────┼─────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼
│ ... │ ... │ABC123 │ ABC123 │ MOBI │ EOL │ A │ B │ admin│ 1 │LOT001│ 1 │... │
├──────┴──────┴───────┴────────┴──────┴─────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┼
│ [ Close ] │
└───────────────────────────────────────────────────────────────────────────────────────────┘
```
## 6.2 필터 조건 상세
### 1행: 공통 기본 필터
| 항목 | UI 타입 | 기본값 | 설명 |
|------|---------|--------|------|
| **조회 기간** | DatePicker (달력) x 2 | 오늘로부터 7일 전 ~ 오늘 | 조회 대상 날짜 범위 설정 (달력 확대 표시 적용) |
| **판정 결과** | 드롭다운 ComboBox | `[전체]` / `OK` / `NG` | 판정 결과에 따른 필터링 (기본값: 전체) |
### 2행: 상세 검색 필터 (DB 스키마 필드 교차 검색)
| 항목 | UI 타입 | 설명 | 기본값 |
|------|---------|------|--------|
| **ID 검색** | TextBox (텍스트 입력) | `IC_SN` 또는 `PCB_Barcode` 컬럼에 대한 부분 일치 (LIKE) 검색 | (빈 값 = 필터 제외) |
| **작업자** | TextBox (텍스트 입력) | 작업자 ID (`Operator`) 컬럼에 대한 부분 일치 검색 | (빈 값 = 필터 제외) |
| **Lot No** | TextBox (텍스트 입력) | 랏 번호 (`Lot_No`) 컬럼에 대한 부분 일치 검색 | (빈 값 = 필터 제외) |
| **모델** | TextBox (텍스트 입력) | 모델명 (`Model`) 컬럼에 대한 부분 일치 검색 | (빈 값 = 필터 제외) |
## 6.3 조회 및 내보내기 버튼
| 버튼 | 색상 | 동작 |
|------|------|------|
| **Search** | 파란색 계열 | 설정된 2행의 모든 필터 조건으로 SQL Server EOL DB(혹은 CSV)를 부분일치 검색하여 그리드에 표시합니다. |
| **CSV** | 녹색 계열 | 검색 결과를 새 CSV 파일(16개 전체 컬럼 사양)로 저장합니다. |
### Search 버튼 동작 상세
1. DB 연결 활성화 시 SQL Server `dbo.EOL` 테이블에서 Like 조건 쿼리 실행.
2. DB 연결 실패/주소 미설정 시 로컬 CSV 파일에서 인메모리 부분일치 검색 처리 (구형 8컬럼 파일 하위 호환).
3. 검색 결과가 없으면 "해당 조건의 데이터가 없습니다." 알림 표시.
### CSV 버튼 동작 상세
1. 검색 결과가 없으면 "저장할 데이터가 없습니다." 알림 표시.
2. 파일 저장 대화상자 표시 (기본 파일명: `InspectData_yyyyMMdd_HHmmss.csv`).
3. 저장 완료 시 "CSV 파일로 저장되었습니다." 알림 표시.
## 6.4 데이터 그리드 컬럼
| 컬럼 헤더명 | 바인딩 프로퍼티 | 정렬 | 설명 |
|------|------|------|------|
| **검사일자** | `InspectDate` | 좌측 | 검사 일자 (yyyy-MM-dd) |
| **검사시간** | `InspectTime` | 좌측 | 검사 시각 (HH:mm:ss) |
| **IC SN (제품ID)** | `ProductId` (`IcSn`) | 좌측 | 제품 고유 IC SN 시리얼 번호 |
| **PCB 바코드** | `PcbBarcode` | 좌측 | PCB 바코드 식별자 |
| **메이커** | `Maker` | 좌측 | 로그인 시 입력된 제조사명 |
| **모델** | `Model` | 좌측 | 로그인 시 입력된 검사 모델명 |
| **변형 1** | `Variant1` | 좌측 | 로그인 시 입력된 변형 1 사양 |
| **변형 2** | `Variant2` | 좌측 | 로그인 시 입력된 변형 2 사양 |
| **작업자** | `Operator` | 좌측 | 작업자 사용자 ID |
| **작업라인** | `Line` | 좌측 | 로그인 공정 작업 라인 |
| **Lot No** | `LotNo` | 좌측 | 생산 Lot 번호 |
| **지그 번호** | `JigNo` | 좌측 | 사용 지그(Jig) 번호 |
| **검사채널** | `Channel` | 좌측 | 검사 통신 채널 (`LEFT` / `RIGHT`) |
| **SPEC UL** | `SpecUL` | 좌측 | 상한 규격값 |
| **SPEC LL** | `SpecLL` | 좌측 | 하한 규격값 |
| **릭 측정값** | `MeasuredValue` | **우측** | 측정된 리크 유량값 (**볼드 표시**) |
| **최종판정** | `Judgment` | **중앙** | `OK`(녹색) 또는 `NG`(빨간색) 볼드 강조 |
### 그리드 스타일
- 격자선: 수평/수직 연회색 격자선 표시
- 홀수 행 배경: 흰색, 짝수 행 배경: 연회색 배경으로 행 식별 편의 제공
- 선택된 행: 연파랑 배경에 파란색 텍스트로 강조
- 컬럼 헤더 클릭 시 해당 열 기준 오름차순/내림차순 정렬 가능 (컬럼 순서 변경 불가)
- 최소 50행이 항상 표시됩니다 (데이터가 적어도 격자 형태 유지를 위해 빈 행으로 채워짐)
## 6.5 화면 닫기
| 방법 | 설명 |
|------|------|
| 하단 **Close** 버튼 클릭 | 메인 화면으로 복귀 |
| 하단 메뉴 **Data** 버튼 재클릭 | 토글 동작으로 메인 화면으로 복귀 |
---
[← 이전: 시험 설정](05_시험_설정.md) | [다음: 자동 시험 프로세스 →](07_시험_프로세스.md)

99
leak_test_project/Manual/07_데이터_조회.md

@ -1,99 +0,0 @@
# 6. 측정 데이터 조회 (Data View)
[← 메뉴얼 목차로 돌아가기](사용자_메뉴얼.md)
---
하단 메뉴에서 **Data (측정 데이터)** 버튼을 클릭하면 표시됩니다. 다시 클릭하면 메인 화면으로 복귀합니다.
## 7.1 화면 구성
```
┌───────────────────────────────────────────────────────────┐
│ INSPECT DATA LIST (측정 기록 조회) │ ← 타이틀 바
├───────────────────────────────────────────────────────────┤
│ [조회기간] 2026-05-01 ~ 2026-05-08 [판정] 전체 [ID검색] │
│ [Search] [CSV] │ ← 필터 영역
├──────┬──────┬───────┬────┬──────┬──────┬──────┬────┬─────┤
│ 날짜 │재시험│ ID │좌우│UL │LL │측정값│판정│비고 │ ← 데이터 그리드
│ │ │ │ │ │ │ │ │ │
│ ... │ N │ABC123 │LEFT│ 1.00 │-1.00 │0.5234│ OK │ │
│ ... │ N │DEF456 │RIGHT│1.00 │-1.00 │1.2000│ NG │ │
├──────┴──────┴───────┴────┴──────┴──────┴──────┴────┴─────┤
│ [ Close ] │
└───────────────────────────────────────────────────────────┘
```
## 6.2 필터 조건 상세
### 조회 기간 (폭 320px)
| 항목 | UI 타입 | 기본값 | 설명 |
|------|---------|--------|------|
| **시작일** | DatePicker (달력) | 오늘로부터 7일 전 | 조회 시작 날짜 |
| **종료일** | DatePicker (달력) | 오늘 | 조회 종료 날짜 |
> **참고**: 달력 팝업은 1.25배 확대되어 표시됩니다.
### 판정 결과 (폭 120px)
| 항목 | UI 타입 | 선택 가능 값 | 기본값 |
|------|---------|-------------|--------|
| **판정 결과** | 드롭다운 | `[전체]` / `OK` / `NG` | `[전체]` |
### ID 검색 (폭 280px)
| 항목 | UI 타입 | 설명 | 기본값 |
|------|---------|------|--------|
| **ID 검색** | 텍스트 입력 | 제품 ID에 포함된 텍스트로 부분 일치 검색 | (빈 값 = 전체) |
## 6.3 조회 및 내보내기 버튼
| 버튼 | 색상 | 크기 | 동작 |
|------|------|------|------|
| **Search** | 연파랑 배경, 파랑 테두리 | 110×45px | 설정된 필터 조건으로 Logs 폴더의 CSV 파일을 검색합니다 |
| **CSV** | 연녹색 배경, 녹색 테두리 | 110×45px | 검색 결과를 새 CSV 파일로 저장합니다 (저장 위치 선택 대화상자) |
### Search 버튼 동작 상세
1. `[프로그램폴더]\Logs\` 폴더에서 조회 기간에 해당하는 `yyyy-MM-dd.csv` 파일을 탐색
2. 각 CSV 파일의 데이터를 판정/ID 필터 조건으로 필터링
3. 결과를 날짜 역순(최신순)으로 정렬하여 데이터 그리드에 표시
4. 검색 결과가 없으면 "해당 조건의 데이터가 없습니다." 알림 표시
### CSV 버튼 동작 상세
1. 검색 결과가 없으면 "저장할 데이터가 없습니다." 알림 표시
2. 파일 저장 대화상자 표시 (기본 파일명: `InspectData_yyyyMMdd_HHmmss.csv`)
3. 저장 완료 시 "CSV 파일로 저장되었습니다." 알림 표시
## 6.4 데이터 그리드 컬럼
| 컬럼 | 폭 | 정렬 | 설명 |
|------|-----|------|------|
| **날짜** | 130px | 좌측 | 검사 일자 (yyyy-MM-dd 형식) |
| **재시험** | 70px | 좌측 | 재시험 여부 (`Y` 또는 `N`) |
| **ID** | 250px | 좌측 | 제품 ID (ZMDI 센서에서 읽은 값) |
| **좌/우** | 70px | 좌측 | 검사 채널 (`LEFT` 또는 `RIGHT`) |
| **SPEC UL** | 80px | 좌측 | 검사 시점의 상한 규격값 |
| **SPEC LL** | 80px | 좌측 | 검사 시점의 하한 규격값 |
| **측정값** | 100px | **우측** | 측정된 결과 sccm (**볼드**) |
| **판정** | 80px | **중앙** | `OK`(녹색, #27AE60) 또는 `NG`(빨간색, #C0392B), **ExtraBold** |
| **비고** | 나머지(300px~) | 좌측 | 추가 메모 (현재 미사용) |
### 그리드 스타일
- 행 높이: 35px
- 격자선: 수평/수직 모두 연회색(#ECF0F1)
- 홀수 행 배경: 흰색, 짝수 행 배경: 연회색(#F9FBFC)
- 선택된 행: 연파랑 배경(#D6EAF8), 파란색 텍스트(#2E86C1)
- 컬럼 헤더 정렬 가능, 컬럼 순서 변경 불가
- 최소 50행이 항상 표시됩니다 (데이터가 적어도 빈 행으로 채워짐)
## 6.5 화면 닫기
| 방법 | 설명 |
|------|------|
| 하단 **Close** 버튼 클릭 | 메인 화면으로 복귀 |
| 하단 메뉴 **Data** 버튼 재클릭 | 토글 동작으로 메인 화면으로 복귀 |
---
[← 이전: 시험 설정](05_시험_설정.md) | [다음: 자동 시험 프로세스 →](08_시험_프로세스.md)

44
leak_test_project/Manual/08_시험_프로세스.md → leak_test_project/Manual/07_시험_프로세스.md

@ -30,40 +30,38 @@
│ 2단계: 센서 정보 읽기 │
│ · 4251 보드를 통해 ZMDI 센서에서 제품 ID 판독 │
│ · ID / LOW ID를 메인 화면에 표시 │
│ · 읽기 실패해도 빈 데이터("-")로 계속 진행 │
│ · ID 읽기 실패 시: "ID 읽기 실패" 오류 출력 후 │
│ "NG" 판정으로 DB 비동기 저장 및 조기 종료 (Skip) │
│ (DB 저장 실패 시 "DB 저장 실패" 문구 표시) │
└──────────────┬───────────────────────────────────────┘
┌──────────────────────────────────────────────────────┐
│ 3단계: 불량 제품 필터링 │
│ · 센서의 PrevResult 값 확인 │
│ · PrevResult = "F" → 정상 (다음 단계로 진행) │
│ · PrevResult ≠ "F" → "불량 제품 투입" 오류 → 시험 중단│
└──────────────┬───────────────────────────────────────┘
┌──────────────────────────────────────────────────────┐
│ 4단계: LEAK 시험 수행 │
│ 3단계: LEAK 시험 수행 │
│ · "LEAK 시험중" 메시지 표시 │
│ · Sentinel C28 센서의 최종 결과(Auto Result) 대기 │
│ · 최대 대기 시간: 30초 │
│ · 타임아웃 시: NG 판정 + "Sentinel C28 통신 타임아웃" │
│ (DB 저장 실패 시 "DB 저장 실패" 문구 표시) │
└──────────────┬───────────────────────────────────────┘
┌──────────────────────────────────────────────────────┐
5단계: 판정 │
4단계: 판정 │
│ · SPEC LL ≤ 측정값 ≤ SPEC UL → OK │
│ · 범위 밖 → NG │
└──────────────┬───────────────────────────────────────┘
┌──────────────────────────────────────────────────────┐
6단계: SPEC 교차 검증 │
5단계: SPEC 교차 검증 │
│ · 프로그램 판정과 센서 자체 판정 비교 │
│ · 센서 판정 정규화: OK/A/P → Accept, NG/R/F → Reject │
│ · 불일치 시: SpecMismatch = true → 팝업 경고 │
└──────────────┬───────────────────────────────────────┘
┌──────────────────────────────────────────────────────┐
7단계: 결과 출력
6단계: 결과 출력 및 저장
│ · CSV 로그 파일에 검사 데이터 기록 │
│ · MSSQL 중앙 데이터베이스 비동기 저장 │
│ · DB 저장 실패 시: 오류 메시지 칸에 "DB 저장 실패" 표시 │
│ · DIO OK 또는 NG 출력 ON │
│ · 메인 화면에 측정값, 판정 결과 표시 │
│ · "시험 완료" 메시지 표시 │
@ -89,10 +87,10 @@
Sentinel C28 센서에서 30초 이내에 응답이 없으면:
1. "Sentinel C28 통신 타임아웃" 오류 메시지 표시
1. "Sentinel C28 통신 타임아웃" 오류 메시지 표시 (DB 저장 실패 시 "DB 저장 실패"도 오류 메시지에 추가 표시)
2. 해당 채널 DIO `NG` 출력 ON
3. 측정값 `0.0000`, 판정 `NG`, 센서판정 `T/O` (Timeout)로 기록
4. CSV 로그 파일에도 NG로 기록
4. CSV 로그 파일 및 MSSQL 데이터베이스에 NG 결과 비동기 기록
## 7.5 C28 센서 결과 채널 라우팅
@ -114,12 +112,24 @@ Sentinel C28 센서에서 수신한 데이터는 채널 번호(ChannelNo)로 구
3. 500ms 대기 (UI 피드백용)
4. "센서 정보 읽는 중" 메시지 표시
5. 4251 보드를 통해 ZMDI 센서 ID 판독
6. 성공 시: ID / LOW ID 표시 + 불량 제품 필터링 확인 + "ID 테스트 완료"
6. 성공 시: ID / LOW ID 표시 + "ID 테스트 완료"
7. 실패 시: ID를 "-"로 표시 + "ID 테스트 실패"
8. 세마포어 잠금 해제
> **용도**: 설치 후 하드웨어 연결 상태 확인, 디버깅 시 활용합니다. LEAK 시험(4~7단계)은 수행하지 않습니다.
> **용도**: 설치 후 하드웨어 연결 상태 확인, 디버깅 시 활용합니다. LEAK 시험(3~6단계)은 수행하지 않습니다.
## 7.7 SPEC 교차 검증 및 판정 저장 기준
시험 과정에서 프로그램(UI) 판정과 C28 센서 자체의 판정이 불일치하는 경우, 데이터베이스 및 로그 저장의 기준은 다음과 같습니다.
### 저장 기준 요약
* **최종 저장 판정 (Result)**: C28 센서 자체의 판정값과 무관하게, 항상 **프로그램(UI) 판정 기준(`programJudgment`)**으로 데이터베이스 및 로그에 저장됩니다.
* **동작 예시**: UI 상으로 합격(`OK`)이지만 C28 센서 자체적으로는 불합격(`NG`/`R`)인 경우, 최종 DB(`Result` 컬럼) 및 CSV 로그에는 **`OK` (합격)**로 저장됩니다.
### 교차 검증 실패 시의 동작 (Spec Mismatch)
* 프로그램 판정과 센서 자체 판정이 다르면 `specMismatch` 플래그가 `true`로 설정됩니다.
* 불일치 발생 시 콘솔 로그에 경고(`[TestProcess] {side} SPEC Mismatch!`)를 출력하고 UI 완료 이벤트 인자로 전달되지만, **DB에 저장되는 최종 판정 결과(`Result`)에는 영향을 미치지 않고 프로그램 판정값을 유지**합니다.
---
[← 이전: 측정 데이터 조회](07_데이터_조회.md) | [다음: 로그 관리 →](09_로그_관리.md)
[← 이전: 측정 데이터 조회](06_데이터_조회.md) | [다음: 로그 관리 →](08_로그_관리.md)

219
leak_test_project/Manual/08_로그_관리.md

@ -0,0 +1,219 @@
# 8. 로그 및 데이터 관리
[← 메뉴얼 목차로 돌아가기](사용자_메뉴얼.md)
---
## 8.1 검사 데이터 로그 (CSV)
### 기본 정보
| 항목 | 내용 |
|------|------|
| **저장 위치** | `[프로그램 폴더]\Logs\yyyy-MM-dd.csv` |
| **파일 형식** | CSV (UTF-8 인코딩) |
| **파일 생성** | 날짜별 자동 생성 (해당 날짜 첫 시험 시) |
| **데이터 추가** | 매 시험 완료 시 자동으로 해당 날짜 파일에 1행 추가 |
### CSV 헤더 및 컬럼
```
IC_SN,PCB_Barcode,Maker,Model,Variant_1,Variant_2,Operator,Production_Date,Line,Lot_No,Jig_No,Channel,Spec_UL,Spec_LL,Leak_Value,Result
```
| 컬럼 | 예시 | 설명 |
|------|------|------|
| `IC_SN` | `260508A00123B01C` | 제품 고유 시리얼 번호 (PK/FK) |
| `PCB_Barcode` | `260508A00123B01C` | PCB 바코드 고유 식별 정보 (UK) |
| `Maker` | `Hyundai` | 메이커 정보 |
| `Model` | `NE1aW` | 모델 정보 |
| `Variant_1` | `PT` | 변형1 정보 |
| `Variant_2` | `SENSOR` | 변형2 정보 |
| `Operator` | `op001` | 작업자 ID |
| `Production_Date` | `2026-05-08 10:30:15` | 검사 수행 일시 |
| `Line` | `L1` | 작업 라인 번호 |
| `Lot_No` | `LOT001` | Lot 번호 |
| `Jig_No` | `JIG01` | 지그 번호 |
| `Channel` | `LEFT` | 검사 채널 (LEFT 또는 RIGHT) |
| `Spec_UL` | `1.00` | 규격 상한값 |
| `Spec_LL` | `-1.00` | 규격 하한값 |
| `Leak_Value` | `0.5234` | 측정된 릭 측정값 (소수점 4자리) |
| `Result` | `OK` | 최종 판정 결과 (OK 또는 NG) |
### CSV 예시
```csv
IC_SN,PCB_Barcode,Maker,Model,Variant_1,Variant_2,Operator,Production_Date,Line,Lot_No,Jig_No,Channel,Spec_UL,Spec_LL,Leak_Value,Result
260508A00123B01C,260508A00123B01C,Hyundai,NE1aW,PT,SENSOR,op001,2026-05-08 10:30:15,L1,LOT001,JIG01,LEFT,1.00,-1.00,0.5234,OK
260508A00456D02E,260508A00456D02E,Hyundai,NE1aW,PT,SENSOR,op002,2026-05-08 10:30:45,L1,LOT001,JIG01,RIGHT,1.00,-1.00,1.2000,NG
```
> **참고**: 타임아웃 발생 시에도 측정값 `0.0000`, 판정 `NG`로 로그에 기록됩니다.
> **참고**: 프로그램은 구형 8개 컬럼 포맷(`Date,Time,ID,Channel,SpecUL,SpecLL,Value,Judgment`)의 로그 파일에 대해서도 컬럼 크기를 기준으로 자동 감지 및 파싱(하위 호환성)을 수행합니다.
> **참고**: CSV 값에 쉼표, 따옴표, 줄바꿈이 포함된 경우 자동으로 이스케이프 처리됩니다.
---
## 8.2 시스템 로그
### 기본 정보
| 항목 | 내용 |
|------|------|
| **저장 위치** | `[프로그램 폴더]\Logs\yyyy-MM-dd_system.log` |
| **파일 형식** | 텍스트 파일 (UTF-8 인코딩) |
| **내용** | 통신 오류, 시스템 이벤트 등 |
### 로그 형식
```
[HH:mm:ss.fff] [TAG] 메시지 내용
```
### 예시
```
[10:30:00.123] [SYSTEM] 프로그램 시작
[10:30:01.456] [COMM] Sentinel C28 포트 연결 성공 (COM1)
[10:35:15.789] [ERROR] Sentinel C28 통신 타임아웃
```
---
## 8.3 설정 파일
### config.xml
| 항목 | 내용 |
|------|------|
| **위치** | 프로그램 실행 폴더 |
| **생성** | 최초 실행 시 기본값으로 자동 생성 |
| **수정** | Comm. Settings 또는 Parameters에서 저장 시 갱신 |
```xml
<AppConfig>
<SensorPort>COM1</SensorPort>
<SensorBaudRate>9600</SensorBaudRate>
<Board4251Port>COM3</Board4251Port>
<Board4251BaudRate>115200</Board4251BaudRate>
<Board4251Timeout>5000</Board4251Timeout>
<SpecUL>1.00</SpecUL>
<SpecLL>-1.00</SpecLL>
</AppConfig>
```
| 설정 키 | 기본값 | 설정 위치 | 설명 |
|---------|--------|----------|------|
| `SensorPort` | COM1 | Comm. Settings | C28 센서 COM 포트 |
| `SensorBaudRate` | 9600 | Comm. Settings | C28 센서 통신 속도 |
| `Board4251Port` | COM3 | Comm. Settings | 4251 보드 COM 포트 |
| `Board4251BaudRate` | 115200 | Comm. Settings | 4251 보드 통신 속도 |
| `Board4251Timeout` | 5000 | (코드 내 고정) | 4251 보드 응답 대기 시간(ms) |
| `SpecUL` | 1.00 | Parameters | 누설 상한 규격값 |
| `SpecLL` | -1.00 | Parameters | 누설 하한 규격값 |
### Database.ini
| 항목 | 내용 |
|------|------|
| **위치** | 프로그램 실행 폴더 |
| **생성** | 배포 시 기본 포함 및 파일 누락 상태로 기동 시 기본 양식이 자동 생성됨 |
| **수정** | 메모장 등의 텍스트 에디터를 통해 연결 주소 및 프로시저 정보 직접 수정 |
```ini
; [데이터베이스 접속 상세 설정]
Db_Server=192.168.0.20
Db_Name=NE1aW_PT_SENSOR
Db_User=sa
Db_Password=sa1234
[Login]
Procedure=dbo.CheckOperator
ProcessName=EOL
OfflinePreview=false
```
| 설정 키 | 예시 | 설명 |
|---------|--------|------|
| `Db_Server` | 192.168.0.20 | 데이터베이스 서버 IP 주소 또는 호스트명 |
| `Db_Name` | NE1aW_PT_SENSOR | 대상 데이터베이스(DB) 이름 |
| `Db_User` | sa | 데이터베이스 접속 계정 ID |
| `Db_Password` | sa1234 | 데이터베이스 접속 계정 비밀번호 |
| `Procedure` | dbo.CheckOperator | 로그인 계정 검증용 저장 프로시저명 |
| `ProcessName` | EOL | 검증 공정명 |
| `OfflinePreview` | false | true 시 DB 연결 없이 로그인 즉시 성공 (오프라인 모드) |
### DioConfig.ini
| 항목 | 내용 |
|------|------|
| **위치** | `[프로그램 폴더]\Settings\DioConfig.ini` |
| **생성** | 최초 실행 시 자동 생성 |
| **수정** | 일반적으로 수정 불필요 (하드웨어 변경 시에만) |
```ini
[DIO]
CompanyName=ADLink
BoardCnt=1
[BOARD_1]
BoardType=PCI_7432
BoardNumber=0
[BOARD_1_IN]
0=LEFT_START,1,좌측 시험 시작 신호
1=RIGHT_START,1,우측 시험 시작 신호
[BOARD_1_OUT]
0=LEFT_OK,1,좌측 합격 출력
1=RIGHT_OK,1,우측 합격 출력
2=PC_ON,1,PC 가동 신호
3=LEFT_NG,1,좌측 불합격 출력
4=RIGHT_NG,1,우측 불합격 출력
```
**INI 포맷 설명**: `비트인덱스=신호명,활성여부,설명`
---
## 8.4 파일 관리 주의사항
| 주의사항 | 설명 |
|---------|------|
| Logs 폴더 삭제 금지 | 검사 기록이 영구 삭제됩니다 |
| CSV 파일 직접 편집 | Excel 등으로 편집 시 인코딩이 변경될 수 있으므로 주의 |
| config.xml 직접 편집 | 잘못된 값 입력 시 프로그램 오작동 가능. 프로그램 내 설정 화면 사용 권장 |
| 디스크 용량 | 일일 수백 건 검사 기준, 연간 약 50~100MB 정도의 로그가 생성됩니다 |
---
## 8.5 데이터베이스 데이터 관리 (dbo.EOL)
매 시험 완료 시 검사 결과 및 로그인 정보가 데이터베이스에 실시간으로 저장(MERGE/UPSERT)됩니다.
### 데이터베이스 테이블 및 컬럼 매핑
* **대상 테이블**: `[dbo].[EOL]`
* **매핑 상세**:
| DB 컬럼명 | 데이터 타입 | 매핑되는 C# 데이터 | 비고 |
| :--- | :--- | :--- | :--- |
| **`IC_SN`** | `NVARCHAR(10)` | 제품의 최종 조합 ID (`inspectData.ProductId`) | **PK / FK** |
| **`PCB_Barcode`** | `NVARCHAR(10)` | 고유 식별용 바코드 정보 (`inspectData.ProductId` 매핑) | **UK** |
| `Maker` | `NVARCHAR(10)` | 로그인 제조사 정보 (`App.LoginResult.Maker`) | |
| `Model` | `NVARCHAR(10)` | 로그인 모델 정보 (`App.LoginResult.Model`) | |
| `Variant_1` | `NVARCHAR(10)` | 로그인 변형1 정보 (`App.LoginResult.Variant1`) | |
| `Variant_2` | `NVARCHAR(10)` | 로그인 변형2 정보 (`App.LoginResult.Variant2`) | |
| `Operator` | `NVARCHAR(10)` | 로그인 검사자 ID (`App.LoginResult.Operator`) | |
| `Production_Date` | `DATETIME2(0)` | 검사 수행 일시 (`DateTime.Now`) | |
| `Line` | `NVARCHAR(5)` | 로그인 작업 라인 (`App.LoginResult.LineNo`) | |
| `Lot_No` | `NVARCHAR(8)` | 로그인 Lot 번호 (`App.LoginResult.LotNo`) | |
| `Jig_No` | `NVARCHAR(10)` | 로그인 지그 번호 (`App.LoginResult.JigNo`) | |
| `Channel` | `NVARCHAR(5)` | 검사 채널 (`LEFT` / `RIGHT`) | |
| `Spec_UL` | `DECIMAL(3,2)` | 규격 상한값 (소수점 2자리) | |
| `Spec_LL` | `DECIMAL(3,2)` | 규격 하한값 (소수점 2자리) | |
| `Leak_Value` | `DECIMAL(5,4)` | 측정된 릭 값 (소수점 4자리) | |
| `Result` | `NVARCHAR(5)` | 최종 판정 결과 (`OK` 또는 `NG`) | |
---
[← 이전: 시험 프로세스](07_시험_프로세스.md) | [다음: 오류 대응 →](09_오류_대응.md)

150
leak_test_project/Manual/09_로그_관리.md

@ -1,150 +0,0 @@
# 8. 로그 및 데이터 관리
[← 메뉴얼 목차로 돌아가기](사용자_메뉴얼.md)
---
## 8.1 검사 데이터 로그 (CSV)
### 기본 정보
| 항목 | 내용 |
|------|------|
| **저장 위치** | `[프로그램 폴더]\Logs\yyyy-MM-dd.csv` |
| **파일 형식** | CSV (UTF-8 인코딩) |
| **파일 생성** | 날짜별 자동 생성 (해당 날짜 첫 시험 시) |
| **데이터 추가** | 매 시험 완료 시 자동으로 해당 날짜 파일에 1행 추가 |
### CSV 헤더 및 컬럼
```
Date,Time,Channel,ID,Value,Judgment,SpecUL,SpecLL,Retest
```
| 컬럼 | 예시 | 설명 |
|------|------|------|
| `Date` | `2026-05-08` | 검사 일자 |
| `Time` | `10:30:15` | 검사 시각 |
| `Channel` | `LEFT` | 검사 채널 (LEFT 또는 RIGHT) |
| `ID` | `260508A00123B01C` | 제품 ID |
| `Value` | `0.5234` | 측정값 (sccm, 소수점 4자리) |
| `Judgment` | `OK` | 판정 결과 (OK 또는 NG) |
| `SpecUL` | `1.00` | 검사 시점의 상한 규격값 |
| `SpecLL` | `-1.00` | 검사 시점의 하한 규격값 |
| `Retest` | `N` | 재시험 여부 (Y 또는 N) |
### CSV 예시
```csv
Date,Time,Channel,ID,Value,Judgment,SpecUL,SpecLL,Retest
2026-05-08,10:30:15,LEFT,260508A00123B01C,0.5234,OK,1.00,-1.00,N
2026-05-08,10:30:45,RIGHT,260508A00456D02E,1.2000,NG,1.00,-1.00,N
2026-05-08,10:31:20,LEFT,260508A00789F03G,0.0000,NG,1.00,-1.00,N
```
> **참고**: 타임아웃 발생 시에도 측정값 `0.0000`, 판정 `NG`로 로그에 기록됩니다.
> **참고**: CSV 값에 쉼표, 따옴표, 줄바꿈이 포함된 경우 자동으로 이스케이프 처리됩니다.
---
## 8.2 시스템 로그
### 기본 정보
| 항목 | 내용 |
|------|------|
| **저장 위치** | `[프로그램 폴더]\Logs\yyyy-MM-dd_system.log` |
| **파일 형식** | 텍스트 파일 (UTF-8 인코딩) |
| **내용** | 통신 오류, 시스템 이벤트 등 |
### 로그 형식
```
[HH:mm:ss.fff] [TAG] 메시지 내용
```
### 예시
```
[10:30:00.123] [SYSTEM] 프로그램 시작
[10:30:01.456] [COMM] Sentinel C28 포트 연결 성공 (COM1)
[10:35:15.789] [ERROR] Sentinel C28 통신 타임아웃
```
---
## 8.3 설정 파일
### config.xml
| 항목 | 내용 |
|------|------|
| **위치** | 프로그램 실행 폴더 |
| **생성** | 최초 실행 시 기본값으로 자동 생성 |
| **수정** | Comm. Settings 또는 Parameters에서 저장 시 갱신 |
```xml
<AppConfig>
<SensorPort>COM1</SensorPort>
<SensorBaudRate>9600</SensorBaudRate>
<Board4251Port>COM3</Board4251Port>
<Board4251BaudRate>115200</Board4251BaudRate>
<Board4251Timeout>5000</Board4251Timeout>
<SpecUL>1.00</SpecUL>
<SpecLL>-1.00</SpecLL>
</AppConfig>
```
| 설정 키 | 기본값 | 설정 위치 | 설명 |
|---------|--------|----------|------|
| `SensorPort` | COM1 | Comm. Settings | C28 센서 COM 포트 |
| `SensorBaudRate` | 9600 | Comm. Settings | C28 센서 통신 속도 |
| `Board4251Port` | COM3 | Comm. Settings | 4251 보드 COM 포트 |
| `Board4251BaudRate` | 115200 | Comm. Settings | 4251 보드 통신 속도 |
| `Board4251Timeout` | 5000 | (코드 내 고정) | 4251 보드 응답 대기 시간(ms) |
| `SpecUL` | 1.00 | Parameters | 누설 상한 규격값 |
| `SpecLL` | -1.00 | Parameters | 누설 하한 규격값 |
### DioConfig.ini
| 항목 | 내용 |
|------|------|
| **위치** | `[프로그램 폴더]\Settings\DioConfig.ini` |
| **생성** | 최초 실행 시 자동 생성 |
| **수정** | 일반적으로 수정 불필요 (하드웨어 변경 시에만) |
```ini
[DIO]
CompanyName=ADLink
BoardCnt=1
[BOARD_1]
BoardType=PCI_7432
BoardNumber=0
[BOARD_1_IN]
0=LEFT_START,1,좌측 시험 시작 신호
1=RIGHT_START,1,우측 시험 시작 신호
[BOARD_1_OUT]
0=LEFT_OK,1,좌측 합격 출력
1=RIGHT_OK,1,우측 합격 출력
2=PC_ON,1,PC 가동 신호
3=LEFT_NG,1,좌측 불합격 출력
4=RIGHT_NG,1,우측 불합격 출력
```
**INI 포맷 설명**: `비트인덱스=신호명,활성여부,설명`
---
## 8.4 파일 관리 주의사항
| 주의사항 | 설명 |
|---------|------|
| Logs 폴더 삭제 금지 | 검사 기록이 영구 삭제됩니다 |
| CSV 파일 직접 편집 | Excel 등으로 편집 시 인코딩이 변경될 수 있으므로 주의 |
| config.xml 직접 편집 | 잘못된 값 입력 시 프로그램 오작동 가능. 프로그램 내 설정 화면 사용 권장 |
| 디스크 용량 | 일일 수백 건 검사 기준, 연간 약 50~100MB 정도의 로그가 생성됩니다 |
---
[← 이전: 시험 프로세스](08_시험_프로세스.md) | [다음: 오류 대응 →](10_오류_대응.md)

21
leak_test_project/Manual/10_오류_대응.md → leak_test_project/Manual/09_오류_대응.md

@ -15,8 +15,6 @@
> **참고**: 통신 설정을 저장하면 기존 연결을 모두 해제하고 새 설정으로 재연결을 시도합니다. 포트 변경 후 재시작 없이 바로 적용됩니다.
---
## 9.2 시험 중 오류
시험 프로세스 진행 중 발생하는 오류입니다. 메인 화면의 **오류 메시지** 영역에 표시됩니다.
@ -24,11 +22,16 @@
| 오류 메시지 | 원인 | 조치 방법 |
|------------|------|----------|
| `Sentinel C28 통신 타임아웃` | 30초 내 C28 센서 응답 없음 | ① 센서 전원 ON 확인 ② 시리얼 케이블 연결 확인 ③ BaudRate 설정 확인 (기본: 9600) |
| `불량 제품 투입 (이전 결과: X)` | ZMDI 센서의 PrevResult가 "F"가 아님 (이전 검사 불합격 이력) | 해당 제품을 라인에서 제거하고 정상 제품 투입 |
| `통신 실패 (4251 보드 CHx 상태 타임아웃)` | 4251 보드가 응답 시간(15초) 내에 상태 응답을 회신하지 않음 | ① 4251 보드 전원 공급 및 케이블 연결선 확인 ② 통신 노이즈 방지 조치 확인 |
| `통신 실패 (4251 보드 CHx 상태 이상 또는 Fail)` | 4251 보드로부터 정상 응답을 받지 못했거나 오류 코드가 반환됨 | ① 하드웨어 오결선 여부 점검 ② 채널 센서 구동 전원 확인 |
| `통신 실패 (4251 보드 CHx ID 대기 타임아웃)` | 4251 보드를 통해 ZMDI 센서 ID를 15초 내에 판독하지 못함 | ① 센서가 지그(Jig)에 올바르게 안착되었는지 물리적 확인 ② 접촉 단자 이물질 청소 |
| `SPEC 불일치 - 프로그램: OK, 센서: R` | 프로그램 SPEC과 센서 자체 SPEC의 판정이 다름 | ① 프로그램의 SPEC UL/LL 확인 (Parameters) ② C28 센서의 SPEC 설정 확인 ③ 양쪽 설정값 동기화 |
| `LEAK 센서 데이터 오류` | C28에서 수신된 데이터 파싱 실패 | ① 센서 통신 상태 확인 ② 케이블 접촉 불량 확인 ③ BaudRate 불일치 확인 |
| `DB 저장 실패` | 중앙 MSSQL DB 서버에 검사 데이터를 저장하지 못함 | ① PC 네트워크 케이블 및 통신 연결 확인 ② Database.ini 파일의 접속 정보(IP/비밀번호 등) 확인 ③ 로컬 일일 CSV 로그 파일(`Logs/yyyy-MM-dd.csv`)에 백업이 잘 저장되었는지 확인 |
---
### SPEC 교차 검증 경고 팝업
## 9.3 SPEC 교차 검증 경고 팝업
프로그램 판정과 센서 판정이 불일치하면 팝업 경고가 표시됩니다:
@ -42,18 +45,18 @@
---
## 9.3 DIO 보드 오류
## 9.4 DIO 보드 오류
| 상황 | 프로그램 동작 | 영향 |
|------|-------------|------|
| PCI-7432 보드 미인식 | **시뮬레이션 모드**로 자동 전환 | DIO 입출력이 동작하지 않음 (실제 입출력 포트 제어 불가) |
| 보드 런타임 오류 | 좌/우 채널 오류 메시지에 `[DIO Board Error]` 표시 | 오류 내용에 따라 조치 |
> **시뮬레이션 모드**: ADLINK 드라이버가 설되지 않은 개발 환경에서도 프로그램의 나머지 기능(통신 설정, 데이터 조회 등)을 테스트할 수 있도록 자동 전환됩니다.
> **시뮬레이션 모드**: ADLINK 드라이버가 설되지 않은 개발 환경에서도 프로그램의 나머지 기능(통신 설정, 데이터 조회 등)을 테스트할 수 있도록 자동 전환됩니다.
---
## 9.4 일반적인 문제 해결
## 9.5 일반적인 문제 해결
| 증상 | 가능한 원인 | 해결 방법 |
|------|-----------|----------|
@ -66,7 +69,7 @@
---
## 9.5 프로그램 종료
## 9.6 프로그램 종료
### 종료 방법
@ -103,4 +106,4 @@
---
[← 이전: 로그 관리](09_로그_관리.md) | [메뉴얼 목차로 돌아가기](사용자_메뉴얼.md)
[← 이전: 로그 관리](08_로그_관리.md) | [메뉴얼 목차로 돌아가기](사용자_메뉴얼.md)

BIN
leak_test_project/Manual/Pasted image 20260624104139.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

BIN
leak_test_project/Manual/Pasted image 20260624104208.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

BIN
leak_test_project/Manual/Pasted image 20260624104256.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
leak_test_project/Manual/Pasted image 20260624104353.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
leak_test_project/Manual/Pasted image 20260624104445.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

BIN
leak_test_project/Manual/Pasted image 20260624104506.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
leak_test_project/Manual/Pasted image 20260624104531.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
leak_test_project/Manual/Pasted image 20260624104558.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

16
leak_test_project/Manual/사용자_메뉴얼.md

@ -11,14 +11,14 @@
| 번호 | 문서 | 설명 |
|------|------|------|
| 1 | [시스템 개요 및 요구사항](01_시스템_개요.md) | 프로그램 소개, 주요 기능, 하드웨어/소프트웨어 요구사항, DIO 신호 매핑 |
| 2 | [초기 설정 가이드](02_초기_설정.md) | 설치 전 준비, 프로그램 실행, 최초 설정 순서 (통신→시험→I/O 확인) |
| 3 | [메인 화면 (Home View)](03_메인_화면.md) | 타이틀 바, 좌/우 채널 모니터링 영역, 하단 메뉴 바 상세 설명 |
| 2 | [초기 설정 가이드](02_초기_설정.md) | 설치 전 준비, 로그인 및 프로그램 실행, 최초 설정 순서 (통신→시험→동작 테스트) |
| 3 | [메인 화면 (Home View)](03_메인_화면.md) | 타이틀 바(작업자/모델/라인 정보 배지 및 로그아웃), 좌/우 채널 모니터링 영역, 하단 메뉴 바 상세 설명 |
| 4 | [통신 설정 (Comm. Settings)](04_통신_설정.md) | 4251 보드 및 Sentinel C28 센서의 COM 포트/BaudRate 설정 방법 |
| 5 | [시험 설정 (Parameters)](05_시험_설정.md) | SPEC UL/LL 판정 규격값 설정 방법 |
| 6 | [측정 데이터 조회 (Data View)](07_데이터_조회.md) | 검사 기록 검색, 필터링, CSV 내보내기 방법 |
| 7 | [자동 시험 프로세스](08_시험_프로세스.md) | 7단계 자동 시험 흐름, 순차 실행, ID 수동 테스트 |
| 8 | [로그 및 데이터 관리](09_로그_관리.md) | 검사 데이터 로그, 시스템 로그, 설정 파일 구조 |
| 9 | [오류 대응 및 프로그램 종료](10_오류_대응.md) | 통신 오류, 시험 중 오류, DIO 보드 오류 대응, 종료 절차 |
| 6 | [측정 데이터 조회 (Data View)](06_데이터_조회.md) | 검사 기록 검색, 필터링, CSV 내보내기 방법 |
| 7 | [자동 시험 프로세스](07_시험_프로세스.md) | 7단계 자동 시험 흐름, 순차 실행, ID 수동 테스트 |
| 8 | [로그 및 데이터 관리](08_로그_관리.md) | 검사 데이터 로그, 시스템 로그, 설정 파일 구조 |
| 9 | [오류 대응 및 프로그램 종료](09_오류_대응.md) | 통신 오류, 시험 중 오류, DIO 보드 오류 대응, 종료 절차 |
---
@ -27,10 +27,10 @@
처음 사용하는 경우 아래 순서를 따르세요:
1. **하드웨어 연결 확인** → [시스템 개요 및 요구사항](01_시스템_개요.md) 참조
2. **프로그램 실행**`leak_test_project.exe` 더블 클릭
2. **프로그램 실행 및 로그인**`leak_test_project.exe` 더블 클릭 후 작업 정보 입력 및 로그인 완료 (데모 계정은 ID/PW `test`/`test` 입력, 오프라인 환경일 경우 `Database.ini`에서 `OfflinePreview=true` 설정 확인)
3. **통신 설정** → 하단 메뉴 `Comm. Settings` → COM 포트 선택 → `설정 저장`
4. **시험 설정** → 하단 메뉴 `Parameters` → SPEC UL/LL 입력 → `설정 저장`
5. **시험 시작** → DIO 시작 신호 또는 `ID Test` 버튼으로 동작 확인
5. **시험 시작** → DIO 시작 신호 또는 `ID Test (L/R)` 버튼으로 동작 확인
> **참고**: 각 단계의 상세 내용은 위 문서 링크를 참조하세요.

21
leak_test_project/Models/ProjectModels.cs

@ -16,7 +16,10 @@ namespace leak_test_project.Models
public double SpecUL { get; set; } = 1.00;
public double SpecLL { get; set; } = -1.00;
/// <summary>MSSQL 데이터베이스 연결 문자열</summary>
/// <summary>
/// MSSQL 데이터베이스 연결 문자열 (더 이상 사용되지 않음. 대신 Database.ini의 설정을 읽어 접속합니다.)
/// </summary>
[Obsolete("Use Database.ini ConnectionString instead.")]
public string DbConnectionString { get; set; } = "Server=192.168.0.20;Database=NE1aW_PT_SENSOR;User Id=sa;Password=sa1234;";
}
@ -91,6 +94,19 @@ namespace leak_test_project.Models
public string SpecLL { get; set; }
public string MeasuredValue { get; set; }
public string Judgment { get; set; }
// 신규 DB 테이블 EOL 컬럼 매핑 및 정합성 보장을 위한 추가 프로퍼티
public string IcSn { get; set; } = string.Empty;
public string PcbBarcode { get; set; } = string.Empty;
public string Maker { get; set; } = string.Empty;
public string Model { get; set; } = string.Empty;
public string Variant1 { get; set; } = string.Empty;
public string Variant2 { get; set; } = string.Empty;
public string Operator { get; set; } = string.Empty;
public DateTime? ProductionDate { get; set; }
public string Line { get; set; } = string.Empty;
public string LotNo { get; set; } = string.Empty;
public string JigNo { get; set; } = string.Empty;
}
public class ParsedData
@ -154,7 +170,6 @@ namespace leak_test_project.Models
/// <summary>최종 조합 ID (년+월+일+시리얼+라인+항목)</summary>
public string ID { get; set; } = "";
/// <summary>이전 검사 결과. F=미시험/불합격, P=합격. 불량 제품 필터링에 사용.</summary>
public string PrevResult { get; set; } = "";
}
}

61
leak_test_project/Services/Board4251.cs

@ -290,7 +290,7 @@ namespace leak_test_project.Services
public bool Connect() => _service.Connect();
public void Disconnect() => _service.Disconnect();
public SensorIdData ReadSensor()
public async Task<SensorIdData> ReadSensorAsync()
{
try
{
@ -298,23 +298,33 @@ namespace leak_test_project.Services
// 0. 초기화 (x00o 전송)
ProgressMessage?.Invoke(this, "4251 보드 초기화 중...");
// x00o 명령을 보내고 응답 내용에 상관없이 다음 단계 진행 (최대 5초 대기)
Task.Run(() => _service.SendCommandAsync("x00o\r\n")).Wait(1000);
Task.Delay(350).Wait(); // 보드 안정화 대기
// x00o 명령을 보내고 응답 내용에 상관없이 다음 단계 진행
_ = _service.SendCommandAsync("x00o\r\n");
await Task.Delay(350); // 보드 안정화 대기
// 1. 보드 상태 확인 (Fail인 경우 중단)
ProgressMessage?.Invoke(this, "4251 보드 데이터 읽는 중...");
int extendedTimeout = 15000;
string statusCmd = $"x00c_00{channel}101:owt28006727ea97c7801";
var statusTask = Task.Run(() => _service.CheckStatusAsync(channel));
if (!statusTask.Wait(extendedTimeout))
bool statusResult = false;
using (var cts = new CancellationTokenSource(extendedTimeout))
{
string buf = _service.GetLastBuffer();
string displayBuf = string.IsNullOrEmpty(buf) ? "수신된 데이터 없음" : buf;
ErrorMessage?.Invoke(this, $"통신 실패 (4251 보드 CH{channel} 상태 타임아웃)\r\n[송신값]: {statusCmd}\r\n[수신값]: {displayBuf}");
return null;
var statusTask = _service.CheckStatusAsync(channel);
var delayTask = Task.Delay(extendedTimeout, cts.Token);
var completedTask = await Task.WhenAny(statusTask, delayTask);
if (completedTask == delayTask)
{
string buf = _service.GetLastBuffer();
string displayBuf = string.IsNullOrEmpty(buf) ? "수신된 데이터 없음" : buf;
ErrorMessage?.Invoke(this, $"통신 실패 (4251 보드 CH{channel} 상태 타임아웃)\r\n[송신값]: {statusCmd}\r\n[수신값]: {displayBuf}");
return null;
}
cts.Cancel();
statusResult = await statusTask;
}
if (!statusTask.Result)
if (!statusResult)
{
string buf = _service.GetLastBuffer();
string displayBuf = string.IsNullOrEmpty(buf) ? "수신된 데이터 없음" : buf;
@ -329,20 +339,26 @@ namespace leak_test_project.Services
for (int attempt = 1; attempt <= maxAttempts; attempt++)
{
var idTask = Task.Run(() => _service.ReadIdAsync(channel));
if (!idTask.Wait(extendedTimeout))
using (var cts = new CancellationTokenSource(extendedTimeout))
{
if (attempt == maxAttempts)
var idTask = _service.ReadIdAsync(channel);
var delayTask = Task.Delay(extendedTimeout, cts.Token);
var completedTask = await Task.WhenAny(idTask, delayTask);
if (completedTask == delayTask)
{
string buf = _service.GetLastBuffer();
string displayBuf = string.IsNullOrEmpty(buf) ? "수신된 데이터 없음" : buf;
ErrorMessage?.Invoke(this, $"통신 실패 (4251 보드 CH{channel} ID 대기 타임아웃)\r\n[송신값]: {idCmd}\r\n[수신값]: {displayBuf}");
return null;
if (attempt == maxAttempts)
{
string buf = _service.GetLastBuffer();
string displayBuf = string.IsNullOrEmpty(buf) ? "수신된 데이터 없음" : buf;
ErrorMessage?.Invoke(this, $"통신 실패 (4251 보드 CH{channel} ID 대기 타임아웃)\r\n[송신값]: {idCmd}\r\n[수신값]: {displayBuf}");
return null;
}
continue;
}
continue;
cts.Cancel();
rawId = await idTask;
}
rawId = idTask.Result;
if (string.IsNullOrEmpty(rawId))
{
if (attempt == maxAttempts)
@ -361,7 +377,7 @@ namespace leak_test_project.Services
if (attempt < maxAttempts)
{
ProgressMessage?.Invoke(this, $"ID 읽기 재시도 중... ({attempt}/{maxAttempts})");
Task.Delay(350).Wait(); // 재시도 전 약간의 딜레이
await Task.Delay(350); // 재시도 전 약간의 딜레이
continue;
}
else
@ -382,8 +398,7 @@ namespace leak_test_project.Services
LowID = rawId,
ID = rawId, // 16자리 전체를 ID로 사용
Serial = "", // 시리얼 번호는 현재 존재하지 않으므로 강제로 파싱하지 않음
Item = "N/A",
PrevResult = "F" // '불량제품 투입' 필터를 통과하기 위한 강제 초기화
Item = "N/A"
};
ProgressMessage?.Invoke(this, "ID 읽기 성공");

122
leak_test_project/Services/DatabaseSettings.cs

@ -0,0 +1,122 @@
using System;
using System.IO;
namespace Housing.Services
{
public class DatabaseSettings
{
public string Db_Server { get; set; } = string.Empty;
public string Db_Name { get; set; } = string.Empty;
public string Db_User { get; set; } = string.Empty;
public string Db_Password { get; set; } = string.Empty;
private string _rawConnectionString = string.Empty;
public string ConnectionString
{
get
{
// 개별 설정이 모두 지정되어 있는 경우 조합해서 사용
if (!string.IsNullOrWhiteSpace(Db_Server) &&
!string.IsNullOrWhiteSpace(Db_Name) &&
!string.IsNullOrWhiteSpace(Db_User) &&
!string.IsNullOrWhiteSpace(Db_Password))
{
return $"Server={Db_Server};Database={Db_Name};User Id={Db_User};Password={Db_Password};";
}
// 없으면 레거시 ConnectionString 필드를 그대로 사용
return _rawConnectionString;
}
set
{
_rawConnectionString = value;
}
}
private static readonly object _dbLock = new object();
private static DatabaseSettings _cachedSettings;
private static DateTime _lastWriteTime;
private static string _cachedPath;
public static DatabaseSettings Load(string path)
{
lock (_dbLock)
{
try
{
if (File.Exists(path))
{
var currentWriteTime = File.GetLastWriteTime(path);
if (_cachedSettings != null && _cachedPath == path && _lastWriteTime == currentWriteTime)
{
return _cachedSettings;
}
var settings = new DatabaseSettings();
var lines = File.ReadAllLines(path);
foreach (var line in lines)
{
var trimmed = line.Trim();
// 주석 행 스킵
if (trimmed.StartsWith(";") || trimmed.StartsWith("#"))
{
continue;
}
if (trimmed.StartsWith("ConnectionString", StringComparison.OrdinalIgnoreCase))
{
var parts = trimmed.Split(new[] { '=' }, 2);
if (parts.Length == 2)
{
settings.ConnectionString = parts[1].Trim();
}
}
else if (trimmed.StartsWith("Db_Server", StringComparison.OrdinalIgnoreCase))
{
var parts = trimmed.Split(new[] { '=' }, 2);
if (parts.Length == 2)
{
settings.Db_Server = parts[1].Trim();
}
}
else if (trimmed.StartsWith("Db_Name", StringComparison.OrdinalIgnoreCase))
{
var parts = trimmed.Split(new[] { '=' }, 2);
if (parts.Length == 2)
{
settings.Db_Name = parts[1].Trim();
}
}
else if (trimmed.StartsWith("Db_User", StringComparison.OrdinalIgnoreCase))
{
var parts = trimmed.Split(new[] { '=' }, 2);
if (parts.Length == 2)
{
settings.Db_User = parts[1].Trim();
}
}
else if (trimmed.StartsWith("Db_Password", StringComparison.OrdinalIgnoreCase))
{
var parts = trimmed.Split(new[] { '=' }, 2);
if (parts.Length == 2)
{
settings.Db_Password = parts[1].Trim();
}
}
}
_cachedSettings = settings;
_lastWriteTime = currentWriteTime;
_cachedPath = path;
return settings;
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[DatabaseSettings Load Error] {ex.Message}");
}
return _cachedSettings ?? new DatabaseSettings();
}
}
}
}

82
leak_test_project/Services/EolDatabaseService.cs

@ -21,46 +21,84 @@ namespace leak_test_project.Services
/// EOL 검사 결과를 데이터베이스에 저장합니다. (동일 ID 존재 시 업데이트, 미존재 시 삽입)
/// </summary>
/// <param name="inspectData">검사 결과 데이터</param>
public async Task SaveEolDataAsync(InspectData inspectData)
/// <returns>저장 성공 여부 (성공: true, 실패: false)</returns>
public async Task<bool> SaveEolDataAsync(InspectData inspectData, Housing.Login.StartupLoginWindowResult loginInfo)
{
if (inspectData == null) return;
if (inspectData == null) return false;
// MSSQL MERGE 문을 사용한 UPSERT 처리
// MSSQL MERGE 문을 사용한 UPSERT 처리 (신규 dbo.EOL 스키마 적용)
const string query = @"
MERGE INTO [dbo].[EOL] AS Target
USING (SELECT @ID AS ID) AS Source
ON (Target.ID = Source.ID)
USING (SELECT @IC_SN AS IC_SN) AS Source
ON (Target.IC_SN = Source.IC_SN)
WHEN MATCHED THEN
UPDATE SET
Target.PCB_Barcode = @PCB_Barcode,
Target.Maker = @Maker,
Target.Model = @Model,
Target.Variant_1 = @Variant_1,
Target.Variant_2 = @Variant_2,
Target.Operator = @Operator,
Target.Production_Date = @Production_Date,
Target.Line = @Line,
Target.Lot_No = @Lot_No,
Target.Jig_No = @Jig_No,
Target.Channel = @Channel,
Target.SpecUL = @SpecUL,
Target.SpecLL = @SpecLL,
Target.LeakValue = @LeakValue,
Target.Result = @Result,
Target.InspectDate = @InspectDate,
Target.InspectTime = @InspectTime
Target.Spec_UL = @Spec_UL,
Target.Spec_LL = @Spec_LL,
Target.Leak_Value = @Leak_Value,
Target.Result = @Result
WHEN NOT MATCHED THEN
INSERT ([ID], [Channel], [SpecUL], [SpecLL], [LeakValue], [Result], [InspectDate], [InspectTime])
VALUES (@ID, @Channel, @SpecUL, @SpecLL, @LeakValue, @Result, @InspectDate, @InspectTime);";
INSERT (IC_SN, PCB_Barcode, Maker, Model, Variant_1, Variant_2, Operator, Production_Date, Line, Lot_No, Jig_No, Channel, Spec_UL, Spec_LL, Leak_Value, Result)
VALUES (@IC_SN, @PCB_Barcode, @Maker, @Model, @Variant_1, @Variant_2, @Operator, @Production_Date, @Line, @Lot_No, @Jig_No, @Channel, @Spec_UL, @Spec_LL, @Leak_Value, @Result);";
try
{
var maker = loginInfo?.Maker ?? string.Empty;
var model = loginInfo?.Model ?? string.Empty;
var variant1 = loginInfo?.Variant1 ?? string.Empty;
var variant2 = loginInfo?.Variant2 ?? string.Empty;
var operatorId = loginInfo?.Operator ?? string.Empty;
var lineNo = loginInfo?.LineNo ?? string.Empty;
var lotNo = loginInfo?.LotNo ?? string.Empty;
var jigNo = loginInfo?.JigNo ?? string.Empty;
using (var conn = new SqlConnection(_connectionString))
{
using (var cmd = new SqlCommand(query, conn))
{
cmd.Parameters.AddWithValue("@ID", string.IsNullOrEmpty(inspectData.ProductId) ? "-" : inspectData.ProductId);
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("@Maker", maker);
cmd.Parameters.AddWithValue("@Model", model);
cmd.Parameters.AddWithValue("@Variant_1", variant1);
cmd.Parameters.AddWithValue("@Variant_2", variant2);
cmd.Parameters.AddWithValue("@Operator", operatorId);
cmd.Parameters.AddWithValue("@Production_Date", inspectData.ProductionDate ?? DateTime.Now);
cmd.Parameters.AddWithValue("@Line", lineNo);
cmd.Parameters.AddWithValue("@Lot_No", lotNo);
cmd.Parameters.AddWithValue("@Jig_No", jigNo);
cmd.Parameters.AddWithValue("@Channel", string.IsNullOrEmpty(inspectData.Channel) ? "N/A" : inspectData.Channel);
cmd.Parameters.AddWithValue("@SpecUL", string.IsNullOrEmpty(inspectData.SpecUL) ? "0.00" : inspectData.SpecUL);
cmd.Parameters.AddWithValue("@SpecLL", string.IsNullOrEmpty(inspectData.SpecLL) ? "0.00" : inspectData.SpecLL);
cmd.Parameters.AddWithValue("@LeakValue", string.IsNullOrEmpty(inspectData.MeasuredValue) ? "0.0000" : inspectData.MeasuredValue);
// Spec_UL, Spec_LL, Leak_Value 형 변환 및 대입
decimal specUL = 0;
if (decimal.TryParse(inspectData.SpecUL, out var parsedUL)) specUL = parsedUL;
cmd.Parameters.AddWithValue("@Spec_UL", specUL);
decimal specLL = 0;
if (decimal.TryParse(inspectData.SpecLL, out var parsedLL)) specLL = parsedLL;
cmd.Parameters.AddWithValue("@Spec_LL", specLL);
decimal leakValue = 0;
if (decimal.TryParse(inspectData.MeasuredValue, out var parsedValue)) leakValue = parsedValue;
cmd.Parameters.AddWithValue("@Leak_Value", leakValue);
cmd.Parameters.AddWithValue("@Result", string.IsNullOrEmpty(inspectData.Judgment) ? "NG" : inspectData.Judgment);
cmd.Parameters.AddWithValue("@InspectDate", string.IsNullOrEmpty(inspectData.InspectDate) ? DateTime.Now.ToString("yyyy-MM-dd") : inspectData.InspectDate);
cmd.Parameters.AddWithValue("@InspectTime", string.IsNullOrEmpty(inspectData.InspectTime) ? DateTime.Now.ToString("HH:mm:ss") : inspectData.InspectTime);
await conn.OpenAsync();
await cmd.ExecuteNonQueryAsync();
Console.WriteLine($"[DB SUCCESS] EOL Data saved/updated for ID: {inspectData.ProductId}");
Console.WriteLine($"[DB SUCCESS] EOL Data saved/updated for IC_SN: {inspectData.ProductId}");
return true;
}
}
}
@ -68,6 +106,7 @@ namespace leak_test_project.Services
{
// 실시간 검사 공정 루프 흐름이 DB 커넥션 예외로 중단되지 않도록 예외 격리 처리
Console.WriteLine($"[DB ERROR] Failed to upsert EOL Data: {ex.Message}");
return false;
}
}
@ -89,8 +128,9 @@ namespace leak_test_project.Services
return true;
}
}
catch
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[DB Connection Check Error] {ex.Message}");
return false;
}
}

2
leak_test_project/Services/IIdSensorService.cs

@ -11,6 +11,6 @@ namespace leak_test_project.Services
bool Connect();
void Disconnect();
SensorIdData ReadSensor();
System.Threading.Tasks.Task<SensorIdData> ReadSensorAsync();
}
}

99
leak_test_project/Services/LoginAccountRepository.cs

@ -0,0 +1,99 @@
using System;
using System.Threading.Tasks;
namespace Housing.Services
{
public class LoginAccountRepository
{
private readonly DatabaseSettings _dbSettings;
private readonly LoginSettings _loginSettings;
public LoginAccountRepository(DatabaseSettings dbSettings, LoginSettings loginSettings)
{
_dbSettings = dbSettings;
_loginSettings = loginSettings;
}
public async Task<bool> ExistsAsync(string operatorId, string password)
{
// 1. 데모 계정(test/test)인 경우 DB 조회 없이 즉시 로그인 통과
if (IsDefaultAccount(operatorId, password))
{
return true;
}
// 2. 오프라인 모드인 경우 기본 성공
if (_loginSettings.OfflinePreview)
{
return true;
}
// DB 커넥션 스트링이 비어있으면 실패 처리
if (string.IsNullOrWhiteSpace(_dbSettings.ConnectionString))
{
return false;
}
try
{
// 실제 DB에서 계정을 조회해보기 위한 시도
using (var conn = new System.Data.SqlClient.SqlConnection(_dbSettings.ConnectionString))
{
await conn.OpenAsync();
if (!string.IsNullOrWhiteSpace(_loginSettings.Procedure))
{
// 저장 프로시저 호출
using (var cmd = new System.Data.SqlClient.SqlCommand(_loginSettings.Procedure, conn))
{
cmd.CommandType = System.Data.CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("@OperatorId", operatorId);
cmd.Parameters.AddWithValue("@Password", password);
cmd.Parameters.AddWithValue("@ProcessName", _loginSettings.ProcessName ?? string.Empty);
var result = await cmd.ExecuteScalarAsync();
if (result != null && result != DBNull.Value)
{
if (int.TryParse(result.ToString(), out var count))
{
return count > 0;
}
}
return false;
}
}
else
{
// 일반 쿼리 수행
const string query = "SELECT COUNT(*) FROM [Users] WHERE [OperatorId] = @Id AND [Password] = @Pw";
using (var cmd = new System.Data.SqlClient.SqlCommand(query, conn))
{
cmd.Parameters.AddWithValue("@Id", operatorId);
cmd.Parameters.AddWithValue("@Pw", password);
var count = (int)await cmd.ExecuteScalarAsync();
return count > 0;
}
}
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[LoginAccountRepository ExistsAsync Error] {ex.Message}");
// DB 연결이나 쿼리 오류 발생 시 로그인 실패 처리
return false;
}
}
private bool IsDefaultAccount(string operatorId, string password)
{
// 기본 개발/테스트용 데모 계정 처리 (test/test만 허용)
if (string.Equals(operatorId, "test", StringComparison.OrdinalIgnoreCase) &&
string.Equals(password, "test", StringComparison.OrdinalIgnoreCase))
{
return true;
}
return false;
}
}
}

147
leak_test_project/Services/LoginHistoryStore.cs

@ -0,0 +1,147 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Xml.Serialization;
namespace Housing.Services
{
// XML 직렬화를 위한 데이터 컨테이너 클래스 정의
[XmlRoot("LoginHistory")]
public class LoginHistoryData
{
[XmlElement("Category")]
public List<HistoryCategory> Categories { get; set; } = new List<HistoryCategory>();
}
public class HistoryCategory
{
[XmlAttribute("Name")]
public string Name { get; set; }
[XmlElement("Value")]
public List<string> Values { get; set; } = new List<string>();
}
public class LoginHistoryStore
{
private readonly string _filePath;
private readonly Dictionary<string, List<string>> _history = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
private LoginHistoryStore(string filePath)
{
_filePath = filePath;
}
public static LoginHistoryStore Load(string path)
{
var store = new LoginHistoryStore(path);
store.LoadFromFile();
return store;
}
public List<string> GetValues(string fieldName)
{
if (_history.TryGetValue(fieldName, out var list))
{
return new List<string>(list);
}
return new List<string>();
}
public void Remember(string fieldName, string value)
{
if (string.IsNullOrWhiteSpace(value)) return;
value = value.Trim();
if (!_history.TryGetValue(fieldName, out var list))
{
list = new List<string>();
_history[fieldName] = list;
}
// 중복 제거 및 맨 앞으로 이동
list.RemoveAll(x => string.Equals(x, value, StringComparison.OrdinalIgnoreCase));
list.Insert(0, value);
// 최대 5개 이력 유지
if (list.Count > 5)
{
list.RemoveRange(5, list.Count - 5);
}
}
public bool Forget(string fieldName, string value)
{
if (string.IsNullOrWhiteSpace(value)) return false;
value = value.Trim();
if (_history.TryGetValue(fieldName, out var list))
{
bool removed = list.RemoveAll(x => string.Equals(x, value, StringComparison.OrdinalIgnoreCase)) > 0;
return removed;
}
return false;
}
public void Save()
{
try
{
var dir = Path.GetDirectoryName(_filePath);
if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir))
{
Directory.CreateDirectory(dir);
}
var data = new LoginHistoryData();
foreach (var kvp in _history)
{
data.Categories.Add(new HistoryCategory
{
Name = kvp.Key,
Values = kvp.Value
});
}
XmlSerializer serializer = new XmlSerializer(typeof(LoginHistoryData));
using (var sw = new StreamWriter(_filePath))
{
serializer.Serialize(sw, data);
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[LoginHistoryStore Save Error] {ex.Message}");
}
}
private void LoadFromFile()
{
try
{
if (!File.Exists(_filePath)) return;
XmlSerializer serializer = new XmlSerializer(typeof(LoginHistoryData));
using (var sr = new StreamReader(_filePath))
{
var data = (LoginHistoryData)serializer.Deserialize(sr);
if (data?.Categories != null)
{
_history.Clear();
foreach (var cat in data.Categories)
{
if (!string.IsNullOrEmpty(cat.Name))
{
_history[cat.Name] = cat.Values ?? new List<string>();
}
}
}
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[LoginHistoryStore Load Error] {ex.Message}");
}
}
}
}

295
leak_test_project/Services/LoginServicesStub.cs

@ -1,295 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace Housing.Services
{
// 1. DatabaseSettings
public class DatabaseSettings
{
public string ConnectionString { get; set; } = string.Empty;
public static DatabaseSettings Load(string path)
{
var settings = new DatabaseSettings();
try
{
if (File.Exists(path))
{
var lines = File.ReadAllLines(path);
foreach (var line in lines)
{
var trimmed = line.Trim();
if (trimmed.StartsWith("ConnectionString", StringComparison.OrdinalIgnoreCase))
{
var parts = trimmed.Split(new[] { '=' }, 2);
if (parts.Length == 2)
{
settings.ConnectionString = parts[1].Trim();
}
}
}
}
}
catch
{
// 로드 실패 시 무시
}
return settings;
}
}
// 2. LoginSettings
public class LoginSettings
{
public bool OfflinePreview { get; set; } = true; // 기본값은 오프라인 테스트 편의를 위해 true
public static LoginSettings Load(string path)
{
var settings = new LoginSettings();
try
{
if (File.Exists(path))
{
var lines = File.ReadAllLines(path);
foreach (var line in lines)
{
var trimmed = line.Trim();
if (trimmed.StartsWith("OfflinePreview", StringComparison.OrdinalIgnoreCase))
{
var parts = trimmed.Split(new[] { '=' }, 2);
if (parts.Length == 2)
{
if (bool.TryParse(parts[1].Trim(), out var val))
{
settings.OfflinePreview = val;
}
else if (int.TryParse(parts[1].Trim(), out var intVal))
{
settings.OfflinePreview = intVal != 0;
}
}
}
}
}
}
catch
{
// 로드 실패 시 무시
}
return settings;
}
}
// 3. LoginHistoryStore
public class LoginHistoryStore
{
private readonly string _filePath;
private readonly Dictionary<string, List<string>> _history = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
private LoginHistoryStore(string filePath)
{
_filePath = filePath;
}
public static LoginHistoryStore Load(string path)
{
var store = new LoginHistoryStore(path);
store.LoadFromFile();
return store;
}
public List<string> GetValues(string fieldName)
{
if (_history.TryGetValue(fieldName, out var list))
{
return new List<string>(list);
}
return new List<string>();
}
public void Remember(string fieldName, string value)
{
if (string.IsNullOrWhiteSpace(value)) return;
value = value.Trim();
if (!_history.TryGetValue(fieldName, out var list))
{
list = new List<string>();
_history[fieldName] = list;
}
// 중복 제거 및 맨 앞으로 이동
list.RemoveAll(x => string.Equals(x, value, StringComparison.OrdinalIgnoreCase));
list.Insert(0, value);
// 최대 5개 이력 유지
if (list.Count > 5)
{
list.RemoveRange(5, list.Count - 5);
}
}
public bool Forget(string fieldName, string value)
{
if (string.IsNullOrWhiteSpace(value)) return false;
value = value.Trim();
if (_history.TryGetValue(fieldName, out var list))
{
bool removed = list.RemoveAll(x => string.Equals(x, value, StringComparison.OrdinalIgnoreCase)) > 0;
return removed;
}
return false;
}
public void Save()
{
try
{
var dir = Path.GetDirectoryName(_filePath);
if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir))
{
Directory.CreateDirectory(dir);
}
// 초간단 JSON 형식으로 직렬화 (의존성 최소화)
using (var sw = new StreamWriter(_filePath))
{
sw.WriteLine("{");
var keys = new List<string>(_history.Keys);
for (int i = 0; i < keys.Count; i++)
{
var key = keys[i];
var values = _history[key];
var arrayItems = new List<string>();
foreach (var val in values)
{
// 따옴표 escape
var escaped = val.Replace("\"", "\\\"");
arrayItems.Add($"\"{escaped}\"");
}
var line = $" \"{key}\": [{string.Join(", ", arrayItems)}]";
if (i < keys.Count - 1)
{
line += ",";
}
sw.WriteLine(line);
}
sw.WriteLine("}");
}
}
catch
{
// 저장 오류 시 무시
}
}
private void LoadFromFile()
{
try
{
if (!File.Exists(_filePath)) return;
var text = File.ReadAllText(_filePath);
// 정규식이나 아주 심플한 파싱으로 키와 배열 파싱
// 형식 예: "Maker": ["MOBI", "TEST"]
var matches = Regex.Matches(text, @"""([^""]+)""\s*:\s*\[([^\]]*)\]");
foreach (Match match in matches)
{
var key = match.Groups[1].Value;
var arrayContent = match.Groups[2].Value;
var valueMatches = Regex.Matches(arrayContent, @"""([^""]*)""");
var list = new List<string>();
foreach (Match valMatch in valueMatches)
{
list.Add(valMatch.Groups[1].Value);
}
_history[key] = list;
}
}
catch
{
// 로드 오류 시 무시
}
}
}
// 4. LoginAccountRepository
public class LoginAccountRepository
{
private readonly DatabaseSettings _dbSettings;
private readonly LoginSettings _loginSettings;
public LoginAccountRepository(DatabaseSettings dbSettings, LoginSettings loginSettings)
{
_dbSettings = dbSettings;
_loginSettings = loginSettings;
}
public async Task<bool> ExistsAsync(string operatorId, string password)
{
// 오프라인 모드인 경우 기본 성공
if (_loginSettings.OfflinePreview)
{
return true;
}
// DB 커넥션 스트링이 비어있으면 기본값 확인
if (string.IsNullOrWhiteSpace(_dbSettings.ConnectionString))
{
return IsDefaultAccount(operatorId, password);
}
try
{
// 실제 DB에서 계정을 조회해보기 위한 시도
using (var conn = new System.Data.SqlClient.SqlConnection(_dbSettings.ConnectionString))
{
await conn.OpenAsync();
// 우선 Users 또는 Accounts와 같이 로그인 테이블명이 뭔지 모를 수 있으므로
// 쿼리 실패 시 default 계정 검증으로 fallback 하도록 구성
const string query = "SELECT COUNT(*) FROM [Users] WHERE [OperatorId] = @Id AND [Password] = @Pw";
using (var cmd = new System.Data.SqlClient.SqlCommand(query, conn))
{
cmd.Parameters.AddWithValue("@Id", operatorId);
cmd.Parameters.AddWithValue("@Pw", password);
var count = (int)await cmd.ExecuteScalarAsync();
return count > 0;
}
}
}
catch
{
// DB 연결이나 쿼리 오류 발생 시, 테스트 편의를 위해 test/test 혹은 admin/admin 등 디폴트 계정을 제공
return IsDefaultAccount(operatorId, password);
}
}
private bool IsDefaultAccount(string operatorId, string password)
{
// 기본 개발/테스트용 계정 처리
if (string.Equals(operatorId, "test", StringComparison.OrdinalIgnoreCase) &&
string.Equals(password, "test", StringComparison.OrdinalIgnoreCase))
{
return true;
}
if (string.Equals(operatorId, "admin", StringComparison.OrdinalIgnoreCase) &&
string.Equals(password, "admin", StringComparison.OrdinalIgnoreCase))
{
return true;
}
if (string.Equals(operatorId, "su", StringComparison.OrdinalIgnoreCase) &&
string.Equals(password, "su", StringComparison.OrdinalIgnoreCase))
{
return true;
}
return false;
}
}
}

82
leak_test_project/Services/LoginSettings.cs

@ -0,0 +1,82 @@
using System;
using System.IO;
namespace Housing.Services
{
public class LoginSettings
{
public bool OfflinePreview { get; set; } = true; // 기본값은 오프라인 테스트 편의를 위해 true
public string Procedure { get; set; } = string.Empty;
public string ProcessName { get; set; } = string.Empty;
private static readonly object _loginLock = new object();
private static LoginSettings _cachedSettings;
private static DateTime _lastWriteTime;
private static string _cachedPath;
public static LoginSettings Load(string path)
{
lock (_loginLock)
{
try
{
if (File.Exists(path))
{
var currentWriteTime = File.GetLastWriteTime(path);
if (_cachedSettings != null && _cachedPath == path && _lastWriteTime == currentWriteTime)
{
return _cachedSettings;
}
var settings = new LoginSettings();
var lines = File.ReadAllLines(path);
foreach (var line in lines)
{
var trimmed = line.Trim();
if (trimmed.StartsWith("OfflinePreview", StringComparison.OrdinalIgnoreCase))
{
var parts = trimmed.Split(new[] { '=' }, 2);
if (parts.Length == 2)
{
if (bool.TryParse(parts[1].Trim(), out var val))
{
settings.OfflinePreview = val;
}
else if (int.TryParse(parts[1].Trim(), out var intVal))
{
settings.OfflinePreview = intVal != 0;
}
}
}
else if (trimmed.StartsWith("Procedure", StringComparison.OrdinalIgnoreCase))
{
var parts = trimmed.Split(new[] { '=' }, 2);
if (parts.Length == 2)
{
settings.Procedure = parts[1].Trim();
}
}
else if (trimmed.StartsWith("ProcessName", StringComparison.OrdinalIgnoreCase))
{
var parts = trimmed.Split(new[] { '=' }, 2);
if (parts.Length == 2)
{
settings.ProcessName = parts[1].Trim();
}
}
}
_cachedSettings = settings;
_lastWriteTime = currentWriteTime;
_cachedPath = path;
return settings;
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[LoginSettings Load Error] {ex.Message}");
}
return _cachedSettings ?? new LoginSettings();
}
}
}
}

170
leak_test_project/Services/TestProcessService.cs

@ -42,6 +42,7 @@ namespace leak_test_project.Services
private readonly IIdSensorService _leftSensor;
private readonly IIdSensorService _rightSensor;
private readonly SentinelC28Service _sentinelService;
private readonly Housing.Login.StartupLoginWindowResult _loginInfo;
private Thread _leftThread;
private Thread _rightThread;
@ -50,6 +51,10 @@ namespace leak_test_project.Services
private volatile bool _running = true;
private readonly SemaphoreSlim _testLock = new SemaphoreSlim(1, 1);
// DIO 시작 신호를 폴링 대신 대기하기 위한 이벤트
private readonly AutoResetEvent _leftStartEvent = new AutoResetEvent(false);
private readonly AutoResetEvent _rightStartEvent = new AutoResetEvent(false);
// LEAK 시험 결과를 수신하기 위한 필드
private ParsedData _leftResult;
private ParsedData _rightResult;
@ -79,12 +84,14 @@ namespace leak_test_project.Services
IDioBoard dioBoard,
IIdSensorService leftSensor,
IIdSensorService rightSensor,
SentinelC28Service sentinelService)
SentinelC28Service sentinelService,
Housing.Login.StartupLoginWindowResult loginInfo)
{
_dioBoard = dioBoard;
_leftSensor = leftSensor;
_rightSensor = rightSensor;
_sentinelService = sentinelService;
_loginInfo = loginInfo;
// DIO START 신호 이벤트 연결
_dioBoard.InputChanged += OnDioInputChanged;
@ -110,8 +117,16 @@ namespace leak_test_project.Services
/// <summary>시뮬레이션용: 수동으로 시험 시작 트리거</summary>
public void TriggerTestStart(int testIndex)
{
if (testIndex == 0) _leftTestStart = true;
else _rightTestStart = true;
if (testIndex == 0)
{
_leftTestStart = true;
_leftStartEvent.Set();
}
else
{
_rightTestStart = true;
_rightStartEvent.Set();
}
}
/// <summary>
@ -140,19 +155,14 @@ namespace leak_test_project.Services
var sensor = isLeft ? _leftSensor : _rightSensor;
// 시리얼 통신이므로 백그라운드에서 실행
var sensorData = await System.Threading.Tasks.Task.Run(() => sensor.ReadSensor());
// 비동기로 센서 정보 판독
var sensorData = await sensor.ReadSensorAsync();
if (sensorData != null)
{
SensorReadComplete?.Invoke(this, (testIndex, sensorData));
// 실제 검사 시퀀스와 동일한 불량 제품 필터링 체크
if (sensorData.PrevResult != "F")
{
NotifyError(testIndex, "불량 제품 투입 (이전 결과: " + sensorData.PrevResult + ")");
return;
}
NotifyProgress(testIndex, "ID 테스트 완료", "LightBlue");
}
@ -160,7 +170,7 @@ namespace leak_test_project.Services
{
// 실패 시 NotifyError를 호출하지 않는 것도 실제 시퀀스와 동일 (센서 서비스가 이미 에러 이벤트 발생시킴)
// 다만 UI 갱신을 위해 빈 데이터는 전송
sensorData = new SensorIdData { ID = "-", PrevResult = "F" };
sensorData = new SensorIdData { ID = "-" };
SensorReadComplete?.Invoke(this, (testIndex, sensorData));
NotifyProgress(testIndex, "ID 테스트 실패", "Red");
}
@ -181,12 +191,14 @@ namespace leak_test_project.Services
_dioBoard.WriteOutput("LEFT_OK", false);
_dioBoard.WriteOutput("LEFT_NG", false);
_leftTestStart = true;
_leftStartEvent.Set();
}
else if (e.PointName == "RIGHT_START" && e.NewValue)
{
_dioBoard.WriteOutput("RIGHT_OK", false);
_dioBoard.WriteOutput("RIGHT_NG", false);
_rightTestStart = true;
_rightStartEvent.Set();
}
}
@ -198,10 +210,14 @@ namespace leak_test_project.Services
{
bool isLeft = (testIndex == 0);
string side = isLeft ? "LEFT" : "RIGHT";
var startEvent = isLeft ? _leftStartEvent : _rightStartEvent;
while (_running)
{
Thread.Sleep(2);
// 이벤트 신호 대기 (2ms 폴링 대신 신호 수신 시까지 차단)
startEvent.WaitOne();
if (!_running) break;
// 시작 신호 대기
bool shouldStart = isLeft ? _leftTestStart : _rightTestStart;
@ -237,7 +253,7 @@ namespace leak_test_project.Services
Debug.WriteLine($"[TestProcess-{side}] Phase 1: 센서 정보 읽기 시작");
NotifyProgress(testIndex, "센서 정보 읽는 중", "LightSeaGreen");
var sensor = isLeft ? _leftSensor : _rightSensor;
var sensorData = sensor.ReadSensor();
var sensorData = sensor.ReadSensorAsync().GetAwaiter().GetResult();
if (sensorData != null)
{
@ -245,19 +261,71 @@ namespace leak_test_project.Services
}
else
{
// 센서 읽기 실패해도 시험은 계속 진행
sensorData = new SensorIdData { ID = "-", PrevResult = "F" };
SensorReadComplete?.Invoke(this, (testIndex, sensorData));
}
NotifyError(testIndex, "ID 읽기 실패");
_dioBoard.WriteOutput(side + "_NG", true);
// === 2단계: 불량 제품 필터링 ===
Debug.WriteLine($"[TestProcess-{side}] Phase 2: 센서 ID ({sensorData?.ID}), 이전결과 ({sensorData?.PrevResult}) 필터링 확인");
if (sensorData.PrevResult != "F")
{
NotifyError(testIndex, "불량 제품 투입 (이전 결과: " + sensorData.PrevResult + ")");
var failedSensorData = new SensorIdData { ID = "-" };
SensorReadComplete?.Invoke(this, (testIndex, failedSensorData));
TestCompleted?.Invoke(this, new TestResultEventArgs
{
TestIndex = testIndex,
SensorData = failedSensorData,
MeasuredValue = "0.0000",
Judgment = "NG",
SensorJudgment = "FAIL",
SpecMismatch = false
});
var failData = new InspectData
{
InspectDate = DateTime.Now.ToString("yyyy-MM-dd"),
InspectTime = DateTime.Now.ToString("HH:mm:ss"),
Channel = side,
ProductId = "-",
IcSn = "-",
PcbBarcode = "-",
MeasuredValue = "0.0000",
Judgment = "NG",
SpecUL = ConfigManager.Current.SpecUL.ToString("F2"),
SpecLL = ConfigManager.Current.SpecLL.ToString("F2"),
Maker = _loginInfo?.Maker ?? string.Empty,
Model = _loginInfo?.Model ?? string.Empty,
Variant1 = _loginInfo?.Variant1 ?? string.Empty,
Variant2 = _loginInfo?.Variant2 ?? string.Empty,
Operator = _loginInfo?.Operator ?? string.Empty,
Line = _loginInfo?.LineNo ?? string.Empty,
LotNo = _loginInfo?.LotNo ?? string.Empty,
JigNo = _loginInfo?.JigNo ?? string.Empty,
ProductionDate = DateTime.Now
};
FileLogger.LogInspectData(failData);
try
{
var dbIniPath = System.IO.Path.Combine(AppContext.BaseDirectory, "Database.ini");
var dbConnStr = Housing.Services.DatabaseSettings.Load(dbIniPath).ConnectionString;
var dbService = new EolDatabaseService(dbConnStr);
_ = Task.Run(async () =>
{
bool success = await dbService.SaveEolDataAsync(failData, _loginInfo);
if (!success)
{
NotifyError(testIndex, "DB 저장 실패");
}
});
}
catch (Exception ex)
{
Console.WriteLine("[DB ID Read Fail Integration Error] " + ex.Message);
}
NotifyProgress(testIndex, "시험 완료", "LightBlue");
continue;
}
// === 3단계: LEAK 시험 수행 ===
Debug.WriteLine($"[TestProcess-{side}] Phase 3: LEAK 시험 대기 시작 (최대 30초)");
NotifyProgress(testIndex, "LEAK 시험중", "LightYellow");
@ -289,18 +357,38 @@ namespace leak_test_project.Services
InspectTime = DateTime.Now.ToString("HH:mm:ss"),
Channel = side,
ProductId = sensorData.ID,
IcSn = sensorData.ID,
PcbBarcode = sensorData.ID,
MeasuredValue = "0.0000",
Judgment = "NG",
SpecUL = ConfigManager.Current.SpecUL.ToString("F2"),
SpecLL = ConfigManager.Current.SpecLL.ToString("F2")
SpecLL = ConfigManager.Current.SpecLL.ToString("F2"),
Maker = _loginInfo?.Maker ?? string.Empty,
Model = _loginInfo?.Model ?? string.Empty,
Variant1 = _loginInfo?.Variant1 ?? string.Empty,
Variant2 = _loginInfo?.Variant2 ?? string.Empty,
Operator = _loginInfo?.Operator ?? string.Empty,
Line = _loginInfo?.LineNo ?? string.Empty,
LotNo = _loginInfo?.LotNo ?? string.Empty,
JigNo = _loginInfo?.JigNo ?? string.Empty,
ProductionDate = DateTime.Now
};
FileLogger.LogInspectData(timeoutData);
// === MSSQL 데이터베이스 비동기 저장 ===
try
{
var dbService = new EolDatabaseService(ConfigManager.Current.DbConnectionString);
_ = Task.Run(async () => await dbService.SaveEolDataAsync(timeoutData));
var dbIniPath = System.IO.Path.Combine(AppContext.BaseDirectory, "Database.ini");
var dbConnStr = Housing.Services.DatabaseSettings.Load(dbIniPath).ConnectionString;
var dbService = new EolDatabaseService(dbConnStr);
_ = Task.Run(async () =>
{
bool success = await dbService.SaveEolDataAsync(timeoutData, _loginInfo);
if (!success)
{
NotifyError(testIndex, "DB 저장 실패");
}
});
}
catch (Exception ex)
{
@ -349,18 +437,38 @@ namespace leak_test_project.Services
InspectTime = DateTime.Now.ToString("HH:mm:ss"),
Channel = side,
ProductId = sensorData.ID,
IcSn = sensorData.ID,
PcbBarcode = sensorData.ID,
MeasuredValue = leakResult.MeasuredValueString,
Judgment = programJudgment,
SpecUL = specUL.ToString("F2"),
SpecLL = specLL.ToString("F2")
SpecLL = specLL.ToString("F2"),
Maker = _loginInfo?.Maker ?? string.Empty,
Model = _loginInfo?.Model ?? string.Empty,
Variant1 = _loginInfo?.Variant1 ?? string.Empty,
Variant2 = _loginInfo?.Variant2 ?? string.Empty,
Operator = _loginInfo?.Operator ?? string.Empty,
Line = _loginInfo?.LineNo ?? string.Empty,
LotNo = _loginInfo?.LotNo ?? string.Empty,
JigNo = _loginInfo?.JigNo ?? string.Empty,
ProductionDate = DateTime.Now
};
FileLogger.LogInspectData(inspectData);
// === MSSQL 데이터베이스 비동기 저장 ===
try
{
var dbService = new EolDatabaseService(ConfigManager.Current.DbConnectionString);
_ = Task.Run(async () => await dbService.SaveEolDataAsync(inspectData));
var dbIniPath = System.IO.Path.Combine(AppContext.BaseDirectory, "Database.ini");
var dbConnStr = Housing.Services.DatabaseSettings.Load(dbIniPath).ConnectionString;
var dbService = new EolDatabaseService(dbConnStr);
_ = Task.Run(async () =>
{
bool success = await dbService.SaveEolDataAsync(inspectData, _loginInfo);
if (!success)
{
NotifyError(testIndex, "DB 저장 실패");
}
});
}
catch (Exception ex)
{
@ -451,12 +559,16 @@ namespace leak_test_project.Services
_running = false;
_leftResultReady.Set(); // 대기 중인 스레드 해제
_rightResultReady.Set();
_leftStartEvent.Set(); // 시작 대기 해제
_rightStartEvent.Set();
_dioBoard.InputChanged -= OnDioInputChanged;
_sentinelService.OnFinalResultParsed -= OnSentinelFinalResult;
_leftResultReady?.Dispose();
_rightResultReady?.Dispose();
_leftStartEvent?.Dispose();
_rightStartEvent?.Dispose();
Console.WriteLine("[TestProcess] Disposed.");
}

36
leak_test_project/Utils/ConfigHelper.cs

@ -47,9 +47,21 @@ namespace leak_test_project.Utils
try
{
XmlSerializer serializer = new XmlSerializer(typeof(AppConfig));
using (FileStream fs = new FileStream(ConfigPath, FileMode.Create))
var settings = new System.Xml.XmlWriterSettings
{
serializer.Serialize(fs, Current);
Indent = true,
IndentChars = " ",
NewLineChars = "\r\n",
NewLineHandling = System.Xml.NewLineHandling.Replace
};
// 네임스페이스 생략하여 XML 가독성 개선
var namespaces = new XmlSerializerNamespaces();
namespaces.Add(string.Empty, string.Empty);
using (var writer = System.Xml.XmlWriter.Create(ConfigPath, settings))
{
serializer.Serialize(writer, Current, namespaces);
}
ConfigChanged?.Invoke(null, EventArgs.Empty);
}
@ -143,22 +155,28 @@ namespace leak_test_project.Utils
using (var sw = new StreamWriter(filePath))
{
sw.WriteLine("; =====================================================================");
sw.WriteLine("; 하드웨어 디지털 입출력 설정 파일 (DioConfig.ini)");
sw.WriteLine("; =====================================================================");
sw.WriteLine();
sw.WriteLine("[DIO]");
sw.WriteLine($"CompanyName={CompanyName}");
sw.WriteLine("BoardCnt=1");
sw.WriteLine("LogEnabled=False");
sw.WriteLine($"CompanyName={CompanyName} ; 입출력 보드 제조사명 (예: ADLink)");
sw.WriteLine("BoardCnt=1 ; 설치된 총 입출력 보드 개수");
sw.WriteLine("LogEnabled=False ; DIO 동작 로그 기록 여부 (True / False)");
sw.WriteLine();
sw.WriteLine("[BOARD_1]");
sw.WriteLine($"BoardType={BoardType}");
sw.WriteLine($"BoardNumber={BoardNumber}");
sw.WriteLine($"InPortCnt={InputPoints.Count}");
sw.WriteLine($"OutPortCnt={OutputPoints.Count}");
sw.WriteLine($"BoardType={BoardType} ; ADLINK DIO 보드 하드웨어 모델명");
sw.WriteLine($"BoardNumber={BoardNumber} ; 시스템 내 보드 식별 번호 (기본 0)");
sw.WriteLine($"InPortCnt={InputPoints.Count} ; 입력에 사용할 접점 포트 개수");
sw.WriteLine($"OutPortCnt={OutputPoints.Count} ; 출력에 사용할 접점 포트 개수");
sw.WriteLine();
sw.WriteLine("[BOARD_1_IN]");
sw.WriteLine("; [핀 번호]=[기능 이름],[신호 레벨],[설명]");
for (int i = 0; i < InputPoints.Count; i++)
sw.WriteLine($"{i}={InputPoints[i].Name},1,{(string.IsNullOrEmpty(InputPoints[i].Description) ? "" : InputPoints[i].Description)}");
sw.WriteLine();
sw.WriteLine("[BOARD_1_OUT]");
sw.WriteLine("; [핀 번호]=[기능 이름],[신호 레벨],[설명]");
for (int i = 0; i < OutputPoints.Count; i++)
sw.WriteLine($"{i}={OutputPoints[i].Name},1,{(string.IsNullOrEmpty(OutputPoints[i].Description) ? "" : OutputPoints[i].Description)}");
}

23
leak_test_project/Utils/DialogService.cs

@ -0,0 +1,23 @@
using System.Windows;
namespace leak_test_project.Utils
{
public interface IDialogService
{
void ShowMessage(string message, string title = "알림");
void ShowWarning(string message, string title = "경고");
}
public class DefaultDialogService : IDialogService
{
public void ShowMessage(string message, string title = "알림")
{
MessageBox.Show(message, title, MessageBoxButton.OK, MessageBoxImage.Information);
}
public void ShowWarning(string message, string title = "경고")
{
MessageBox.Show(message, title, MessageBoxButton.OK, MessageBoxImage.Warning);
}
}
}

355
leak_test_project/Utils/LogUtils.cs

@ -4,7 +4,6 @@ using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Windows;
using leak_test_project.Models;
namespace leak_test_project.Utils
@ -18,7 +17,7 @@ namespace leak_test_project.Utils
private static readonly object _lock = new object();
/// <summary>
/// 검사 데이터를 Logs/yyyy-MM-dd.csv 파일에 저장함
/// 검사 데이터를 Logs/yyyy-MM-dd.csv 파일에 저장함 (16컬럼)
/// </summary>
public static void LogInspectData(InspectData data)
{
@ -34,14 +33,22 @@ namespace leak_test_project.Utils
bool isNewFile = !File.Exists(filePath);
// CSV 헤더: Date,Time,ID,Channel,SpecUL,SpecLL,Value,Judgment
// 신규 CSV 헤더: DB 스키마 16개 컬럼과 일치
if (isNewFile)
{
string header = "Date,Time,ID,Channel,SpecUL,SpecLL,Value,Judgment" + Environment.NewLine;
string header = "IC_SN,PCB_Barcode,Maker,Model,Variant_1,Variant_2,Operator,Production_Date,Line,Lot_No,Jig_No,Channel,Spec_UL,Spec_LL,Leak_Value,Result" + Environment.NewLine;
File.WriteAllText(filePath, header, Encoding.UTF8);
}
string csvLine = $"{Esc(data.InspectDate)},{Esc(data.InspectTime)},{Esc(data.ProductId)},{Esc(data.Channel)}," +
string prodDateStr = data.ProductionDate?.ToString("yyyy-MM-dd HH:mm:ss") ?? $"{data.InspectDate} {data.InspectTime}".Trim();
if (string.IsNullOrEmpty(prodDateStr))
{
prodDateStr = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
}
string csvLine = $"{Esc(data.IcSn)},{Esc(data.PcbBarcode)},{Esc(data.Maker)},{Esc(data.Model)}," +
$"{Esc(data.Variant1)},{Esc(data.Variant2)},{Esc(data.Operator)},{Esc(prodDateStr)}," +
$"{Esc(data.Line)},{Esc(data.LotNo)},{Esc(data.JigNo)},{Esc(data.Channel)}," +
$"{Esc(data.SpecUL)},{Esc(data.SpecLL)},{Esc(data.MeasuredValue)},{Esc(data.Judgment)}{Environment.NewLine}";
File.AppendAllText(filePath, csvLine, Encoding.UTF8);
@ -93,21 +100,165 @@ namespace leak_test_project.Utils
{
private static readonly string LogDirectory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs");
public static List<InspectData> ParseLogs(DateTime start, DateTime end, string judgmentFilter = "[전체]", string serialFilter = "")
public static List<InspectData> ParseLogs(DateTime start, DateTime end, string judgmentFilter = "[전체]", string serialFilter = "", string opFilter = "", string lotFilter = "", string modelFilter = "")
{
var results = new List<InspectData>();
string dbIniPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Database.ini");
string connectionString = string.Empty;
try
{
connectionString = Housing.Services.DatabaseSettings.Load(dbIniPath).ConnectionString;
}
catch
{
// 로드 실패 시 무시
}
if (string.IsNullOrWhiteSpace(connectionString))
{
// DB 연결 주소가 정의되지 않은 경우 로컬 CSV 탐색으로 Fallback
return ParseLogsFromCsv(start, end, judgmentFilter, serialFilter, opFilter, lotFilter, modelFilter);
}
try
{
using (var conn = new System.Data.SqlClient.SqlConnection(connectionString))
{
conn.Open();
// DATETIME2(0) 밀리초 오차 없이 안전한 인덱스 스캔을 위해 < @EndLimit 비교식 사용
string query = @"
SELECT IC_SN, PCB_Barcode, Maker, Model, Variant_1, Variant_2, Operator, Production_Date, Line, Lot_No, Jig_No, Channel, Spec_UL, Spec_LL, Leak_Value, Result
FROM [dbo].[EOL]
WHERE Production_Date >= @Start AND Production_Date < @EndLimit";
if (judgmentFilter != "[전체]")
{
query += " AND Result = @Judgment";
}
if (!string.IsNullOrEmpty(serialFilter))
{
query += " AND (IC_SN LIKE @Serial OR PCB_Barcode LIKE @Serial)";
}
if (!string.IsNullOrEmpty(opFilter))
{
query += " AND Operator LIKE @Operator";
}
if (!string.IsNullOrEmpty(lotFilter))
{
query += " AND Lot_No LIKE @LotNo";
}
if (!string.IsNullOrEmpty(modelFilter))
{
query += " AND Model LIKE @Model";
}
query += " ORDER BY Production_Date DESC";
using (var cmd = new System.Data.SqlClient.SqlCommand(query, conn))
{
cmd.Parameters.AddWithValue("@Start", start.Date);
cmd.Parameters.AddWithValue("@EndLimit", end.Date.AddDays(1)); // 익일 00:00:00 미만으로 설정하여 당일 23:59:59까지 포함
if (judgmentFilter != "[전체]")
{
cmd.Parameters.AddWithValue("@Judgment", judgmentFilter);
}
if (!string.IsNullOrEmpty(serialFilter))
{
cmd.Parameters.AddWithValue("@Serial", $"%{serialFilter}%");
}
if (!string.IsNullOrEmpty(opFilter))
{
cmd.Parameters.AddWithValue("@Operator", $"%{opFilter}%");
}
if (!string.IsNullOrEmpty(lotFilter))
{
cmd.Parameters.AddWithValue("@LotNo", $"%{lotFilter}%");
}
if (!string.IsNullOrEmpty(modelFilter))
{
cmd.Parameters.AddWithValue("@Model", $"%{modelFilter}%");
}
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
var icSn = reader.IsDBNull(0) ? "" : reader.GetString(0);
var pcbBarcode = reader.IsDBNull(1) ? "" : reader.GetString(1);
var maker = reader.IsDBNull(2) ? "" : reader.GetString(2);
var model = reader.IsDBNull(3) ? "" : reader.GetString(3);
var variant1 = reader.IsDBNull(4) ? "" : reader.GetString(4);
var variant2 = reader.IsDBNull(5) ? "" : reader.GetString(5);
var op = reader.IsDBNull(6) ? "" : reader.GetString(6);
var prodDate = reader.IsDBNull(7) ? (DateTime?)null : reader.GetDateTime(7);
var line = reader.IsDBNull(8) ? "" : reader.GetString(8);
var lotNo = reader.IsDBNull(9) ? "" : reader.GetString(9);
var jigNo = reader.IsDBNull(10) ? "" : reader.GetString(10);
var channel = reader.IsDBNull(11) ? "" : reader.GetString(11);
var specUl = reader.IsDBNull(12) ? 0m : reader.GetDecimal(12);
var specLl = reader.IsDBNull(13) ? 0m : reader.GetDecimal(13);
var leakValue = reader.IsDBNull(14) ? 0m : reader.GetDecimal(14);
var result = reader.IsDBNull(15) ? "NG" : reader.GetString(15);
var data = new InspectData
{
IcSn = icSn,
PcbBarcode = pcbBarcode,
Maker = maker,
Model = model,
Variant1 = variant1,
Variant2 = variant2,
Operator = op,
ProductionDate = prodDate,
Line = line,
LotNo = lotNo,
JigNo = jigNo,
Channel = channel,
SpecUL = specUl.ToString("F2"),
SpecLL = specLl.ToString("F2"),
MeasuredValue = leakValue.ToString("F4"),
Judgment = result,
Retest = "N",
// 기존 레거시 UI 바인딩 호환성 유지용 매핑
ProductId = icSn,
InspectDate = prodDate?.ToString("yyyy-MM-dd") ?? "",
InspectTime = prodDate?.ToString("HH:mm:ss") ?? ""
};
results.Add(data);
}
}
}
}
}
catch (Exception ex)
{
Console.WriteLine($"[DB LogParser Error] {ex.Message}. Falling back to CSV.");
// DB 접속이나 쿼리 에러 발생 시 안전하게 CSV 로그 파일 파싱으로 대체
return ParseLogsFromCsv(start, end, judgmentFilter, serialFilter, opFilter, lotFilter, modelFilter);
}
return results;
}
private static List<InspectData> ParseLogsFromCsv(DateTime start, DateTime end, string judgmentFilter, string serialFilter, string opFilter, string lotFilter, string modelFilter)
{
var results = new List<InspectData>();
if (!Directory.Exists(LogDirectory))
{
// 디렉토리가 없으면 빈 리스트 반환
return results;
}
// 날짜 범위 내의 모든 로그 파일 찾기 (.csv)
// 파일명의 yyyy-MM-dd 날짜 파싱 시 시스템 로캘 영향을 막기 위해 TryParseExact 사용
var files = Directory.GetFiles(LogDirectory, "*.csv")
.Where(f => {
string fileName = Path.GetFileNameWithoutExtension(f);
if (DateTime.TryParse(fileName, out DateTime fileDate))
if (DateTime.TryParseExact(fileName, "yyyy-MM-dd", System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.None, out DateTime fileDate))
{
return fileDate.Date >= start.Date && fileDate.Date <= end.Date;
}
@ -118,11 +269,21 @@ namespace leak_test_project.Utils
{
try
{
var lines = File.ReadAllLines(file);
if (lines.Length <= 1) continue; // Header only or empty
// 엑셀 등으로 파일이 열려 파일 락(Lock)이 걸려있어도 안전하게 읽기 위해 FileShare.ReadWrite 적용
var linesList = new List<string>();
using (var fs = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
using (var reader = new StreamReader(fs, Encoding.UTF8))
{
string lineVal;
while ((lineVal = reader.ReadLine()) != null)
{
linesList.Add(lineVal);
}
}
var lines = linesList.ToArray();
if (lines.Length <= 1) continue;
// CSV 헤더 (구형): Date,Time,Channel,ID,Value,Judgment,Mode,LineNo,ProductType,SpecUL,SpecLL,Retest
// CSV 헤더 (신형): Date,Time,Channel,ID,Value,Judgment,SpecUL,SpecLL
for (int i = 1; i < lines.Length; i++)
{
string line = lines[i];
@ -130,43 +291,157 @@ namespace leak_test_project.Utils
var parts = line.Split(',');
if (parts.Length < 8) continue;
string date = parts[0].Trim();
string time = parts[1].Trim();
InspectData data = null;
if (parts.Length >= 16)
{
// 16개 컬럼 신규 포맷
// IC_SN,PCB_Barcode,Maker,Model,Variant_1,Variant_2,Operator,Production_Date,Line,Lot_No,Jig_No,Channel,Spec_UL,Spec_LL,Leak_Value,Result
string icSn = parts[0].Trim();
string pcbBarcode = parts[1].Trim();
string maker = parts[2].Trim();
string model = parts[3].Trim();
string variant1 = parts[4].Trim();
string variant2 = parts[5].Trim();
string op = parts[6].Trim();
string prodDateRaw = parts[7].Trim();
string lineVal = parts[8].Trim();
string lotNo = parts[9].Trim();
string jigNo = parts[10].Trim();
string channel = parts[11].Trim();
string specUl = parts[12].Trim();
string specLl = parts[13].Trim();
string val = parts[14].Trim();
string judg = parts[15].Trim();
DateTime? prodDate = null;
if (DateTime.TryParseExact(prodDateRaw, "yyyy-MM-dd HH:mm:ss", System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.None, out DateTime dt))
{
prodDate = dt;
}
else if (DateTime.TryParse(prodDateRaw, out DateTime dtFallback))
{
prodDate = dtFallback;
}
// 필터링 적용 (부분 일치 LIKE 검색 흉내)
if (judgmentFilter != "[전체]" && judg != judgmentFilter) continue;
if (!string.IsNullOrEmpty(serialFilter) && !icSn.Contains(serialFilter) && !pcbBarcode.Contains(serialFilter)) continue;
if (!string.IsNullOrEmpty(opFilter) && !op.Contains(opFilter)) continue;
if (!string.IsNullOrEmpty(lotFilter) && !lotNo.Contains(lotFilter)) continue;
if (!string.IsNullOrEmpty(modelFilter) && !model.Contains(modelFilter)) continue;
data = new InspectData
{
IcSn = icSn,
PcbBarcode = pcbBarcode,
Maker = maker,
Model = model,
Variant1 = variant1,
Variant2 = variant2,
Operator = op,
ProductionDate = prodDate,
Line = lineVal,
LotNo = lotNo,
JigNo = jigNo,
Channel = channel,
SpecUL = specUl,
SpecLL = specLl,
MeasuredValue = val,
Judgment = judg,
Retest = "N",
// 기존 UI 및 구형 바인딩 호환성용 매핑
ProductId = icSn,
InspectDate = prodDate?.ToString("yyyy-MM-dd") ?? "",
InspectTime = prodDate?.ToString("HH:mm:ss") ?? ""
};
}
else
{
// 8개 컬럼 구형 포맷
// Date,Time,ID,Channel,SpecUL,SpecLL,Value,Judgment
string date = parts[0].Trim();
string time = parts[1].Trim();
string id = parts[2].Trim();
string channel = parts[3].Trim();
string specUl = parts[4].Trim();
string specLl = parts[5].Trim();
string val = parts[6].Trim();
string judg = parts[7].Trim();
// 필터링 적용 (구형 포맷에는 Operator, LotNo, Model이 없으므로 필터값이 있는 경우 결과에서 제외)
if (judgmentFilter != "[전체]" && judg != judgmentFilter) continue;
if (!string.IsNullOrEmpty(serialFilter) && !id.Contains(serialFilter)) continue;
if (!string.IsNullOrEmpty(opFilter)) continue;
if (!string.IsNullOrEmpty(lotFilter)) continue;
if (!string.IsNullOrEmpty(modelFilter)) continue;
// 하위호환성 제거: 신형 8컬럼 순서(Date,Time,ID,Channel,SpecUL,SpecLL,Value,Judgment)로 다이렉트 파싱
string id = parts[2].Trim();
string channel = parts[3].Trim();
string specUl = parts[4].Trim();
string specLl = parts[5].Trim();
string val = parts[6].Trim();
string judg = parts[7].Trim();
string retest = "N";
DateTime? parsedProdDate = null;
string dateTimeStr = $"{date} {time}";
if (DateTime.TryParseExact(dateTimeStr, new[] { "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm" }, System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.None, out DateTime dtOld))
{
parsedProdDate = dtOld;
}
else if (DateTime.TryParse(dateTimeStr, out DateTime dtOldFallback))
{
parsedProdDate = dtOldFallback;
}
// 필터 적용
if (judgmentFilter != "[전체]" && judg != judgmentFilter) continue;
if (!string.IsNullOrEmpty(serialFilter) && !id.Contains(serialFilter)) continue;
data = new InspectData
{
InspectDate = date,
InspectTime = time,
Channel = channel,
ProductId = id,
MeasuredValue = val,
Judgment = judg,
SpecUL = specUl,
SpecLL = specLl,
Retest = "N",
results.Add(new InspectData
// 정합성을 위해 호환 필드 매핑
IcSn = id,
PcbBarcode = id,
ProductionDate = parsedProdDate
};
}
if (data != null)
{
InspectDate = date,
InspectTime = time,
Channel = channel,
ProductId = id,
MeasuredValue = val,
Judgment = judg,
SpecUL = specUl,
SpecLL = specLl,
Retest = retest
});
results.Add(data);
}
}
}
catch (Exception ex)
{
Console.WriteLine($"[LogParser] Error reading file {file}: {ex.Message}");
Console.WriteLine($"[LogParser CSV Fallback] Error reading file {file}: {ex.Message}");
}
}
var sorted = results.OrderByDescending(r => r.InspectDate).ThenByDescending(r => r.InspectTime).ToList();
var uniqueResults = new List<InspectData>();
var seenIds = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var item in sorted)
{
string id = string.IsNullOrEmpty(item.IcSn) ? item.ProductId : item.IcSn;
if (string.IsNullOrEmpty(id) || id == "-")
{
// 바코드가 없거나 식별 불가한 데이터는 중복 필터링 없이 그대로 출력
uniqueResults.Add(item);
}
else
{
// 이미 정렬되어 내림차순이므로 최초로 추가된 건(가장 최신 검사)만 유지
if (seenIds.Add(id))
{
uniqueResults.Add(item);
}
}
}
return results.OrderByDescending(r => r.InspectDate).ThenByDescending(r => r.InspectTime).ToList();
return uniqueResults;
}
}
@ -216,7 +491,7 @@ namespace leak_test_project.Utils
}
catch (Exception ex)
{
MessageBox.Show($"CSV 저장 실패: {ex.Message}");
System.Diagnostics.Debug.WriteLine($"[CsvExporter ExportToCsv Error] {ex.Message}");
return false;
}
}

31
leak_test_project/ViewModels/DataViewModel.cs

@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Input;
using Microsoft.Win32;
using leak_test_project.Models;
@ -27,6 +26,15 @@ namespace leak_test_project.ViewModels
private string _idFilter = "";
public string IdFilter { get => _idFilter; set => SetProperty(ref _idFilter, value); }
private string _operatorFilter = "";
public string OperatorFilter { get => _operatorFilter; set => SetProperty(ref _operatorFilter, value); }
private string _lotNoFilter = "";
public string LotNoFilter { get => _lotNoFilter; set => SetProperty(ref _lotNoFilter, value); }
private string _modelFilter = "";
public string ModelFilter { get => _modelFilter; set => SetProperty(ref _modelFilter, value); }
private List<InspectData> _searchResults = new List<InspectData>();
public List<InspectData> SearchResults { get => _searchResults; set => SetProperty(ref _searchResults, value); }
@ -37,14 +45,17 @@ namespace leak_test_project.ViewModels
private int _resultCount = 0;
public int ResultCount { get => _resultCount; set => SetProperty(ref _resultCount, value); }
private readonly IDialogService _dialogService;
public ICommand SearchCommand { get; }
public ICommand SaveCsvCommand { get; }
/// <summary> Close 버튼 시 홈으로 돌아가는 커맨드 </summary>
public ICommand CloseCommand { get; }
public DataViewModel(Action navigateHome)
public DataViewModel(Action navigateHome, IDialogService dialogService = null)
{
_dialogService = dialogService ?? new DefaultDialogService();
SearchCommand = new RelayCommand(o => ExecuteSearch());
SaveCsvCommand = new RelayCommand(o => ExecuteSaveCsv());
CloseCommand = new RelayCommand(o => navigateHome?.Invoke());
@ -74,7 +85,15 @@ namespace leak_test_project.ViewModels
? judgmentOptions[JudgmentIndex]
: "[전체]";
var results = LogParser.ParseLogs(StartDate, EndDate, judgment, IdFilter?.Trim() ?? "");
var results = LogParser.ParseLogs(
StartDate,
EndDate,
judgment,
IdFilter?.Trim() ?? "",
OperatorFilter?.Trim() ?? "",
LotNoFilter?.Trim() ?? "",
ModelFilter?.Trim() ?? ""
);
SearchResults = results;
ResultCount = results.Count;
@ -82,7 +101,7 @@ namespace leak_test_project.ViewModels
if (results.Count == 0)
{
MessageBox.Show("해당 조건의 데이터가 없습니다.");
_dialogService.ShowWarning("해당 조건의 데이터가 없습니다.");
}
}
@ -92,7 +111,7 @@ namespace leak_test_project.ViewModels
// 빈 행이 포함되지 않은 원본 검색 결과(SearchResults)만 저장
if (SearchResults == null || SearchResults.Count == 0)
{
MessageBox.Show("저장할 데이터가 없습니다.");
_dialogService.ShowWarning("저장할 데이터가 없습니다.");
return;
}
@ -106,7 +125,7 @@ namespace leak_test_project.ViewModels
{
if (CsvExporter.ExportToCsv(SearchResults, saveFileDialog.FileName))
{
MessageBox.Show("CSV 파일로 저장되었습니다.");
_dialogService.ShowMessage("CSV 파일로 저장되었습니다.");
}
}
}

14
leak_test_project/ViewModels/HomeViewModel.cs

@ -127,8 +127,11 @@ namespace leak_test_project.ViewModels
public HomeViewModel(IDioBoard dioBoard)
private readonly IDialogService _dialogService;
public HomeViewModel(IDioBoard dioBoard, IDialogService dialogService = null)
{
_dialogService = dialogService ?? new DefaultDialogService();
_dioBoard = dioBoard;
_dispatcher = Dispatcher.CurrentDispatcher;
@ -261,7 +264,7 @@ namespace leak_test_project.ViewModels
});
// 자동 시험 프로세스
_testProcess = new TestProcessService(_dioBoard, _leftSensor, _rightSensor, _sentinelService);
_testProcess = new TestProcessService(_dioBoard, _leftSensor, _rightSensor, _sentinelService, App.LoginResult);
_testProcess.ProgressChanged += (s, e) => _dispatcher.Invoke(() =>
{
@ -332,11 +335,7 @@ namespace leak_test_project.ViewModels
if (e.TestIndex == 0) LeftError = msg;
else RightError = msg;
MessageBox.Show(
"프로그램 스팩과 센서의 스팩이 서로 맞지 않습니다.",
"SPEC 교차 검증 경고",
MessageBoxButton.OK,
MessageBoxImage.Warning);
_dialogService.ShowWarning("프로그램 스팩과 센서의 스팩이 서로 맞지 않습니다.", "SPEC 교차 검증 경고");
}
});
@ -425,7 +424,6 @@ namespace leak_test_project.ViewModels
{
ConfigManager.ConfigChanged -= OnConfigChanged;
CleanupAll();
_dioBoard?.Dispose();
}
}
}

142
leak_test_project/Views/DataView.xaml

@ -88,50 +88,86 @@
<TextBlock Text="INSPECT DATA LIST (측정 기록 조회)" Foreground="White" FontWeight="SemiBold" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="18"/>
</Border>
<!-- Filter Area (Centered) -->
<StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,10">
<!-- 기간 -->
<StackPanel Width="320">
<Border Style="{StaticResource FilterHeaderBorder}">
<TextBlock Text="조회 기간" Style="{StaticResource FilterHeaderLabel}"/>
</Border>
<Border Style="{StaticResource FilterContentBorder}">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center">
<DatePicker SelectedDate="{Binding StartDate}" Width="130" FontSize="13" VerticalAlignment="Center"/>
<TextBlock Text="~" VerticalAlignment="Center" Margin="8,0" FontSize="16" FontWeight="Bold" Foreground="#7F8C8D"/>
<DatePicker SelectedDate="{Binding EndDate}" Width="130" FontSize="13" VerticalAlignment="Center"/>
</StackPanel>
</Border>
<!-- Filter Area (2-Row Layout) -->
<StackPanel Grid.Row="1" Orientation="Vertical" HorizontalAlignment="Center" Margin="0,10">
<!-- Row 1: Common Basic Filters -->
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,0,0,8">
<!-- 기간 -->
<StackPanel Width="320">
<Border Style="{StaticResource FilterHeaderBorder}">
<TextBlock Text="조회 기간" Style="{StaticResource FilterHeaderLabel}"/>
</Border>
<Border Style="{StaticResource FilterContentBorder}">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center">
<DatePicker SelectedDate="{Binding StartDate}" Width="130" FontSize="13" VerticalAlignment="Center"/>
<TextBlock Text="~" VerticalAlignment="Center" Margin="8,0" FontSize="16" FontWeight="Bold" Foreground="#7F8C8D"/>
<DatePicker SelectedDate="{Binding EndDate}" Width="130" FontSize="13" VerticalAlignment="Center"/>
</StackPanel>
</Border>
</StackPanel>
<!-- 판정 -->
<StackPanel Width="150" Margin="10,0,0,0">
<Border Style="{StaticResource FilterHeaderBorder}">
<TextBlock Text="판정 결과" Style="{StaticResource FilterHeaderLabel}"/>
</Border>
<Border Style="{StaticResource FilterContentBorder}">
<ComboBox SelectedIndex="{Binding JudgmentIndex}" Width="130" Height="30" VerticalAlignment="Center" FontSize="13">
<ComboBoxItem Content="[전체]"/>
<ComboBoxItem Content="OK"/>
<ComboBoxItem Content="NG"/>
</ComboBox>
</Border>
</StackPanel>
</StackPanel>
<!-- 판정 -->
<StackPanel Width="120" Margin="10,0,0,0">
<Border Style="{StaticResource FilterHeaderBorder}">
<TextBlock Text="판정 결과" Style="{StaticResource FilterHeaderLabel}"/>
</Border>
<Border Style="{StaticResource FilterContentBorder}">
<ComboBox SelectedIndex="{Binding JudgmentIndex}" Width="100" Height="30" VerticalAlignment="Center" FontSize="13">
<ComboBoxItem Content="[전체]"/>
<ComboBoxItem Content="OK"/>
<ComboBoxItem Content="NG"/>
</ComboBox>
</Border>
</StackPanel>
<!-- ID 검색 -->
<StackPanel Width="280" Margin="10,0,0,0">
<Border Style="{StaticResource FilterHeaderBorder}">
<TextBlock Text="ID 검색" Style="{StaticResource FilterHeaderLabel}"/>
</Border>
<Border Style="{StaticResource FilterContentBorder}">
<TextBox Text="{Binding IdFilter, UpdateSourceTrigger=PropertyChanged}" Width="250" Height="30" VerticalAlignment="Center" FontSize="14" FontWeight="SemiBold" Padding="5,0"/>
</Border>
</StackPanel>
<!-- Buttons -->
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" Margin="15,0,0,0">
<Button Content="Search" Style="{StaticResource ActionButton}" Command="{Binding SearchCommand}" Background="#D4E6F1" BorderBrush="#2E86C1"/>
<Button Content="CSV" Style="{StaticResource ActionButton}" Command="{Binding SaveCsvCommand}" Background="#D4EFDF" BorderBrush="#27AE60" Click="Button_Click"/>
<!-- Row 2: Detail Database Columns Filters -->
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<!-- ID 검색 -->
<StackPanel Width="170">
<Border Style="{StaticResource FilterHeaderBorder}">
<TextBlock Text="ID 검색" Style="{StaticResource FilterHeaderLabel}"/>
</Border>
<Border Style="{StaticResource FilterContentBorder}">
<TextBox Text="{Binding IdFilter, UpdateSourceTrigger=PropertyChanged}" Width="150" Height="30" VerticalAlignment="Center" FontSize="13" FontWeight="SemiBold" Padding="5,0"/>
</Border>
</StackPanel>
<!-- 작업자 검색 -->
<StackPanel Width="140" Margin="10,0,0,0">
<Border Style="{StaticResource FilterHeaderBorder}">
<TextBlock Text="작업자" Style="{StaticResource FilterHeaderLabel}"/>
</Border>
<Border Style="{StaticResource FilterContentBorder}">
<TextBox Text="{Binding OperatorFilter, UpdateSourceTrigger=PropertyChanged}" Width="120" Height="30" VerticalAlignment="Center" FontSize="13" FontWeight="SemiBold" Padding="5,0"/>
</Border>
</StackPanel>
<!-- Lot No 검색 -->
<StackPanel Width="140" Margin="10,0,0,0">
<Border Style="{StaticResource FilterHeaderBorder}">
<TextBlock Text="Lot No" Style="{StaticResource FilterHeaderLabel}"/>
</Border>
<Border Style="{StaticResource FilterContentBorder}">
<TextBox Text="{Binding LotNoFilter, UpdateSourceTrigger=PropertyChanged}" Width="120" Height="30" VerticalAlignment="Center" FontSize="13" FontWeight="SemiBold" Padding="5,0"/>
</Border>
</StackPanel>
<!-- 모델 검색 -->
<StackPanel Width="140" Margin="10,0,0,0">
<Border Style="{StaticResource FilterHeaderBorder}">
<TextBlock Text="모델" Style="{StaticResource FilterHeaderLabel}"/>
</Border>
<Border Style="{StaticResource FilterContentBorder}">
<TextBox Text="{Binding ModelFilter, UpdateSourceTrigger=PropertyChanged}" Width="120" Height="30" VerticalAlignment="Center" FontSize="13" FontWeight="SemiBold" Padding="5,0"/>
</Border>
</StackPanel>
<!-- Buttons -->
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" Margin="15,0,0,0">
<Button Content="Search" Style="{StaticResource ActionButton}" Command="{Binding SearchCommand}" Background="#D4E6F1" BorderBrush="#2E86C1"/>
<Button Content="CSV" Style="{StaticResource ActionButton}" Command="{Binding SaveCsvCommand}" Background="#D4EFDF" BorderBrush="#27AE60" Click="Button_Click"/>
</StackPanel>
</StackPanel>
</StackPanel>
@ -143,13 +179,22 @@
CanUserSortColumns="True" CanUserReorderColumns="False"
BorderThickness="0" SelectionMode="Extended" SelectionChanged="DataGrid_SelectionChanged">
<DataGrid.Columns>
<DataGridTextColumn Header="날짜" Binding="{Binding InspectDate}" Width="130"/>
<DataGridTextColumn Header="시간" Binding="{Binding InspectTime}" Width="100"/>
<DataGridTextColumn Header="ID" Binding="{Binding ProductId}" Width="250"/>
<DataGridTextColumn Header="좌/우" Binding="{Binding Channel}" Width="70"/>
<DataGridTextColumn Header="검사일자" Binding="{Binding InspectDate}" Width="110"/>
<DataGridTextColumn Header="검사시간" Binding="{Binding InspectTime}" Width="90"/>
<DataGridTextColumn Header="IC SN (제품ID)" Binding="{Binding ProductId}" Width="150"/>
<DataGridTextColumn Header="PCB 바코드" Binding="{Binding PcbBarcode}" Width="150"/>
<DataGridTextColumn Header="메이커" Binding="{Binding Maker}" Width="90"/>
<DataGridTextColumn Header="모델" Binding="{Binding Model}" Width="90"/>
<DataGridTextColumn Header="변형 1" Binding="{Binding Variant1}" Width="90"/>
<DataGridTextColumn Header="변형 2" Binding="{Binding Variant2}" Width="90"/>
<DataGridTextColumn Header="작업자" Binding="{Binding Operator}" Width="90"/>
<DataGridTextColumn Header="작업라인" Binding="{Binding Line}" Width="80"/>
<DataGridTextColumn Header="Lot No" Binding="{Binding LotNo}" Width="95"/>
<DataGridTextColumn Header="지그 번호" Binding="{Binding JigNo}" Width="80"/>
<DataGridTextColumn Header="검사채널" Binding="{Binding Channel}" Width="80"/>
<DataGridTextColumn Header="SPEC UL" Binding="{Binding SpecUL}" Width="80"/>
<DataGridTextColumn Header="SPEC LL" Binding="{Binding SpecLL}" Width="80"/>
<DataGridTextColumn Header="측정값" Binding="{Binding MeasuredValue}" Width="100">
<DataGridTextColumn Header="측정값" Binding="{Binding MeasuredValue}" Width="100">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Right"/>
@ -158,7 +203,7 @@
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="판정" Binding="{Binding Judgment}" Width="80">
<DataGridTextColumn Header="최종판정" Binding="{Binding Judgment}" Width="80">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Center"/>
@ -174,7 +219,6 @@
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="비고" Width="*" MinWidth="300"/>
</DataGrid.Columns>
</DataGrid>
</Border>

8
leak_test_project/Views/StartupLoginWindow.xaml

@ -397,7 +397,6 @@
Style="{StaticResource FieldLabelStyle}"/>
<TextBox x:Name="MakerTextBox"
Grid.Row="1"
Text="MOBI"
Style="{StaticResource InputStyle}"/>
</Grid>
</Border>
@ -415,7 +414,6 @@
Style="{StaticResource FieldLabelStyle}"/>
<TextBox x:Name="ModelTextBox"
Grid.Row="1"
Text="MOBI-EV-01"
Style="{StaticResource InputStyle}"/>
</Grid>
</Border>
@ -433,7 +431,6 @@
Style="{StaticResource FieldLabelStyle}"/>
<TextBox x:Name="Variant1TextBox"
Grid.Row="1"
Text="V1-01"
Style="{StaticResource InputStyle}"/>
</Grid>
</Border>
@ -451,7 +448,6 @@
Style="{StaticResource FieldLabelStyle}"/>
<TextBox x:Name="Variant2TextBox"
Grid.Row="1"
Text="V2-01"
Style="{StaticResource InputStyle}"/>
</Grid>
</Border>
@ -480,7 +476,6 @@
Style="{StaticResource FieldLabelStyle}"/>
<TextBox x:Name="OperatorTextBox"
Grid.Row="1"
Text="test"
Style="{StaticResource InputStyle}"/>
</Grid>
</Border>
@ -527,7 +522,6 @@
Style="{StaticResource FieldLabelStyle}"/>
<TextBox x:Name="LineNoTextBox"
Grid.Row="1"
Text="LINE-3"
Style="{StaticResource InputStyle}"/>
</Grid>
</Border>
@ -544,7 +538,6 @@
Style="{StaticResource FieldLabelStyle}"/>
<TextBox x:Name="LotNoTextBox"
Grid.Row="1"
Text="LOT-20260605"
Style="{StaticResource InputStyle}"/>
</Grid>
</Border>
@ -561,7 +554,6 @@
Style="{StaticResource FieldLabelStyle}"/>
<TextBox x:Name="JigNoTextBox"
Grid.Row="1"
Text="JIG-12"
Style="{StaticResource InputStyle}"/>
</Grid>
</Border>

2
leak_test_project/Views/StartupLoginWindow.xaml.cs

@ -328,7 +328,7 @@ namespace Housing.Login
return Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"Housing",
"LoginHistory.json");
"LoginHistory.xml");
}
}
}

24
leak_test_project/bin/Debug/Database.ini

@ -0,0 +1,24 @@
; =====================================================================
; Mobi Leak Test System - 데이터베이스 및 로그인 설정 파일 (Database.ini)
; =====================================================================
; [데이터베이스 접속 상세 설정]
; Db_Server : 중앙 MSSQL 데이터베이스 서버의 IP 주소 또는 호스트명
; Db_Name : 검사 데이터를 저장할 대상 데이터베이스 이름
; Db_User : 데이터베이스 접속 계정 ID (예: sa)
; Db_Password: 데이터베이스 접속 계정의 비밀번호
Db_Server=192.168.0.20
Db_Name=NE1aW_PT_SENSOR
Db_User=sa
Db_Password=sa1234
[Login]
; Procedure: 로그인 검증에 사용할 DB 저장 프로시저명입니다. (공란일 경우 기본 테이블 조회 쿼리 작동)
Procedure=dbo.CheckOperator
; ProcessName: 로그인 검증 시 전달할 공정 이름입니다.
ProcessName=EOL
; OfflinePreview: 오프라인 모드 활성화 여부 (true = DB 연결 없이 로그인 통과, false = 실제 DB 인증)
OfflinePreview=false

BIN
leak_test_project/bin/Debug/app.publish/leak_test_project.exe

Binary file not shown.

2
leak_test_project/bin/Debug/leak_test_project.application

@ -14,7 +14,7 @@
<dsig:Transform Algorithm="urn:schemas-microsoft-com:HashTransforms.Identity" />
</dsig:Transforms>
<dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha256" />
<dsig:DigestValue>l3ZX9B+uam5XK1m0sy2nwxnA38smbnh5KmVt6jFffEY=</dsig:DigestValue>
<dsig:DigestValue>oBxYPjguQtv1EuRHadukWOmGPPqWPod5v+TZToduxNk=</dsig:DigestValue>
</hash>
</dependentAssembly>
</dependency>

BIN
leak_test_project/bin/Debug/leak_test_project.exe

Binary file not shown.

4
leak_test_project/bin/Debug/leak_test_project.exe.manifest

@ -42,14 +42,14 @@
</dependentAssembly>
</dependency>
<dependency>
<dependentAssembly dependencyType="install" allowDelayedBinding="true" codebase="leak_test_project.exe" size="219136">
<dependentAssembly dependencyType="install" allowDelayedBinding="true" codebase="leak_test_project.exe" size="237568">
<assemblyIdentity name="leak_test_project" version="1.0.0.0" language="neutral" processorArchitecture="msil" />
<hash>
<dsig:Transforms>
<dsig:Transform Algorithm="urn:schemas-microsoft-com:HashTransforms.Identity" />
</dsig:Transforms>
<dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha256" />
<dsig:DigestValue>K7q6P0n/e7IBxUL2PiojluRdc1gfMK7T7k4cyvQZhss=</dsig:DigestValue>
<dsig:DigestValue>NEvi6EY1Ysg2m7d04Ix80K81U0DF+ZZKnv+9QRT3SMc=</dsig:DigestValue>
</hash>
</dependentAssembly>
</dependency>

BIN
leak_test_project/bin/Debug/leak_test_project.pdb

Binary file not shown.

24
leak_test_project/bin/Release/Database.ini

@ -0,0 +1,24 @@
; =====================================================================
; Mobi Leak Test System - 데이터베이스 및 로그인 설정 파일 (Database.ini)
; =====================================================================
; [데이터베이스 접속 상세 설정]
; Db_Server : 중앙 MSSQL 데이터베이스 서버의 IP 주소 또는 호스트명
; Db_Name : 검사 데이터를 저장할 대상 데이터베이스 이름
; Db_User : 데이터베이스 접속 계정 ID (예: sa)
; Db_Password: 데이터베이스 접속 계정의 비밀번호
Db_Server=192.168.0.20
Db_Name=NE1aW_PT_SENSOR
Db_User=sa
Db_Password=sa1234
[Login]
; Procedure: 로그인 검증에 사용할 DB 저장 프로시저명입니다. (공란일 경우 기본 테이블 조회 쿼리 작동)
Procedure=dbo.CheckOperator
; ProcessName: 로그인 검증 시 전달할 공정 이름입니다.
ProcessName=EOL
; OfflinePreview: 오프라인 모드 활성화 여부 (true = DB 연결 없이 로그인 통과, false = 실제 DB 인증)
OfflinePreview=false

2
leak_test_project/bin/Release/Logs/2026-06-18_system.log

@ -0,0 +1,2 @@
[11:13:29.052] [ERROR] [Board4251] Failed to receive response after 3 retries: x00o
[11:13:30.394] [ERROR] [Board4251] Failed to receive response after 3 retries: x00c_001101:owt28006727ea97c7801

8
leak_test_project/bin/Release/Logs/2026-06-25_system.log

@ -0,0 +1,8 @@
[10:17:36.444] [ERROR] [Board4251] Failed to receive response after 3 retries: x00o
[10:17:37.692] [ERROR] [Board4251] Failed to receive response after 3 retries: x00c_001101:owt28006727ea97c7801
[10:17:39.458] [ERROR] [Board4251] Failed to receive response after 3 retries: x00o
[10:17:40.707] [ERROR] [Board4251] Failed to receive response after 3 retries: x00c_002101:owt28006727ea97c7801
[10:17:43.159] [ERROR] [Board4251] Failed to receive response after 3 retries: x00o
[10:17:44.414] [ERROR] [Board4251] Failed to receive response after 3 retries: x00c_002101:owt28006727ea97c7801
[10:17:46.175] [ERROR] [Board4251] Failed to receive response after 3 retries: x00o
[10:17:47.441] [ERROR] [Board4251] Failed to receive response after 3 retries: x00c_001101:owt28006727ea97c7801

BIN
leak_test_project/bin/Release/app.publish/leak_test_project.exe

Binary file not shown.

3
leak_test_project/bin/Release/config.xml

@ -6,6 +6,5 @@
<Board4251BaudRate>115200</Board4251BaudRate>
<Board4251Timeout>5000</Board4251Timeout>
<SpecUL>0.1</SpecUL>
<SpecLL>-0.2</SpecLL>
<DbConnectionString>Server=192.168.0.20;Database=NE1aW_PT_SENSOR;User Id=sa;Password=sa1234;</DbConnectionString>
<SpecLL>-0.1</SpecLL>
</AppConfig>

2
leak_test_project/bin/Release/leak_test_project.application

@ -14,7 +14,7 @@
<dsig:Transform Algorithm="urn:schemas-microsoft-com:HashTransforms.Identity" />
</dsig:Transforms>
<dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha256" />
<dsig:DigestValue>qNbiAc1kf0eiU+DGDJlSHaQrr7D5MVNH8vmLeW563VI=</dsig:DigestValue>
<dsig:DigestValue>n7VmvcxpOf8Y+LRFjH5TmdoI+vXXgYov1j6fqW/Nbxs=</dsig:DigestValue>
</hash>
</dependentAssembly>
</dependency>

BIN
leak_test_project/bin/Release/leak_test_project.exe

Binary file not shown.

4
leak_test_project/bin/Release/leak_test_project.exe.manifest

@ -42,14 +42,14 @@
</dependentAssembly>
</dependency>
<dependency>
<dependentAssembly dependencyType="install" allowDelayedBinding="true" codebase="leak_test_project.exe" size="190464">
<dependentAssembly dependencyType="install" allowDelayedBinding="true" codebase="leak_test_project.exe" size="210432">
<assemblyIdentity name="leak_test_project" version="1.0.0.0" language="neutral" processorArchitecture="msil" />
<hash>
<dsig:Transforms>
<dsig:Transform Algorithm="urn:schemas-microsoft-com:HashTransforms.Identity" />
</dsig:Transforms>
<dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha256" />
<dsig:DigestValue>oU1Vb+qDbIJPdoRRPncFp5ECBdIWWwNNrCazZR4lxdI=</dsig:DigestValue>
<dsig:DigestValue>bIiBUDqHxOgJSZaDgyYguxKJoGO3oFxaqRp+FVa4a/A=</dsig:DigestValue>
</hash>
</dependentAssembly>
</dependency>

BIN
leak_test_project/bin/Release/leak_test_project.pdb

Binary file not shown.

12
leak_test_project/leak_test_project.csproj

@ -94,7 +94,6 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\DataView.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
@ -129,15 +128,18 @@
<Compile Include="Services\DioBoardFactory.cs" />
<Compile Include="Services\TestProcessService.cs" />
<Compile Include="Services\EolDatabaseService.cs" />
<Compile Include="Services\LoginServicesStub.cs" />
<Compile Include="Services\DatabaseSettings.cs" />
<Compile Include="Services\LoginSettings.cs" />
<Compile Include="Services\LoginHistoryStore.cs" />
<Compile Include="Services\LoginAccountRepository.cs" />
<Compile Include="Utils\SentinelParser.cs" />
<Compile Include="Utils\LogUtils.cs" />
<Compile Include="Utils\ConfigHelper.cs" />
<Compile Include="Utils\DialogService.cs" />
<Compile Include="Models\ProjectModels.cs" />
<Compile Include="Views\HomeView.xaml.cs">
<DependentUpon>HomeView.xaml</DependentUpon>
</Compile>
<Compile Include="Views\DataView.xaml.cs">
<DependentUpon>DataView.xaml</DependentUpon>
</Compile>
@ -154,7 +156,6 @@
<Compile Include="ViewModels\Core\RelayCommand.cs" />
<Compile Include="ViewModels\DataViewModel.cs" />
<Compile Include="ViewModels\HomeViewModel.cs" />
<Compile Include="ViewModels\MainViewModel.cs" />
<Compile Include="ViewModels\ParametersViewModel.cs" />
<Compile Include="MainWindow.xaml.cs">
@ -189,6 +190,9 @@
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
<None Include="Database.ini">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<BootstrapperPackage Include=".NETFramework,Version=v4.7.2">

BIN
leak_test_project/obj/Debug/App.baml

Binary file not shown.

BIN
leak_test_project/obj/Debug/MainWindow.baml

Binary file not shown.

BIN
leak_test_project/obj/Debug/Views/CommunicationWindow.baml

Binary file not shown.

BIN
leak_test_project/obj/Debug/Views/DataView.baml

Binary file not shown.

6
leak_test_project/obj/Debug/Views/DataView.g.cs

@ -1,4 +1,4 @@
#pragma checksum "..\..\..\Views\DataView.xaml" "{8829d00f-11b8-4213-878b-770e8597ac16}" "CF3D9FA0B3A93AD2D6C065F35DF39EBFC783D422DB5170F1B6A1280CD7D48859"
#pragma checksum "..\..\..\Views\DataView.xaml" "{8829d00f-11b8-4213-878b-770e8597ac16}" "E806D581E87844448860858BA46A605448A6BF6EEC5FF26FF0D442F616E4D3C7"
//------------------------------------------------------------------------------
// <auto-generated>
// 이 코드는 도구를 사용하여 생성되었습니다.
@ -71,7 +71,7 @@ namespace leak_test_project.Views {
{
case 1:
#line 134 "..\..\..\Views\DataView.xaml"
#line 169 "..\..\..\Views\DataView.xaml"
((System.Windows.Controls.Button)(target)).Click += new System.Windows.RoutedEventHandler(this.Button_Click);
#line default
@ -79,7 +79,7 @@ namespace leak_test_project.Views {
return;
case 2:
#line 144 "..\..\..\Views\DataView.xaml"
#line 180 "..\..\..\Views\DataView.xaml"
((System.Windows.Controls.DataGrid)(target)).SelectionChanged += new System.Windows.Controls.SelectionChangedEventHandler(this.DataGrid_SelectionChanged);
#line default

BIN
leak_test_project/obj/Debug/Views/HomeView.baml

Binary file not shown.

BIN
leak_test_project/obj/Debug/Views/ParametersWindow.baml

Binary file not shown.

BIN
leak_test_project/obj/Debug/Views/StartupLoginWindow.baml

Binary file not shown.

22
leak_test_project/obj/Debug/Views/StartupLoginWindow.g.cs

@ -1,4 +1,4 @@
#pragma checksum "..\..\..\Views\StartupLoginWindow.xaml" "{8829d00f-11b8-4213-878b-770e8597ac16}" "10236E62B98A3B25C61EAC333EF6CCD078A69767002979415693621924B5E4AC"
#pragma checksum "..\..\..\Views\StartupLoginWindow.xaml" "{8829d00f-11b8-4213-878b-770e8597ac16}" "E10917AAAC5C59A6940A4937C1165BBFED3D9CE02854042592965F3FCDF2EE1F"
//------------------------------------------------------------------------------
// <auto-generated>
// 이 코드는 도구를 사용하여 생성되었습니다.
@ -48,7 +48,7 @@ namespace Housing.Login {
#line hidden
#line 416 "..\..\..\Views\StartupLoginWindow.xaml"
#line 415 "..\..\..\Views\StartupLoginWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBox ModelTextBox;
@ -56,7 +56,7 @@ namespace Housing.Login {
#line hidden
#line 434 "..\..\..\Views\StartupLoginWindow.xaml"
#line 432 "..\..\..\Views\StartupLoginWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBox Variant1TextBox;
@ -64,7 +64,7 @@ namespace Housing.Login {
#line hidden
#line 452 "..\..\..\Views\StartupLoginWindow.xaml"
#line 449 "..\..\..\Views\StartupLoginWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBox Variant2TextBox;
@ -72,7 +72,7 @@ namespace Housing.Login {
#line hidden
#line 481 "..\..\..\Views\StartupLoginWindow.xaml"
#line 477 "..\..\..\Views\StartupLoginWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBox OperatorTextBox;
@ -80,7 +80,7 @@ namespace Housing.Login {
#line hidden
#line 498 "..\..\..\Views\StartupLoginWindow.xaml"
#line 493 "..\..\..\Views\StartupLoginWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.PasswordBox PasswordBox;
@ -88,7 +88,7 @@ namespace Housing.Login {
#line hidden
#line 528 "..\..\..\Views\StartupLoginWindow.xaml"
#line 523 "..\..\..\Views\StartupLoginWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBox LineNoTextBox;
@ -96,7 +96,7 @@ namespace Housing.Login {
#line hidden
#line 545 "..\..\..\Views\StartupLoginWindow.xaml"
#line 539 "..\..\..\Views\StartupLoginWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBox LotNoTextBox;
@ -104,7 +104,7 @@ namespace Housing.Login {
#line hidden
#line 562 "..\..\..\Views\StartupLoginWindow.xaml"
#line 555 "..\..\..\Views\StartupLoginWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBox JigNoTextBox;
@ -112,7 +112,7 @@ namespace Housing.Login {
#line hidden
#line 570 "..\..\..\Views\StartupLoginWindow.xaml"
#line 562 "..\..\..\Views\StartupLoginWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.Button LoginButton;
@ -179,7 +179,7 @@ namespace Housing.Login {
case 10:
this.LoginButton = ((System.Windows.Controls.Button)(target));
#line 574 "..\..\..\Views\StartupLoginWindow.xaml"
#line 566 "..\..\..\Views\StartupLoginWindow.xaml"
this.LoginButton.Click += new System.Windows.RoutedEventHandler(this.LogInButton_Click);
#line default

2
leak_test_project/obj/Debug/leak_test_project.application

@ -14,7 +14,7 @@
<dsig:Transform Algorithm="urn:schemas-microsoft-com:HashTransforms.Identity" />
</dsig:Transforms>
<dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha256" />
<dsig:DigestValue>l3ZX9B+uam5XK1m0sy2nwxnA38smbnh5KmVt6jFffEY=</dsig:DigestValue>
<dsig:DigestValue>oBxYPjguQtv1EuRHadukWOmGPPqWPod5v+TZToduxNk=</dsig:DigestValue>
</hash>
</dependentAssembly>
</dependency>

2
leak_test_project/obj/Debug/leak_test_project.csproj.CoreCompileInputs.cache

@ -1 +1 @@
f74477eeb98a106556f1b65e440f8cfa8362ac43ae60c03174d4f2bb878b7a02
628713ffbc5f55cd805898b1d31f7980abb2a5a8c3c1aad9e2f31b3c27ae4897

1
leak_test_project/obj/Debug/leak_test_project.csproj.FileListAbsolute.txt

@ -30,3 +30,4 @@ C:\Users\COMPUTER1\Desktop\mobi\leak_test_project\leak_test_project\obj\Debug\le
C:\Users\COMPUTER1\Desktop\mobi\leak_test_project\leak_test_project\obj\Debug\leak_test_project.application
C:\Users\COMPUTER1\Desktop\mobi\leak_test_project\leak_test_project\obj\Debug\leak_test_project.exe
C:\Users\COMPUTER1\Desktop\mobi\leak_test_project\leak_test_project\obj\Debug\leak_test_project.pdb
C:\Users\COMPUTER1\Desktop\mobi\leak_test_project\leak_test_project\bin\Debug\Database.ini

BIN
leak_test_project/obj/Debug/leak_test_project.exe

Binary file not shown.

4
leak_test_project/obj/Debug/leak_test_project.exe.manifest

@ -42,14 +42,14 @@
</dependentAssembly>
</dependency>
<dependency>
<dependentAssembly dependencyType="install" allowDelayedBinding="true" codebase="leak_test_project.exe" size="219136">
<dependentAssembly dependencyType="install" allowDelayedBinding="true" codebase="leak_test_project.exe" size="237568">
<assemblyIdentity name="leak_test_project" version="1.0.0.0" language="neutral" processorArchitecture="msil" />
<hash>
<dsig:Transforms>
<dsig:Transform Algorithm="urn:schemas-microsoft-com:HashTransforms.Identity" />
</dsig:Transforms>
<dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha256" />
<dsig:DigestValue>K7q6P0n/e7IBxUL2PiojluRdc1gfMK7T7k4cyvQZhss=</dsig:DigestValue>
<dsig:DigestValue>NEvi6EY1Ysg2m7d04Ix80K81U0DF+ZZKnv+9QRT3SMc=</dsig:DigestValue>
</hash>
</dependentAssembly>
</dependency>

BIN
leak_test_project/obj/Debug/leak_test_project.g.resources

Binary file not shown.

BIN
leak_test_project/obj/Debug/leak_test_project.pdb

Binary file not shown.

8
leak_test_project/obj/Debug/leak_test_project_MarkupCompile.cache

@ -10,10 +10,10 @@ none
false
DEBUG;TRACE
C:\Users\COMPUTER1\Desktop\mobi\leak_test_project\leak_test_project\App.xaml
6681427662
1-698605034
311518714309
14-767506772
6-1954804774
1-1942997596
35231504871
14188644636
Views\HomeView.xaml;Views\DataView.xaml;Views\ParametersWindow.xaml;Views\CommunicationWindow.xaml;Views\StartupLoginWindow.xaml;MainWindow.xaml;
False

BIN
leak_test_project/obj/Release/App.baml

Binary file not shown.

BIN
leak_test_project/obj/Release/DesignTimeResolveAssemblyReferencesInput.cache

Binary file not shown.

62
leak_test_project/obj/Release/GeneratedInternalTypeHelper.g.cs

@ -1,62 +1,2 @@
//------------------------------------------------------------------------------
// <auto-generated>
// 이 코드는 도구를 사용하여 생성되었습니다.
// 런타임 버전:4.0.30319.42000
//
// 파일 내용을 변경하면 잘못된 동작이 발생할 수 있으며, 코드를 다시 생성하면
// 이러한 변경 내용이 손실됩니다.
// </auto-generated>
//------------------------------------------------------------------------------
namespace XamlGeneratedNamespace {
/// <summary>
/// GeneratedInternalTypeHelper
/// </summary>
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "4.0.0.0")]
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public sealed class GeneratedInternalTypeHelper : System.Windows.Markup.InternalTypeHelper {
/// <summary>
/// CreateInstance
/// </summary>
protected override object CreateInstance(System.Type type, System.Globalization.CultureInfo culture) {
return System.Activator.CreateInstance(type, ((System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic)
| (System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.CreateInstance)), null, null, culture);
}
/// <summary>
/// GetPropertyValue
/// </summary>
protected override object GetPropertyValue(System.Reflection.PropertyInfo propertyInfo, object target, System.Globalization.CultureInfo culture) {
return propertyInfo.GetValue(target, System.Reflection.BindingFlags.Default, null, null, culture);
}
/// <summary>
/// SetPropertyValue
/// </summary>
protected override void SetPropertyValue(System.Reflection.PropertyInfo propertyInfo, object target, object value, System.Globalization.CultureInfo culture) {
propertyInfo.SetValue(target, value, System.Reflection.BindingFlags.Default, null, null, culture);
}
/// <summary>
/// CreateDelegate
/// </summary>
protected override System.Delegate CreateDelegate(System.Type delegateType, object target, string handler) {
return ((System.Delegate)(target.GetType().InvokeMember("_CreateDelegate", (System.Reflection.BindingFlags.InvokeMethod
| (System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)), null, target, new object[] {
delegateType,
handler}, null)));
}
/// <summary>
/// AddEventHandler
/// </summary>
protected override void AddEventHandler(System.Reflection.EventInfo eventInfo, object target, System.Delegate handler) {
eventInfo.AddEventHandler(target, handler);
}
}
}


BIN
leak_test_project/obj/Release/MainWindow.baml

Binary file not shown.

BIN
leak_test_project/obj/Release/Views/CommunicationWindow.baml

Binary file not shown.

BIN
leak_test_project/obj/Release/Views/DataView.baml

Binary file not shown.

6
leak_test_project/obj/Release/Views/DataView.g.cs

@ -1,4 +1,4 @@
#pragma checksum "..\..\..\Views\DataView.xaml" "{8829d00f-11b8-4213-878b-770e8597ac16}" "CF3D9FA0B3A93AD2D6C065F35DF39EBFC783D422DB5170F1B6A1280CD7D48859"
#pragma checksum "..\..\..\Views\DataView.xaml" "{8829d00f-11b8-4213-878b-770e8597ac16}" "E806D581E87844448860858BA46A605448A6BF6EEC5FF26FF0D442F616E4D3C7"
//------------------------------------------------------------------------------
// <auto-generated>
// 이 코드는 도구를 사용하여 생성되었습니다.
@ -71,7 +71,7 @@ namespace leak_test_project.Views {
{
case 1:
#line 134 "..\..\..\Views\DataView.xaml"
#line 169 "..\..\..\Views\DataView.xaml"
((System.Windows.Controls.Button)(target)).Click += new System.Windows.RoutedEventHandler(this.Button_Click);
#line default
@ -79,7 +79,7 @@ namespace leak_test_project.Views {
return;
case 2:
#line 144 "..\..\..\Views\DataView.xaml"
#line 180 "..\..\..\Views\DataView.xaml"
((System.Windows.Controls.DataGrid)(target)).SelectionChanged += new System.Windows.Controls.SelectionChangedEventHandler(this.DataGrid_SelectionChanged);
#line default

6
leak_test_project/obj/Release/Views/DataView.g.i.cs

@ -1,4 +1,4 @@
#pragma checksum "..\..\..\Views\DataView.xaml" "{8829d00f-11b8-4213-878b-770e8597ac16}" "CF3D9FA0B3A93AD2D6C065F35DF39EBFC783D422DB5170F1B6A1280CD7D48859"
#pragma checksum "..\..\..\Views\DataView.xaml" "{8829d00f-11b8-4213-878b-770e8597ac16}" "E806D581E87844448860858BA46A605448A6BF6EEC5FF26FF0D442F616E4D3C7"
//------------------------------------------------------------------------------
// <auto-generated>
// 이 코드는 도구를 사용하여 생성되었습니다.
@ -71,7 +71,7 @@ namespace leak_test_project.Views {
{
case 1:
#line 134 "..\..\..\Views\DataView.xaml"
#line 169 "..\..\..\Views\DataView.xaml"
((System.Windows.Controls.Button)(target)).Click += new System.Windows.RoutedEventHandler(this.Button_Click);
#line default
@ -79,7 +79,7 @@ namespace leak_test_project.Views {
return;
case 2:
#line 144 "..\..\..\Views\DataView.xaml"
#line 180 "..\..\..\Views\DataView.xaml"
((System.Windows.Controls.DataGrid)(target)).SelectionChanged += new System.Windows.Controls.SelectionChangedEventHandler(this.DataGrid_SelectionChanged);
#line default

BIN
leak_test_project/obj/Release/Views/HomeView.baml

Binary file not shown.

BIN
leak_test_project/obj/Release/Views/ParametersWindow.baml

Binary file not shown.

BIN
leak_test_project/obj/Release/Views/StartupLoginWindow.baml

Binary file not shown.

22
leak_test_project/obj/Release/Views/StartupLoginWindow.g.cs

@ -1,4 +1,4 @@
#pragma checksum "..\..\..\Views\StartupLoginWindow.xaml" "{8829d00f-11b8-4213-878b-770e8597ac16}" "10236E62B98A3B25C61EAC333EF6CCD078A69767002979415693621924B5E4AC"
#pragma checksum "..\..\..\Views\StartupLoginWindow.xaml" "{8829d00f-11b8-4213-878b-770e8597ac16}" "E10917AAAC5C59A6940A4937C1165BBFED3D9CE02854042592965F3FCDF2EE1F"
//------------------------------------------------------------------------------
// <auto-generated>
// 이 코드는 도구를 사용하여 생성되었습니다.
@ -48,7 +48,7 @@ namespace Housing.Login {
#line hidden
#line 416 "..\..\..\Views\StartupLoginWindow.xaml"
#line 415 "..\..\..\Views\StartupLoginWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBox ModelTextBox;
@ -56,7 +56,7 @@ namespace Housing.Login {
#line hidden
#line 434 "..\..\..\Views\StartupLoginWindow.xaml"
#line 432 "..\..\..\Views\StartupLoginWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBox Variant1TextBox;
@ -64,7 +64,7 @@ namespace Housing.Login {
#line hidden
#line 452 "..\..\..\Views\StartupLoginWindow.xaml"
#line 449 "..\..\..\Views\StartupLoginWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBox Variant2TextBox;
@ -72,7 +72,7 @@ namespace Housing.Login {
#line hidden
#line 481 "..\..\..\Views\StartupLoginWindow.xaml"
#line 477 "..\..\..\Views\StartupLoginWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBox OperatorTextBox;
@ -80,7 +80,7 @@ namespace Housing.Login {
#line hidden
#line 498 "..\..\..\Views\StartupLoginWindow.xaml"
#line 493 "..\..\..\Views\StartupLoginWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.PasswordBox PasswordBox;
@ -88,7 +88,7 @@ namespace Housing.Login {
#line hidden
#line 528 "..\..\..\Views\StartupLoginWindow.xaml"
#line 523 "..\..\..\Views\StartupLoginWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBox LineNoTextBox;
@ -96,7 +96,7 @@ namespace Housing.Login {
#line hidden
#line 545 "..\..\..\Views\StartupLoginWindow.xaml"
#line 539 "..\..\..\Views\StartupLoginWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBox LotNoTextBox;
@ -104,7 +104,7 @@ namespace Housing.Login {
#line hidden
#line 562 "..\..\..\Views\StartupLoginWindow.xaml"
#line 555 "..\..\..\Views\StartupLoginWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBox JigNoTextBox;
@ -112,7 +112,7 @@ namespace Housing.Login {
#line hidden
#line 570 "..\..\..\Views\StartupLoginWindow.xaml"
#line 562 "..\..\..\Views\StartupLoginWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.Button LoginButton;
@ -179,7 +179,7 @@ namespace Housing.Login {
case 10:
this.LoginButton = ((System.Windows.Controls.Button)(target));
#line 574 "..\..\..\Views\StartupLoginWindow.xaml"
#line 566 "..\..\..\Views\StartupLoginWindow.xaml"
this.LoginButton.Click += new System.Windows.RoutedEventHandler(this.LogInButton_Click);
#line default

22
leak_test_project/obj/Release/Views/StartupLoginWindow.g.i.cs

@ -1,4 +1,4 @@
#pragma checksum "..\..\..\Views\StartupLoginWindow.xaml" "{8829d00f-11b8-4213-878b-770e8597ac16}" "10236E62B98A3B25C61EAC333EF6CCD078A69767002979415693621924B5E4AC"
#pragma checksum "..\..\..\Views\StartupLoginWindow.xaml" "{8829d00f-11b8-4213-878b-770e8597ac16}" "E10917AAAC5C59A6940A4937C1165BBFED3D9CE02854042592965F3FCDF2EE1F"
//------------------------------------------------------------------------------
// <auto-generated>
// 이 코드는 도구를 사용하여 생성되었습니다.
@ -48,7 +48,7 @@ namespace Housing.Login {
#line hidden
#line 416 "..\..\..\Views\StartupLoginWindow.xaml"
#line 415 "..\..\..\Views\StartupLoginWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBox ModelTextBox;
@ -56,7 +56,7 @@ namespace Housing.Login {
#line hidden
#line 434 "..\..\..\Views\StartupLoginWindow.xaml"
#line 432 "..\..\..\Views\StartupLoginWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBox Variant1TextBox;
@ -64,7 +64,7 @@ namespace Housing.Login {
#line hidden
#line 452 "..\..\..\Views\StartupLoginWindow.xaml"
#line 449 "..\..\..\Views\StartupLoginWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBox Variant2TextBox;
@ -72,7 +72,7 @@ namespace Housing.Login {
#line hidden
#line 481 "..\..\..\Views\StartupLoginWindow.xaml"
#line 477 "..\..\..\Views\StartupLoginWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBox OperatorTextBox;
@ -80,7 +80,7 @@ namespace Housing.Login {
#line hidden
#line 498 "..\..\..\Views\StartupLoginWindow.xaml"
#line 493 "..\..\..\Views\StartupLoginWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.PasswordBox PasswordBox;
@ -88,7 +88,7 @@ namespace Housing.Login {
#line hidden
#line 528 "..\..\..\Views\StartupLoginWindow.xaml"
#line 523 "..\..\..\Views\StartupLoginWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBox LineNoTextBox;
@ -96,7 +96,7 @@ namespace Housing.Login {
#line hidden
#line 545 "..\..\..\Views\StartupLoginWindow.xaml"
#line 539 "..\..\..\Views\StartupLoginWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBox LotNoTextBox;
@ -104,7 +104,7 @@ namespace Housing.Login {
#line hidden
#line 562 "..\..\..\Views\StartupLoginWindow.xaml"
#line 555 "..\..\..\Views\StartupLoginWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBox JigNoTextBox;
@ -112,7 +112,7 @@ namespace Housing.Login {
#line hidden
#line 570 "..\..\..\Views\StartupLoginWindow.xaml"
#line 562 "..\..\..\Views\StartupLoginWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.Button LoginButton;
@ -179,7 +179,7 @@ namespace Housing.Login {
case 10:
this.LoginButton = ((System.Windows.Controls.Button)(target));
#line 574 "..\..\..\Views\StartupLoginWindow.xaml"
#line 566 "..\..\..\Views\StartupLoginWindow.xaml"
this.LoginButton.Click += new System.Windows.RoutedEventHandler(this.LogInButton_Click);
#line default

2
leak_test_project/obj/Release/leak_test_project.application

@ -14,7 +14,7 @@
<dsig:Transform Algorithm="urn:schemas-microsoft-com:HashTransforms.Identity" />
</dsig:Transforms>
<dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha256" />
<dsig:DigestValue>qNbiAc1kf0eiU+DGDJlSHaQrr7D5MVNH8vmLeW563VI=</dsig:DigestValue>
<dsig:DigestValue>n7VmvcxpOf8Y+LRFjH5TmdoI+vXXgYov1j6fqW/Nbxs=</dsig:DigestValue>
</hash>
</dependentAssembly>
</dependency>

BIN
leak_test_project/obj/Release/leak_test_project.csproj.AssemblyReference.cache

Binary file not shown.

2
leak_test_project/obj/Release/leak_test_project.csproj.CoreCompileInputs.cache

@ -1 +1 @@
cbc4c65e0c5c8abdc171ee59e1f11d54d6b7bd62192c87410037a9efab12cf46
8765edc9fc5fcfe1db8d41cec7744abfac5abd652e5521541b6bb33dc8fa7af8

1
leak_test_project/obj/Release/leak_test_project.csproj.FileListAbsolute.txt

@ -30,3 +30,4 @@ C:\Users\COMPUTER1\Desktop\mobi\leak_test_project\leak_test_project\obj\Release\
C:\Users\COMPUTER1\Desktop\mobi\leak_test_project\leak_test_project\obj\Release\leak_test_project.pdb
C:\Users\COMPUTER1\Desktop\mobi\leak_test_project\leak_test_project\obj\Release\Views\StartupLoginWindow.baml
C:\Users\COMPUTER1\Desktop\mobi\leak_test_project\leak_test_project\obj\Release\Views\StartupLoginWindow.g.cs
C:\Users\COMPUTER1\Desktop\mobi\leak_test_project\leak_test_project\bin\Release\Database.ini

BIN
leak_test_project/obj/Release/leak_test_project.exe

Binary file not shown.

4
leak_test_project/obj/Release/leak_test_project.exe.manifest

@ -42,14 +42,14 @@
</dependentAssembly>
</dependency>
<dependency>
<dependentAssembly dependencyType="install" allowDelayedBinding="true" codebase="leak_test_project.exe" size="190464">
<dependentAssembly dependencyType="install" allowDelayedBinding="true" codebase="leak_test_project.exe" size="210432">
<assemblyIdentity name="leak_test_project" version="1.0.0.0" language="neutral" processorArchitecture="msil" />
<hash>
<dsig:Transforms>
<dsig:Transform Algorithm="urn:schemas-microsoft-com:HashTransforms.Identity" />
</dsig:Transforms>
<dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha256" />
<dsig:DigestValue>oU1Vb+qDbIJPdoRRPncFp5ECBdIWWwNNrCazZR4lxdI=</dsig:DigestValue>
<dsig:DigestValue>bIiBUDqHxOgJSZaDgyYguxKJoGO3oFxaqRp+FVa4a/A=</dsig:DigestValue>
</hash>
</dependentAssembly>
</dependency>

BIN
leak_test_project/obj/Release/leak_test_project.g.resources

Binary file not shown.

BIN
leak_test_project/obj/Release/leak_test_project.pdb

Binary file not shown.

8
leak_test_project/obj/Release/leak_test_project_MarkupCompile.cache

@ -10,10 +10,10 @@ none
false
TRACE
C:\Users\COMPUTER1\Desktop\mobi\leak_test_project\leak_test_project\App.xaml
6681427662
1-698605034
311518714309
14-767506772
6-1954804774
1-1942997596
35231504871
14188644636
Views\HomeView.xaml;Views\DataView.xaml;Views\ParametersWindow.xaml;Views\CommunicationWindow.xaml;Views\StartupLoginWindow.xaml;MainWindow.xaml;
False

2
leak_test_project/obj/Release/leak_test_project_MarkupCompile.lref

@ -1,4 +1,4 @@

C:\Users\COMPUTER1\Desktop\mobi\leak_test_project\leak_test_project\obj\Release\GeneratedInternalTypeHelper.g.cs
FC:\Users\COMPUTER1\Desktop\mobi\leak_test_project\leak_test_project\App.xaml;;
FC:\Users\COMPUTER1\Desktop\mobi\leak_test_project\leak_test_project\Views\HomeView.xaml;;
FC:\Users\COMPUTER1\Desktop\mobi\leak_test_project\leak_test_project\MainWindow.xaml;;

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save