diff --git a/.vs/marking_gui.slnx/FileContentIndex/a26e37eb-5b6f-417f-8426-dfd11cf0e629.vsidx b/.vs/marking_gui.slnx/FileContentIndex/a26e37eb-5b6f-417f-8426-dfd11cf0e629.vsidx deleted file mode 100644 index f298f97..0000000 Binary files a/.vs/marking_gui.slnx/FileContentIndex/a26e37eb-5b6f-417f-8426-dfd11cf0e629.vsidx and /dev/null differ diff --git a/.vs/marking_gui.slnx/v18/.suo b/.vs/marking_gui.slnx/v18/.suo index 7a14d40..ff9e500 100644 Binary files a/.vs/marking_gui.slnx/v18/.suo and b/.vs/marking_gui.slnx/v18/.suo differ diff --git a/.vs/marking_gui.slnx/v18/DocumentLayout.backup.json b/.vs/marking_gui.slnx/v18/DocumentLayout.backup.json index e9a678d..165fc2d 100644 --- a/.vs/marking_gui.slnx/v18/DocumentLayout.backup.json +++ b/.vs/marking_gui.slnx/v18/DocumentLayout.backup.json @@ -29,7 +29,7 @@ "ToolTip": "C:\\Users\\COMPUTER1\\Desktop\\mobi\\marking_gui\\marking_gui\\MainWindow.xaml", "RelativeToolTip": "marking_gui\\MainWindow.xaml", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003549|", - "WhenOpened": "2026-06-16T05:11:43.746Z", + "WhenOpened": "2026-07-03T01:49:59.337Z", "EditorCaption": "" } ] diff --git a/.vs/marking_gui.slnx/v18/DocumentLayout.json b/.vs/marking_gui.slnx/v18/DocumentLayout.json index e9a678d..165fc2d 100644 --- a/.vs/marking_gui.slnx/v18/DocumentLayout.json +++ b/.vs/marking_gui.slnx/v18/DocumentLayout.json @@ -29,7 +29,7 @@ "ToolTip": "C:\\Users\\COMPUTER1\\Desktop\\mobi\\marking_gui\\marking_gui\\MainWindow.xaml", "RelativeToolTip": "marking_gui\\MainWindow.xaml", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003549|", - "WhenOpened": "2026-06-16T05:11:43.746Z", + "WhenOpened": "2026-07-03T01:49:59.337Z", "EditorCaption": "" } ] diff --git a/docs/developer_manual.md b/docs/developer_manual.md new file mode 100644 index 0000000..c668be9 --- /dev/null +++ b/docs/developer_manual.md @@ -0,0 +1,361 @@ +# Performance Inspect & Laser Marking System - 개발자 메뉴얼 + +본 문서는 **양산형 테스트 기기 모니터링 시스템**의 유지보수, 릴리즈, 시스템 통합 및 디버깅을 위한 개발자용 상세 기술 사양서입니다. 시스템 설계 정보, 하드웨어 연동 규격, 그리고 제어 시퀀스를 포함하고 있습니다. + +--- + +## 1. 시스템 아키텍처 및 요구사항 + +### 1.1 기술 스택 +- **프레임워크**: .NET Framework 4.7.2 (WPF) +- **UI 패턴**: MVVM (MainWindow, SettingsWindow, StartupLoginWindow 등) +- **데이터베이스**: Microsoft SQL Server 2012 이상 (ADO.NET 연동) +- **장비 연동**: + - LMC1 레이저 각인 컨트롤러 (C++ Wrapped DLL `MarkEzdWrapper.dll` 로드) + - Keysight DMM & PSU (TCP/IP 기반 SCPI 통신) + - 4251 센서 판독 보드 (시리얼 RS-232 기반 고유 프로토콜) + +### 1.2 개발 환경 구동 요구사항 +- Visual Studio 2022 이상 +- MSBuild 엔진 (XAML 컴파일 지원) +- MS-SQL Server Express 또는 로컬 DB 인프라 +- EZCAD 2 소프트웨어 및 LMC USB 각인 카드 드라이버 + +--- + +## 2. 설정 파일 (INI) 상세 사양 + +실행 파일 경로에 위치하는 INI 설정 파일들의 각 항목별 역할 및 타입 정의입니다. + +### 2.1 Setting.ini +계측 장비 접속 주소, SCPI 제어 명령어 매개변수 및 레이저 각인 도안 태그 매핑(`[Tags]`) 설정을 포함합니다. + +```ini +; ================================================================= +; 성능 검사 모니터링 시스템 - 레이저 마킹기 연동 설정 파일 +; ================================================================= + +[Settings] +; 마킹 제어 라이브러리 및 드라이버(EzCad) 폴더 경로 +EzdFolderPath=c:\EzCad + +; 테스트 모드 여부 (true 시 하단 테스트 마킹, 강제 검사 버튼 노출) +test=false + + +[Tags] +; 도안(.ezd) 파일 내의 텍스트 오브젝트 치환용 태그명 매핑 리스트 +; (도안에 정의된 오브젝트명과 동일하게 설정해야 정상 작동합니다) + +; 일련번호 (Serial Number) 치환 태그 +SerialTag=SERIAL + +; 작업 년월일 (Lot No) 치환 태그 +LotNoTag=YYMMDD + +; 제품 모델명 (Product Name) 치환 태그 +ProductTag=PRODUCT + +; 2D QR 코드 데이터 치환 태그 +QrCodeTag=QRCODETAG + +; 검사 라인 코드 치환 태그 +LineTag=LCODE + +; 작업 조 (Shift) 치환 태그 +ShiftTag=BAN + + +; ================================================================= +; 계측기 연동 설정 (SCPI / LAN) +; ================================================================= + +[Equipment] +; 계측기 통신 타임아웃 (ms) +Timeout=5000 + +; 측정 안정화 대기 시간 (ms) — Power Supply ON 후 이 시간만큼 대기 후 읽음 +SettleMilliseconds=2000 + +; 검사 전 보드 전원 공급 커맨드 (세미콜론으로 구분) +PreBoardCommand=VOLT 5, (@2);OUTP ON, (@2) +PreBoardDelayMilliseconds=2000 + +; 로그아웃 시 전원 차단 커맨드 +LogoutCommand=OUTP OFF, (@1) + + +[DMM] +; Digital Multimeter — 전압 및 저항 측정 전용 (예시 IP: 192.168.1.100) +VoltageConnection=LAN +VoltageHost=192.168.1.100 +VoltagePort=5025 +VoltageIdnMatch=34465A +VoltageBaudRate=115200 +VoltageTerminator=LF +VoltageSetupCommand= +VoltageReadCommand=MEAS:VOLT:DC? +ResistanceReadCommand=MEAS:RES? + + +[CurrentDMM] +; Digital Multimeter — 전류 측정 전용 (예시 IP: 192.168.1.102 - 현재 제어 시퀀스에서 미사용) +CurrentDMMConnection=LAN +CurrentDMMHost=192.168.1.102 +CurrentDMMPort=5025 +CurrentDMMIdnMatch=34465A +CurrentDMMBaudRate=115200 +CurrentDMMTerminator=LF +CurrentDMMReadCommand=MEAS:CURR:DC? + + +[PowerSupply] +; Power Supply — 출력 제어 및 전류 측정 (예시 IP: 192.168.1.101) +CurrentConnection=LAN +CurrentHost=192.168.1.101 +CurrentPort=5025 +CurrentIdnMatch=E36233A +CurrentBaudRate=9600 +CurrentTerminator=LF +CurrentSetupCommand=OUTP ON, (@1) +CurrentCleanupCommand=OUTP OFF, (@1) +CurrentReadCommand=MEAS:CURR? (@1) + + +[Limits] +; 최종 계측 합/불 판정 규격 임계치 (Min: 하한치, Max: 상한치) +; UI [시험 설정] 메뉴에서 변경 시 자동 저장 및 갱신됩니다. +MinVoltage=4.0 +MaxVoltage=6.0 + +MinCurrent=8.0 +MaxCurrent=10.0 + +MinResistance=0.4 +MaxResistance=0.8 +``` + +### 2.2 Database.ini +생산 이력 조회 및 적재 목적의 DB 연결 설정입니다. + +```ini +[Database] +# SQL 데이터베이스 접속 주소 및 계정 정보 (예시 주소) +Db_Server=192.168.1.200 +Db_Name=MarkingDb +Db_User=db_user +Db_Password=db_password + +# 로컬 테스트 및 DB 점검 시 우회용 플래그 +OfflineMode=False + + +[Login] +# 작업자 로그인 검증 시 호출할 저장 프로시저명 +Procedure=dbo.CheckOperator + +# 작업 공정 식별 이름 (저장 프로시저 매개변수 바인딩용) +ProcessName=Marking + +# 오프라인 로그인 미리보기 활성화 여부 +OfflinePreview=false +``` + +### 2.3 Sensor.ini +4251 센서 통신 보드 시리얼 통신을 위한 포트 매핑 파일입니다. + +```ini +[Sensor] +# 시리얼 COM 포트 번호 +Port=COM3 + +# 통신 속도 +BaudRate=115200 + +# 보드 내부 멀티 채널 스위칭 인덱스 (1 또는 2) +Channel=1 +``` + +--- + +## 3. 데이터베이스 스키마 및 연동 규격 + +### 3.1 테이블 구성 정보 +- **`[dbo].[Users]` (작업자 계정 정보 테이블)** + - `OperatorId` (VARCHAR(50), UNIQUE): 작업자 로그인 ID + - `Password` (VARCHAR(50)): 비밀번호 + - `Name` (VARCHAR(50)): 작업자 성명 + - `IsActive` (BIT): 활성 상태 여부 + +- **`[dbo].[Marking]` (최종 성능 계측값 및 마킹 결과 저장 테이블)** + - `IC_SN` (VARCHAR(50), PRIMARY KEY): 센서 일련번호 + - `Marking_QR` (VARCHAR(100), UNIQUE): 고유 QR 코드 문자열 (yyMMdd + 일련번호) + - `Maker` (VARCHAR(50)), `Model` (VARCHAR(50)): 제품 사양 정보 + - `Variant_1`, `Variant_2` (VARCHAR(50)) + - `Operator` (VARCHAR(50)): 담당 작업자 ID + - `Production_Date` (DATETIME): 생산 일자 및 계측 시간 + - `Line` (VARCHAR(10)): 생산 라인 (최대 10자 제한, 초과 시 코드 내에서 절삭) + - `Lot_No` (VARCHAR(8)): 로트 번호 (최대 8자 제한, 초과 시 코드 내에서 절삭) + - `Jig_No` (VARCHAR(10)): 지그 번호 + - `PT_Vol_1` (DECIMAL(18,2)), `PT_Current_1` (DECIMAL(18,2)), `PT_Resistance_1` (DECIMAL(18,3)): 실측 전압, 전류, 저항값 + - `Result` (VARCHAR(10)): 최종 종합 판정 (`PASS` 또는 `FAIL`) + - `MarkedSerial` (VARCHAR(50)): 최종 마킹된 시퀀스 코드 + +### 3.2 이전 공정 결과 교차 검증 (Subquery) +검사 개시 시점, 제품의 S/N(`IC_SN`)을 기반으로 선행 공정이 완료되었는지 조회합니다. +```sql +SELECT + (SELECT Result FROM Housing_Assembly WHERE IC_SN = @IC_SN) AS HousingResult, + (SELECT Final_Result FROM Cal_EOL_Result WHERE IC_SN = @IC_SN) AS CalResult, + (SELECT Result FROM EOL WHERE IC_SN = @IC_SN) AS EolResult +``` +- **판정 조건**: 조회 결과 3개 항목이 모두 `PASS` 또는 `OK` 상태이어야 최종 검사 기동이 허용됩니다. + +### 3.3 로그인 저장 프로시저 (`[dbo].[CheckOperator]`) +- **프로시저 스펙**: + ```sql + CREATE PROCEDURE [dbo].[CheckOperator] + @Operator VARCHAR(50), + @Password VARCHAR(50), + @ProcessName VARCHAR(50) = NULL + AS + BEGIN + SELECT COUNT(*) + FROM Users + WHERE OperatorId = @Operator + AND Password = @Password + AND IsActive = 1 + END + ``` +- **호출 매핑 코드**: + ADO.NET `SqlCommand` 객체를 사용하여 `CommandType.StoredProcedure` 형식으로 호출하며 리턴값이 `1`일 경우 검증 처리합니다. + +--- + +## 4. 하드웨어 제어 프로토콜 및 SCPI 사양 + +### 4.1 4251 센서 통신 보드 시리얼 패킷 규격 +센서 일련번호를 추출하고 보드를 제어하기 위해 RS-232 시리얼(115200bps)을 통해 다음 패킷 명령을 전송합니다. + +1. **보드 대기 및 리셋 명령**: `"x00o"` +2. **보드 채널 결속 명령**: `"x00c_00{channel}101:owt28006727ea97c7801"` + - `{channel}`은 `Sensor.ini`에서 파싱한 값 (예: `1` 또는 `2`) +3. **고유 ID(IC_SN) 읽기 명령**: `"x00c_00{channel}101:ow2800326003e"` + - 송신 후 보드 응답 버퍼에서 16진수 일련번호 데이터(16자)를 추출합니다. +4. **보정 기본값 전송**: `"x00c_00{channel}001:owt28006727ea97c7801"` + +### 4.2 계측 장비 제어 SCPI 명령어 구성 및 시퀀스 +모든 TCP/IP 계측기는 포트 **5025**를 통해 SCPI 표준 명령으로 조작됩니다. + +1. **장비 연결 및 아이덴티티 검증 (Handshake)**: + - 각 계측기 접속 후 `*IDN?` 명령을 전송하여 응답 텍스트에 `Setting.ini`에 지정된 장비 모델명(예: `34465A`, `E36233A`)이 수록되어 있는지 확인합니다. +2. **테스트 주전원(CH1) 인가**: + - `OUTP ON, (@1)` + - 전원 공급 후 신호 안정을 위해 `SettleMilliseconds`(기본 2000ms, 즉 2초)만큼 대기합니다. +3. **측정 데이터 수집**: + - **전압(V)**: 전압 DMM(예: 192.168.1.100)에 `MEAS:VOLT:DC?`를 질의하여 획득. + - **저항(Ω)**: 저항 DMM(예: 192.168.1.100)에 `MEAS:RES?`를 질의하여 획득. (획득값은 kΩ 단위 조정을 위해 1000으로 나눔) + - **전류(A)**: 파워서플라이(예: 192.168.1.101)의 CH1에 `MEAS:CURR? (@1)`을 질의하여 획득. (획득값은 mA 단위 조정을 위해 1000을 곱함) + - *주의: 전류 측정 전용 DMM(예: 192.168.1.102) 장비는 설정 파일에 로드되나 실제 측정 시퀀스 상에서는 사용되지 않습니다.* +4. **주전원(CH1) 차단**: + - `OUTP OFF, (@1)` + - 계측 도중 예외가 발생하더라도 `finally` 구문 내에서 차단 명령을 전송합니다. + +--- + +## 5. 레이저 마킹기 연동 사양 (LMC1 API) + +C++로 개발된 레이저 각인기 드라이버 DLL인 `MarkEzdWrapper.dll`을 P/Invoke를 통해 호출합니다. + +### 5.1 연동 시 핵심 규칙 +1. **세션 핸들 (`hHandle`) 관리**: + - 프로그램 시작 시 `InitMarkEzd()` 함수를 호출하여 세션 포인터(`IntPtr`)를 받아 전역 변수로 관리합니다. + - 각인 관련 API 함수 호출 시 이 `IntPtr` 핸들을 첫 번째 매개변수로 전달해야 합니다. +2. **유니코드 마샬링 (`CharSet.Unicode`)**: + - C++ DLL 단의 모든 문자열 인자가 `const wchar_t*` (유니코드)를 사용하므로, DllImport 선언의 문자셋 속성은 `CharSet = CharSet.Unicode`로 설정해야 정상 작동합니다. +3. **HWND 부모 창 핸들 지연 초기화**: + - `Init` 함수는 부모 WPF 윈도우의 핸들(`IntPtr`)을 요구합니다. + - Window 생성자 시점에는 핸들이 미생성 상태이므로, 반드시 **`Dispatcher.BeginInvoke(..., DispatcherPriority.Loaded)`**를 활용하여 메인 윈도우 로드가 완료된 후에 핸들을 추출해 초기화해야 합니다. + +### 5.2 DLL 가져오기 (P/Invoke) 선언 명세 +```csharp +[DllImport("MarkEzdWrapper.dll", EntryPoint = "InitMarkEzd", CallingConvention = CallingConvention.Cdecl)] +private static extern IntPtr InitMarkEzd(); + +[DllImport("MarkEzdWrapper.dll", EntryPoint = "Init", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)] +private static extern int Init(IntPtr hHandle, string strEzCadPath, IntPtr hOwenWnd); + +[DllImport("MarkEzdWrapper.dll", EntryPoint = "LoadEzdFile", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)] +private static extern int LoadEzdFile(IntPtr hHandle, string strEzCadFileName); + +[DllImport("MarkEzdWrapper.dll", EntryPoint = "ChangeTextByName", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)] +private static extern int ChangeTextByName(IntPtr hHandle, string strTextName, string strTextNew); + +[DllImport("MarkEzdWrapper.dll", EntryPoint = "Mark", CallingConvention = CallingConvention.Cdecl)] +private static extern int Mark(IntPtr hHandle); + +[DllImport("MarkEzdWrapper.dll", EntryPoint = "Close", CallingConvention = CallingConvention.Cdecl)] +private static extern int Close(IntPtr hHandle); +``` + +--- + +## 6. 데모용 시뮬레이션 격리 및 릴리즈 배포 가이드 + +### 6.1 데모 모드 격리 코드 해제 방법 +상용 빌드 릴리즈 시, 데이터베이스에 실제 로그인하지 않고 통과하는 데모 우회 로직을 소스 코드 단에서 제거해야 합니다. + +1. **[LoginServicesStub.cs](file:///C:/Users/COMPUTER1/Desktop/mobi/marking_gui/marking_gui/Services/LoginServicesStub.cs)** 파일을 엽니다. +2. `ExistsAsync` 메서드 안의 `catch (Exception ex)` 구문 내부에 위치한 아래의 **DEMO MODE 주석 배너** 영역 코드를 삭제하거나 주석 처리하여 릴리즈 빌드를 만드십시오. + +```csharp +// ==================== [DEMO MODE START] ==================== +// 데모 시뮬레이션용 프로시저 성공 연출 코드 +// 상용 릴리즈 시 이 블록은 완전히 제거(삭제)할 수 있습니다. +if (!string.IsNullOrWhiteSpace(_loginSettings.Procedure)) +{ + System.Diagnostics.Debug.WriteLine(string.Format("[데모 시뮬레이션] 프로시저 '{0}' 호출 모사 (ID: {1}, PW: {2}, Process: {3})", + _loginSettings.Procedure, operatorId, password, _loginSettings.ProcessName)); + return IsDefaultAccount(operatorId, password); +} +// ==================== [DEMO MODE END] ==================== +``` + +### 6.2 릴리즈 게시 및 단일 설치 파일(MarkingSetup.exe) 자동 생성 가이드 + +본 프로젝트는 Visual Studio 게시 프로필 및 WiX Toolset 커스텀 타깃이 적용되어 있어, CLI 게시 명령 한 번으로 단일 실행 설치 파일(`MarkingSetup.exe`)을 자동으로 컴파일 및 생성합니다. + +- **권장 CLI 게시 명령어 (Terminal)**: + ```powershell + dotnet publish marking_gui/marking_gui.csproj -c Release -r win-x86 --self-contained + ``` +- **자동 빌드 및 패키징 동작 순서**: + 1. 프로젝트 릴리즈 바이너리 및 의존 라이브러리 일체 자동 수집 + 2. `C:\marking\libs\` 경로로 모든 런타임 DLL 격리 구성 + 3. WiX Toolset(`wix build`)이 가동하여 MSI 패키지 및 단일 EXE 부트스트랩 번들 컴파일 + 4. 최종 설치 파일 `MarkingSetup.exe`가 `C:\marking\` 및 `bin\Release\net9.0-windows\win-x86\publish\` 폴더로 자동 생성 이관 +- **배포 및 전달 대상 파일**: + - `MarkingSetup.exe` (단일 파일만 현장에 전달하여 1-Click 설치 진행) + +### 6.3 배포 설치 디렉터리(`C:\marking\`) 파일 구조 및 런타임 생성 폴더 사양 + +`MarkingSetup.exe`로 인스톨된 후 `C:\marking\` 아래에 배치되는 파일 및 런타임 가동 중 생성되는 디렉터리 명세입니다. + +``` +C:\marking\ +├── marking_gui.exe # 메인 WPF 실행 바이너리 +├── marking_gui.dll # 핵심 애플리케이션 어셈블리 +├── marking_gui.deps.json # 종속성 매니페스트 +├── marking_gui.runtimeconfig.json # .NET 9.0 런타임 환경 구성 +├── Setting.ini # 마킹기 경로, 태그 매핑, 계측기 IP, SCPI 전원, [Limits] 판정 임계치 종합 설정 +├── Database.ini # MS-SQL 접속 계정 및 오프라인 모드(OfflineMode) 설정 +├── Sensor.ini # 4251 센서 통신 보드 시리얼 포트(COM), BaudRate, 채널 설정 +├── libs\ # C++ LMC 드라이버 라이브러리 격리 폴더 +│ ├── MarkEzdWrapper.dll # C# - C++ LMC P/Invoke 래퍼 +│ ├── DataMgr.dll # EZCAD 데이터 매니저 +│ ├── MarkEzd.dll # EZCAD 커널 마킹 라이브러리 +│ └── haspms32.dll # 동글/라이선스 보안 라이브러리 일체 +├── logs\ # 런타임 가동 중 일별 자동 생성되는 시스템/통신/마킹 이력 로그 +│ └── 2026-07-23.log # 텍스트 형태의 일자별 로그 파일 +└── publish_backup\ # 게시 및 버전 업데이트 시 기존 파일 자동 백업 보관 폴더 +``` diff --git a/docs/knowledge_base.md b/docs/knowledge_base.md index b332383..e3e1cd0 100644 --- a/docs/knowledge_base.md +++ b/docs/knowledge_base.md @@ -69,20 +69,35 @@ ``` 4. 삭제 후 빌드를 다시 수행하면, DB 통신 실패 시 로그인이 우회되지 않고 정상적으로 "로그인 DB 확인 실패" 경고가 발생하게 됩니다. -### 3.2. 빌드 실행 방법 -본 프로젝트는 레거시 .NET Framework 4.7.2 타깃의 WPF 애플리케이션으로, XAML 마크업 빌드가 정상 동작하기 위해서는 Visual Studio 환경의 MSBuild 엔진이 필요합니다. -- **MSBuild 권장 경로:** `C:\Program Files\Microsoft Visual Studio\18\Insiders\MSBuild\Current\Bin\MSBuild.exe` (설치 버전에 따라 Insiders, Community, Professional 등으로 달라질 수 있음) -- **빌드 명령어 (터미널):** +### 3.2. 릴리즈 게시 및 단일 설치 파일 패키징 가이드 +본 프로젝트는 **.NET 9.0 (WPF)** 타깃의 32비트(x86) 자립형 애플리케이션으로, 프로젝트 파일([marking_gui.csproj](file:///C:/Users/COMPUTER1/Desktop/mobi/marking_gui/marking_gui/marking_gui.csproj))에 WiX Toolset 단일 통합 설치 마법사 컴파일 타깃이 통합되어 있습니다. + +- **권장 CLI 게시 명령어 (터미널):** ```powershell - & "C:\Program Files\Microsoft Visual Studio\18\Insiders\MSBuild\Current\Bin\MSBuild.exe" /t:rebuild + dotnet publish marking_gui/marking_gui.csproj -c Release -r win-x86 --self-contained ``` -- **출력 경로:** 빌드 완수 후 `marking_gui\bin\Debug\` 또는 `Release\` 폴더에 실행 바이너리 및 `Database.ini` 파일이 함께 생성됩니다. +- **빌드 및 패키징 동작 메커니즘:** + 1. `.NET 9.0 런타임`이 포함된 자립형(Self-Contained) 싱글 실행 바이너리를 빌드합니다. + 2. 계측 및 각인기 드라이버 DLL들을 `C:\marking\libs\` 디렉터리로 격리 수집합니다. + 3. `wix build`가 백그라운드에서 동작하여 `Product.wxs` 및 `Bundle.wxs`를 단일 실행 설치 파일(`MarkingSetup.exe`)로 원스톱 패키징합니다. +- **출력 경로 및 최종 결과물:** + - `C:\marking\MarkingSetup.exe` 및 `marking_gui\bin\Release\net9.0-windows\win-x86\publish\MarkingSetup.exe` + - 현장 라인에는 단일 파일 `MarkingSetup.exe`만 배포하여 1-Click 설치를 진행할 수 있습니다. + +### 3.3. 설치 경로(`C:\marking\`) 내 파일 구성 및 런타임 생성 폴더 +- `marking_gui.exe` (메인 실행 파일) 및 .NET 9.0 런타임 DLL +- `Setting.ini` (장비 IP, 마킹 태그, SCPI 커맨드, `[Limits]` 규격 임계치) +- `Database.ini` (DB 접속 주소 및 `OfflineMode=true` 설정) +- `Sensor.ini` (4251 센서 통신 보드 COM 포트/속도/채널) +- `libs/` (LMC 레이저 각인 드라이버 격리 디렉터리: `MarkEzdWrapper.dll` 등) +- `logs/` (런타임 가동 시 일자별 자동 생성되는 시스템/각인/통신 이력 로그 폴더) +- `publish_backup/` (빌드 배포 시 이전 릴리즈 자동 백업 폴더) --- ## 4. 데이터베이스 구성 가이드 (Database Configuration) -본 시스템은 **Microsoft SQL Server 2012** 이상의 버전에 완전히 호환되도록 구성되었습니다. 데이터베이스 구성 시 [docs/database_setup.sql](file:///C:/Users/COMPUTER1/Desktop/mobi/marking_gui/docs/database_setup.sql) 스크립트를 활용할 수 있습니다. +본 시스템은 **Microsoft SQL Server 2012** 이상의 버전에 완전히 호환되도록 구성되었습니다. 데이터베이스 구성 시 [sql/database_setup.sql](file:///C:/Users/COMPUTER1/Desktop/mobi/marking_gui/sql/database_setup.sql) 스크립트를 활용할 수 있습니다. ### 4.1. 데이터베이스 및 스키마 구조 - **데이터베이스명:** `MarkingDb` @@ -103,3 +118,43 @@ - `@ProcessName` (VARCHAR(50)): 호출 프로세스명 (선택사항, 기본값 NULL) - **리턴:** 매칭되는 활성 사용자가 존재할 시 `1`, 존재하지 않을 시 `0`을 스칼라 결과로 반환합니다. +--- + +## 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)`** 지연 큐를 활용하여 메인 윈도우 로드가 완료된 후에 유효한 창 핸들을 추출해 이니셜라이즈 해야 합니다. + +### 5.2. P/Invoke C# 선언 명세 +```csharp +[DllImport("MarkEzdWrapper.dll", EntryPoint = "InitMarkEzd", CallingConvention = CallingConvention.Cdecl)] +private static extern IntPtr InitMarkEzd(); + +[DllImport("MarkEzdWrapper.dll", EntryPoint = "Init", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)] +private static extern int Init(IntPtr hHandle, string strEzCadPath, IntPtr hOwenWnd); + +[DllImport("MarkEzdWrapper.dll", EntryPoint = "LoadEzdFile", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)] +private static extern int LoadEzdFile(IntPtr hHandle, string strEzCadFileName); + +[DllImport("MarkEzdWrapper.dll", EntryPoint = "ChangeTextByName", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)] +private static extern int ChangeTextByName(IntPtr hHandle, string strTextName, string strTextNew); + +[DllImport("MarkEzdWrapper.dll", EntryPoint = "Mark", CallingConvention = CallingConvention.Cdecl)] +private static extern int Mark(IntPtr hHandle); + +[DllImport("MarkEzdWrapper.dll", EntryPoint = "Close", CallingConvention = CallingConvention.Cdecl)] +private static extern int Close(IntPtr hHandle); +``` + + diff --git a/marking_gui.Setup/Bundle.wxs b/marking_gui.Setup/Bundle.wxs new file mode 100644 index 0000000..e4077c1 --- /dev/null +++ b/marking_gui.Setup/Bundle.wxs @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/marking_gui.Setup/Product.wxs b/marking_gui.Setup/Product.wxs new file mode 100644 index 0000000..f1bedbb --- /dev/null +++ b/marking_gui.Setup/Product.wxs @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/marking_gui/.agents/skills/laser_marking/SKILL.md b/marking_gui/.agents/skills/laser_marking/SKILL.md new file mode 100644 index 0000000..db58849 --- /dev/null +++ b/marking_gui/.agents/skills/laser_marking/SKILL.md @@ -0,0 +1,87 @@ +--- +name: laser_marking_integration +description: Guidelines and P/Invoke signatures for integrating the LMC laser marking machine via MarkEzdWrapper.dll without crashes. +--- + +# LMC 레이저 각인기 연동 규격 및 에이전트 행동 규칙 + +이 규칙은 `MarkEzdWrapper.dll` 파일과 WPF 모니터링 시스템 간의 레이저 각인(마킹) 장치 연동 설계 사양을 고정합니다. **이 사양은 어떠한 경우에도 임의로 단순 값 형식의 int 핸들이나 Ansi 마샬링으로 되돌려서는 안 되며, 반드시 이 명세를 유지해야 합니다.** + +--- + +## 1. 연동 핵심 원칙 (Core Rules) + +1. **IntPtr 기반의 기기 세션 핸들 관리**: + - `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)`** 지연 큐를 활용하여 메인 윈도우 로드가 완료된 후에 유효한 창 핸들을 추출해 이니셜라이즈 해야 합니다. + +--- + +## 2. P/Invoke 표준 선언부 (Standard C# Declarations) + +```csharp +[DllImport("MarkEzdWrapper.dll", EntryPoint = "InitMarkEzd", CallingConvention = CallingConvention.Cdecl)] +private static extern IntPtr InitMarkEzd(); + +[DllImport("MarkEzdWrapper.dll", EntryPoint = "Init", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)] +private static extern int Init(IntPtr hHandle, string strEzCadPath, IntPtr hOwenWnd); + +[DllImport("MarkEzdWrapper.dll", EntryPoint = "LoadEzdFile", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)] +private static extern int LoadEzdFile(IntPtr hHandle, string strEzCadFileName); + +[DllImport("MarkEzdWrapper.dll", EntryPoint = "ChangeTextByName", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)] +private static extern int ChangeTextByName(IntPtr hHandle, string strTextName, string strTextNew); + +[DllImport("MarkEzdWrapper.dll", EntryPoint = "Mark", CallingConvention = CallingConvention.Cdecl)] +private static extern int Mark(IntPtr hHandle); + +[DllImport("MarkEzdWrapper.dll", EntryPoint = "Close", CallingConvention = CallingConvention.Cdecl)] +private static extern int Close(IntPtr hHandle); +``` + +--- + +## 3. 핵심 비즈니스 흐름 코드 (Core Implementation Patterns) + +### 3.1. 장치 초기화 흐름 +```csharp +// 1. 선행 엔진 초기화 및 포인터 획득 +IntPtr markingHandle = InitMarkEzd(); +if (markingHandle == IntPtr.Zero) +{ + // 보드 또는 동글 락키 인식 실패 예외 처리 + return false; +} + +// 2. WPF 메인 윈도우 실제 창 핸들 획득 (Loaded 시점) +IntPtr windowHandle = new System.Windows.Interop.WindowInteropHelper(this).Handle; + +// 3. 컨트롤러 장비 초기화 수행 +int initResult = Init(markingHandle, ezcadFolderPath, windowHandle); +if (initResult == 0) +{ + // 연결 완료 상태 전환 +} +``` + +### 3.2. 공정 각인 요청 흐름 +```csharp +// 1. 도안 로드 +int loadResult = LoadEzdFile(markingHandle, absoluteEzdPath); + +// 2. 도안 내 변수 태그 치환 (Marking.ini 매핑 기준) +ChangeTextByName(markingHandle, "SERIAL", request.Serial); +ChangeTextByName(markingHandle, "YYMMDD", request.LotNo); + +// 3. 각인 기계 동작 기동 +int markResult = Mark(markingHandle); +``` diff --git a/marking_gui/App.xaml.cs b/marking_gui/App.xaml.cs index 8231783..36ae1aa 100644 --- a/marking_gui/App.xaml.cs +++ b/marking_gui/App.xaml.cs @@ -13,12 +13,72 @@ namespace marking_gui /// public partial class App : Application { + [System.Runtime.InteropServices.DllImport("kernel32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto, SetLastError = true)] + private static extern bool SetDllDirectory(string lpPathName); + protected override void OnStartup(StartupEventArgs e) { + // 바로가기 등으로 실행 시 작업 디렉토리가 꼬이는 현상을 방지하기 위해 작업 디렉토리를 실행 파일 폴더로 강제 동기화 + try + { + System.IO.Directory.SetCurrentDirectory(AppDomain.CurrentDomain.BaseDirectory); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine("작업 디렉토리 초기화 중 에러: " + ex.Message); + } + + // 전역 예외 처리 이벤트 핸들러 등록 + AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; + this.DispatcherUnhandledException += App_DispatcherUnhandledException; + TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException; + + Services.LoggerService.Info("=================== [프로그램 시작] ==================="); + + try + { + // DLL 탐색 경로에 실행 디렉토리 하위의 libs 폴더 추가 + string libsDir = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "libs"); + if (System.IO.Directory.Exists(libsDir)) + { + SetDllDirectory(libsDir); + Services.LoggerService.Info("libs 폴더 경로 추가 완료: " + libsDir); + } + } + catch (Exception ex) + { + Services.LoggerService.Error("SetDllDirectory 설정 중 예외 발생", ex); + } + base.OnStartup(e); var mainWindow = new MainWindow(); mainWindow.Show(); } + + protected override void OnExit(ExitEventArgs e) + { + Services.LoggerService.Info("=================== [프로그램 정상 종료] ==================="); + base.OnExit(e); + } + + private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) + { + var ex = e.ExceptionObject as Exception; + Services.LoggerService.Error("[비정상 종료] 처리되지 않은 도메인 예외 (UnhandledException) 발생", ex); + } + + private void App_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e) + { + Services.LoggerService.Error("[비정상 종료] 처리되지 않은 UI 스레드 예외 (DispatcherUnhandledException) 발생", e.Exception); + // 메인 루프를 유지하기 위해 예외를 처리된 것으로 설정 + e.Handled = true; + } + + private void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e) + { + Services.LoggerService.Error("[작동 오류] 처리되지 않은 비동기 태스크 예외 (UnobservedTaskException) 발생", e.Exception); + e.SetObserved(); + } } } diff --git a/marking_gui/LoginOptions.json b/marking_gui/LoginOptions.json new file mode 100644 index 0000000..15c0037 --- /dev/null +++ b/marking_gui/LoginOptions.json @@ -0,0 +1,23 @@ +{ + "loginAccounts": [ + { + "id": "su", + "password": "su" + } + ], + "makers": [ + "AMO", + "ABC" + ], + "models": [ + "MODEL-1" + ], + "variant1Values": [], + "variant2Values": [], + "lineValues": [ + "LINE-1" + ], + "jigNoValues": [ + "JIG-1" + ] +} diff --git a/marking_gui/MainWindow.xaml b/marking_gui/MainWindow.xaml index 3316cc2..861f332 100644 --- a/marking_gui/MainWindow.xaml +++ b/marking_gui/MainWindow.xaml @@ -6,6 +6,7 @@ xmlns:local="clr-namespace:marking_gui" mc:Ignorable="d" Title="양산형 테스트 기기 모니터링 시스템" Height="700" Width="900" + Icon="pack://application:,,,/Marking.ico" MinWidth="900" MinHeight="700" WindowState="Maximized" WindowStyle="None" WindowStartupLocation="CenterScreen" Background="#F3F4F6" ContentRendered="Window_ContentRendered"> @@ -111,6 +112,34 @@ + + + @@ -132,35 +161,39 @@ - + + + - - - + + + - + + + - + - + - + - + - + - + -