본 문서는 **양산형 테스트 기기 모니터링 시스템**의 소프트웨어 아키텍처, 주요 기능 변경 사항(저항 테스트 및 로그인 프로시저 연동), 그리고 유지보수 및 릴리즈를 위한 가이드를 정리한 지식베이스 문서입니다.
---
## 1. 시스템 개요 및 구성 (System Overview)
본 시스템은 제조 라인 최종 단계에서 제품(전기차 부품 등)의 전기적 성능 계측 데이터(전압, 전류, 저항)를 실시간으로 모니터링하고 최종 합격/불합격(PASS/FAIL) 판정을 내린 후 결과를 데이터베이스에 기록하고 설비 제어 완료 신호를 전달하는 WPF 기반 윈도우 애플리케이션입니다.
- **판정 결합:** `isFinalPass = isVoltageOk && isCurrentOk && isResistanceOk` 조건을 통해 세 항목 중 하나라도 이탈 시 자동으로 빨간색 텍스트 및 대형 **`NG`** 판정이 출력되도록 비즈니스 로직을 보강했습니다.
- 전압·전류·저항 응답의 `+9.90000000E+37` 등 절댓값 `9.0e37` 이상인 유한 숫자와 `OL`, `O.L`, `OVERLOAD`, `OVLD` 문자열은 해당 항목의 Overload로 처리합니다.
- 해당 항목만 표시·저장 단위의 `999`로 대체하고 다른 항목의 측정과 화면 갱신을 계속합니다. `999`는 전류/저항 단위 변환으로 확대·축소하지 않습니다. 정상 응답 `999 Ohm`은 기존대로 `0.999 kOhm`으로 처리합니다.
-`EquipmentMeasurementResult`의 항목별 Overload 플래그로 NG를 확정합니다. Overload가 없는 정상 측정값은 기존 범위 판정을 적용합니다. 빈 응답·잘못된 문자열·실제 통신 오류는 별도 오류로 남기며 측정값 전체를 0으로 대체하지 않습니다.
- Overload NG도 일반 성능 NG와 동일한 경로로 처리합니다. 화면에는 해당 항목 `999`와 나머지 실측값을 유지하고, 각인·QR 스캔·DB 저장을 생략한 뒤 다음 검사 대기로 복귀합니다. 새 각인 순번을 소비하지 않습니다.
- DB 저장은 QR 검증까지 합격한 OK에만 허용합니다. DatabaseService도 NG를 SQL 연결·변환 전에 차단하므로 기존 OK 레코드는 NG 재검사로 덮어쓰지 않습니다. 현장 스키마의 세 계측 컬럼/SQL 매개변수는 `DECIMAL(5,2)`를 유지합니다.
- Overload 원문과 전체 계측 순서는 `debug` 폴더의 파일에 기록합니다. 이 진단 로그는 DB 결과 저장과 별개로 유지합니다.
본 프로젝트는 **.NET 9.0 (WPF)** 타깃의 32비트(x86) 자립형 애플리케이션으로, 프로젝트 파일([marking_gui.csproj](file:///C:/Users/COMPUTER1/Desktop/mobi/marking_gui/marking_gui/marking_gui.csproj))에 WiX Toolset 단일 통합 설치 마법사 컴파일 타깃이 통합되어 있습니다.
-`LoggerService.Error` 호출 시 실행 파일 폴더 아래 `debug/error_yyyyMMdd_HHmmss_fff_<id>.log`를 UTF-8로 생성합니다. 폴더명과 파일명은 영문·숫자를 사용하고 로그 본문의 한글은 유지합니다. 일별 로그에는 해당 파일 경로를 기록합니다.
- 파일에는 발생 시각과 시간대, 프로그램 버전, 실행 환경, 예외 종류·내부 예외·스택 추적, 직전 로그 최대 100건을 기록합니다. 설정 파일 전체나 DB 비밀번호는 진단 정보로 수집하지 않습니다.
- 계측 오류에는 장비 주소·포트, 타임아웃·안정화 시간, 송신 명령, 수신 응답 원문, 전원 차단 명령 전송 결과를 추가합니다. 메인 화면의 계측 오류 기록에는 IC_SN·Lot_No·도안 경로도 포함됩니다.
- 일별 로그는 기존 30일 보관 정책을 유지하며, 오류별 진단 파일은 자동 삭제하지 않습니다. 폴더가 삭제되면 다음 기록 시 재생성합니다. 한쪽 저장에 실패해도 다른 쪽 기록을 시도하며, 진단 파일 저장 실패는 일별 로그에 남깁니다.
- 현장 오류 분석 시 발생 시각에 해당하는 진단 파일과 일별 로그를 함께 확인합니다. 이 기능은 원인 추적용이며 계측 판정·마킹·DB 저장 정책을 변경하지 않습니다.
- 실장비 없는 회귀 검증: `dotnet run --project tests/DiagnosticsRegression/DiagnosticsRegression.csproj`
본 시스템은 **Microsoft SQL Server 2012** 이상의 버전에 완전히 호환되도록 구성되었습니다. 데이터베이스 구성 시 [sql/database_setup.sql](file:///C:/Users/COMPUTER1/Desktop/mobi/marking_gui/sql/database_setup.sql) 스크립트를 활용할 수 있습니다.
- 바코드 S/N(`IC_SN`, NVARCHAR(30), 기본키, NOT NULL)을 키로 하며, 30자 초과 시 코드 내에서 절삭합니다.
-`PCB_Barcode` (NVARCHAR(30), NOT NULL): 정상 검사 시 PCB 바코드 스캔값, 강제검사(BYPASS) 시 `IC_SN` 값으로 자동 대체 저장됩니다.
- 고유 QR 코드 문자열 `Marking_QR` (NVARCHAR(60)): `IC_SN;도면품번;HKMC품번;각인번호;` 포맷으로 구성되며, 60자 초과 시 절삭합니다.
- 공정 정보(`Maker`, `Model`, `Variant_1`, `Variant_2`, `Operator`, `Production_Date`, `Line`, `Lot_No`, `Jig_No`) 및 최종 판정(`Result`)이 통합 적재됩니다. 각 문자열 컬럼은 최대 10자로 초과 시 절삭 처리됩니다.
- 3단계에 걸친 전압/전류/저항 컬럼(`PT_Vol_1~3`, `PT_Current_1~3`, `PT_Resistance_1~3`)이 존재하며, 현재는 1번 컬럼들에 최종 계측 수치(소수점 2~3자리 반올림)를 저장하고 2, 3번 컬럼은 `0.00`으로 기본값을 보정하여 저장합니다.
-`PT_Vol_1`, `PT_Current_1`, `PT_Resistance_1`의 현장 컬럼 규격은 모두 `DECIMAL(5,2)`이며 Overload 대체값 `999.00`을 수용합니다.
-`Result` 컬럼에는 QR 검증까지 최종 합격된 **`OK`** 레코드만 저장합니다. Overload NG, 일반 규격 이탈 NG, QR 검증 실패는 저장하지 않으며 기존 레코드도 변경하지 않습니다. 과거에 생성된 `NG:<IC_SN>` QR은 순번 계산에서 제외합니다.
## 5. 레이저 각인기 연동 규격 및 구현 사양 (Laser Marking Device Integration)
본 프로그램은 **LMC1 레이저 각인 컨트롤러 보드** 제어를 위해 C++ DLL Wrapper인 `MarkEzdWrapper.dll`을 활용하며, 하드웨어와의 메모리 정합성 및 실기 작동 안전성을 완벽히 조율한 최종 기술 사양은 다음과 같습니다.
### 5.1. 핵심 연동 규칙 및 메커니즘
1.**IntPtr 기반의 기기 세션 핸들(hHandle) 관리**:
-`InitMarkEzd()` 함수를 기동하여 생성되는 장치 관리 메모리 세션 포인터(`IntPtr`)를 받아 전역 변수에 저장합니다.
- 이후 모든 연동 API(`Init`, `LoadEzdFile`, `ChangeTextByName`, `Mark`, `Close`)의 첫 번째 매개변수로 이 `IntPtr` 핸들을 인계하여 호출해야 합니다. 임의의 정수 값(`0` 등)이나 `ref int` 를 대입하여 호출하면 메모리 위반 크래시(`AccessViolationException`)가 발생합니다.
2.**유니코드(`CharSet.Unicode`) 마샬링 준수**:
- C++ DLL 단의 모든 문자열 인자는 `const wchar_t*` (유니코드)를 사용합니다.
- 따라서 모든 DllImport 선언의 문자셋 속성은 반드시 **`CharSet = CharSet.Unicode`** 여야 합니다. `Ansi`로 지정할 시 경로명 및 파일명이 한자 깨진 글자로 읽혀 `EZCAD.CFG failed` 에러를 냅니다.
3.**WPF 윈도우 핸들(HWND) 지연 초기화**:
-`Init` API 호출 시 실제 유효한 부모 윈도우 핸들(`IntPtr windowHandle`)이 필요합니다.
- MainWindow의 생성자 시점에는 핸들이 미생성 상태(`IntPtr.Zero`)이므로 즉시 초기화하면 안 됩니다.
- 반드시 WPF **`Dispatcher.BeginInvoke(..., DispatcherPriority.Loaded)`** 지연 큐를 활용하여 메인 윈도우 로드가 완료된 후에 유효한 창 핸들을 추출해 이니셜라이즈 해야 합니다.