Browse Source

포트 연결 UI 추가 및 복구 로직 추가

main
최승민 3 weeks ago
parent
commit
adaada7b42
  1. 157
      README.md
  2. BIN
      leak_test_project.Tests/bin/Debug/net472/leak_test_project.Tests.dll
  3. BIN
      leak_test_project.Tests/bin/Debug/net472/leak_test_project.Tests.pdb
  4. 2
      leak_test_project.Tests/obj/Debug/net472/leak_test_project.Tests.AssemblyInfo.cs
  5. 2
      leak_test_project.Tests/obj/Debug/net472/leak_test_project.Tests.AssemblyInfoInputs.cache
  6. BIN
      leak_test_project.Tests/obj/Debug/net472/leak_test_project.Tests.dll
  7. BIN
      leak_test_project.Tests/obj/Debug/net472/leak_test_project.Tests.pdb
  8. 51
      leak_test_project/Infrastructure/RealDioBoard.cs
  9. 189
      leak_test_project/MainWindow.xaml
  10. 4
      leak_test_project/Manual/Communication_Manual.md
  11. 2
      leak_test_project/Manual/HomeView_Manual.md
  12. 3
      leak_test_project/Manual/IO_Monitor_Manual.md
  13. 18
      leak_test_project/Services/SentinelC28Service.cs
  14. 64
      leak_test_project/Services/ZmdiSensorService.cs
  15. 53
      leak_test_project/ViewModels/HomeViewModel.cs
  16. 32
      leak_test_project/ViewModels/InOutViewModel.cs
  17. 4
      leak_test_project/Views/DataView.xaml
  18. BIN
      leak_test_project/bin/Release/app.publish/leak_test_project.exe
  19. 4
      leak_test_project/bin/Release/config.xml
  20. 2
      leak_test_project/bin/Release/leak_test_project.application
  21. BIN
      leak_test_project/bin/Release/leak_test_project.exe
  22. 4
      leak_test_project/bin/Release/leak_test_project.exe.manifest
  23. BIN
      leak_test_project/bin/Release/leak_test_project.pdb
  24. 62
      leak_test_project/obj/Release/GeneratedInternalTypeHelper.g.cs
  25. BIN
      leak_test_project/obj/Release/MainWindow.baml
  26. 12
      leak_test_project/obj/Release/MainWindow.g.cs
  27. 12
      leak_test_project/obj/Release/MainWindow.g.i.cs
  28. BIN
      leak_test_project/obj/Release/Views/DataView.baml
  29. 2
      leak_test_project/obj/Release/Views/DataView.g.cs
  30. 2
      leak_test_project/obj/Release/Views/DataView.g.i.cs
  31. 2
      leak_test_project/obj/Release/leak_test_project.application
  32. BIN
      leak_test_project/obj/Release/leak_test_project.exe
  33. 4
      leak_test_project/obj/Release/leak_test_project.exe.manifest
  34. BIN
      leak_test_project/obj/Release/leak_test_project.g.resources
  35. BIN
      leak_test_project/obj/Release/leak_test_project.pdb
  36. 2
      leak_test_project/obj/Release/leak_test_project_MarkupCompile.lref

157
README.md

@ -6,42 +6,60 @@ Windows 기반 Air Leak Test 장비를 위한 제어 및 모니터링 GUI 프로
```text ```text
leak_test_project/ leak_test_project/
├── Infrastructure/ # 하드웨어 통신 계층 (Low-Level Communication) ├── Infrastructure/ # 하드웨어 통신 및 DIO 계층 (Low-Level Communication)
│ ├── ICommunication.cs # 통신 방식(Serial, TCP) 추상화 인터페이스 │ ├── ICommunication.cs # 통신 방식(Serial, TCP) 추상화 인터페이스
│ └── SerialProvider.cs # RS232 시리얼 통신 구현체 (8N1, IDisposable) │ ├── SerialProvider.cs # RS232 시리얼 통신 구현체 (8N1, IDisposable)
│ ├── IDioBoard.cs # DIO 보드 추상화 인터페이스 (입출력 제어, 이벤트)
│ ├── RealDioBoard.cs # ADLINK PCI DIO 보드 실제 구현체 (10ms 폴링)
│ └── Dask.cs # ADLINK PCIS-DASK API P/Invoke 래퍼
├── Models/ # 데이터 모델 ├── Models/ # 데이터 모델
│ ├── AppConfig.cs # 앱 설정 (Port, BaudRate, Spec UL/LL) │ ├── AppConfig.cs # 앱 설정 (Left/Right/SensorPort, Zmdi/SensorBaudRate, SpecUL/LL)
│ ├── InOutItem.cs # I/O 항목 모델 (Address, Name, Description) │ ├── DioPoint.cs # DIO 포인트 모델 (Name, IsInput, Value) + DioEventArgs
│ ├── InspectData.cs # 검사 결과 데이터 모델 │ ├── InOutItem.cs # I/O 항목 모델 (Address, Name, Description, Value)
│ └── ParsedData.cs # Sentinel C28 파싱 결과 모델 │ ├── InspectData.cs # 검사 결과 데이터 모델 (12개 필드)
│ ├── ParsedData.cs # Sentinel C28 파싱 결과 모델 (MeasuredValue, SensorJudgment 등)
│ └── SensorIdData.cs # ZMDI 센서 ID 데이터 모델 (LowID, Year/Month/Day, Serial 등)
├── Services/ # 비즈니스 로직 및 기기 제어 ├── Services/ # 비즈니스 로직 및 기기 제어
│ ├── IoBoardService.cs # I/O 보드 서비스 (더미 데이터, LEFT/RIGHT 구분) │ ├── DioBoardFactory.cs # DIO 보드 팩토리 (DioConfig.ini 기반 보드 생성)
│ └── SentinelC28Service.cs # Sentinel C28 프로토콜 (자동 재연결 포함) │ ├── IoBoardService.cs # I/O 보드 서비스 (IDioBoard 연동, 상태 갱신/출력 제어)
│ ├── SentinelC28Service.cs # Sentinel C28 프로토콜 (자동 재연결 포함)
│ ├── TestProcessService.cs # 자동 시험 프로세스 (DIO→센서→시험→판정→출력 사이클)
│ └── ZmdiSensorService.cs # ZMDI 센서 시리얼 통신 (ID 읽기/파싱, 4단계 명령)
├── Utils/ # 유틸리티 ├── Utils/ # 유틸리티
│ ├── ConfigManager.cs # XML 기반 설정 관리 (Load/Save) │ ├── ConfigManager.cs # XML 기반 설정 관리 (Load/Save, ConfigChanged 이벤트)
│ ├── CsvExporter.cs # 범용 CSV 내보내기 │ ├── CsvExporter.cs # 범용 CSV 내보내기
│ ├── DioConfigParser.cs # INI 형식 DIO 설정 파서 (보드 타입, 포인트 매핑)
│ ├── FileLogger.cs # 일일 CSV 로그 자동 저장 + 시스템 로그 │ ├── FileLogger.cs # 일일 CSV 로그 자동 저장 + 시스템 로그
│ ├── LogParser.cs # CSV 로그 파싱 및 검색 필터링 │ ├── LogParser.cs # CSV 로그 파싱 및 검색 필터링
│ ├── SentinelCrc8.cs # CRC-8 체크섬 계산 │ ├── SentinelCrc8.cs # CRC-8 체크섬 계산
│ └── SentinelParser.cs # C28 데이터 파싱 (스트리밍/최종결과) │ └── SentinelParser.cs # C28 데이터 파싱 (스트리밍/최종결과/판정)
├── ViewModels/ # MVVM ViewModel ├── ViewModels/ # MVVM ViewModel
│ ├── Core/ │ ├── Core/
│ │ ├── ObservableObject.cs # INotifyPropertyChanged 베이스 클래스 │ │ ├── ObservableObject.cs # INotifyPropertyChanged 베이스 클래스
│ │ └── RelayCommand.cs # ICommand 구현 │ │ └── RelayCommand.cs # ICommand 구현
│ ├── DataViewModel.cs # 데이터 조회/CSV 내보내기 로직 │ ├── DataViewModel.cs # 데이터 조회/CSV 내보내기/더미 데이터 로드
│ ├── HomeViewModel.cs # 2채널 통신, 측정, 판정, 로그 기록 │ ├── HomeViewModel.cs # 2채널 자동 시험, ZMDI/C28 통신, SPEC 교차 검증
│ ├── InOutViewModel.cs # I/O 모니터 페이징 로직 │ ├── InOutViewModel.cs # I/O 모니터 페이징 로직
│ ├── MainViewModel.cs # 화면 전환 앱 생명주기 관리 │ ├── MainViewModel.cs # 화면 전환, DIO 보드 생성, 앱 생명주기 관리
│ └── ParametersViewModel.cs # 파라미터 설정 (예비) │ └── ParametersViewModel.cs # 파라미터 설정 (빈 클래스, 로직은 Window에서 직접 처리)
├── Views/ # UI 화면 ├── Views/ # UI 화면
│ ├── CommunicationWindow.xaml(.cs) # 통신 설정 (포트/보드레이트) │ ├── CommunicationWindow.xaml(.cs) # 통신 설정 (포트/보드레이트)
│ ├── DataView.xaml(.cs) # 검사 이력 조회/검색/CSV 저장 │ ├── DataView.xaml(.cs) # 검사 이력 조회/검색/CSV 저장
│ ├── HomeView.xaml(.cs) # 좌/우 2채널 시험 모니터링 │ ├── HomeView.xaml(.cs) # 좌/우 2채널 시험 모니터링
│ ├── InOutView.xaml(.cs) # I/O 모니터 (LEFT/RIGHT 구분) │ ├── InOutView.xaml(.cs) # I/O 모니터 (INPUT/OUTPUT 구분)
│ └── ParametersWindow.xaml(.cs) # SPEC UL/LL 설정 │ └── ParametersWindow.xaml(.cs) # SPEC UL/LL 설정
├── Manual/ # 사용자 매뉴얼
│ ├── Communication_Manual.md # 통신 설정 매뉴얼
│ ├── DataView_Manual.md # 데이터 조회 매뉴얼
│ ├── HomeView_Manual.md # 메인 화면 매뉴얼
│ ├── IO_Monitor_Manual.md # I/O 모니터 매뉴얼
│ └── Parameter_Manual.md # 파라미터 설정 매뉴얼
├── PCI-Dask.dll # ADLINK PCIS-DASK 네이티브 DLL
├── App.xaml # DataTemplate 매핑 + 공통 스타일 ├── App.xaml # DataTemplate 매핑 + 공통 스타일
├── MainWindow.xaml # 메인 Shell (상단바 + 하단 메뉴) ├── MainWindow.xaml # 메인 Shell (상단바 + 하단 메뉴)
└── MainWindow.xaml.cs # 시계, 창 관리, 종료 확인 └── MainWindow.xaml.cs # 시계, 창 관리, 종료 확인
leak_test_project.Tests/ # 단위 테스트 프로젝트 (xUnit/NUnit)
``` ```
## ✨ 주요 기능 ## ✨ 주요 기능
@ -50,64 +68,91 @@ leak_test_project/
- 좌(LEFT) / 우(RIGHT) 독립 2채널 기밀 시험 상태 실시간 표시 - 좌(LEFT) / 우(RIGHT) 독립 2채널 기밀 시험 상태 실시간 표시
- 측정값(sccm), SPEC 범위(UL/LL), 판정(OK/NG) 실시간 확인 - 측정값(sccm), SPEC 범위(UL/LL), 판정(OK/NG) 실시간 확인
- 판정 결과에 따른 배경색 자동 변경 (OK=녹색, NG=빨간색) - 판정 결과에 따른 배경색 자동 변경 (OK=녹색, NG=빨간색)
- 하단 제품/센서 통신 로그 출력 (최대 500줄 유지, 자동 스크롤) - 좌/우 채널별 상태(Status) 및 오류(Error) 메시지 표시
- ZMDI 센서 ID 정보 (LowID, 제조일, 시리얼, 라인, 제품 구분) 실시간 표시
### 2. Sentinel C28 통신 제어
- **RS-232 시리얼 통신**: 기본 115200 baud, 8N1 ### 2. 자동 시험 프로세스 (TestProcessService)
DIO 시작 신호를 기반으로 좌/우 독립 백그라운드 스레드에서 아래 사이클을 반복합니다:
1. **DIO 시작 신호 대기**: LEFT_START / RIGHT_START 입력 신호 OFF→ON 감지
2. **ZMDI 센서 ID 읽기**: 4단계 시리얼 명령 시퀀스로 제품 ID 파싱
3. **불량 제품 필터링**: 센서의 이전 검사 결과(PrevResult) 확인
4. **LEAK 시험 수행**: Sentinel C28 최종 결과 수신 대기 (30초 타임아웃)
5. **판정**: 프로그램 SPEC(UL/LL) 기반 OK/NG 판정
6. **SPEC 교차 검증**: 프로그램 판정과 센서 자체 판정(A/R) 비교, 불일치 시 경고
7. **로그 기록**: 일일 CSV 파일에 검사 결과 자동 저장
8. **DIO 출력**: 판정 결과에 따라 OK/NG 출력 신호 전송
### 3. DIO 보드 제어 (ADLINK PCI)
- **ADLINK PCI DIO 보드** 연동 (PCIS-DASK API 사용)
- **INI 설정 기반** 입출력 포인트 매핑 (`Settings/DioConfig.ini`)
- **10ms 입력 폴링**: 백그라운드 Task에서 입력 상태 주기적 감시
- **PC_ON 출력**: 프로그램 시작 시 가동 신호 ON, 종료 시 OFF
- **지원 보드 타입**: PCI_7230, PCI_7432, PCI_7433, PCI_7434, PCI_7250
### 4. Sentinel C28 통신 제어
- **RS-232 시리얼 통신**: 기본 9600 baud, 8N1
- **표준 프로토콜 준수**: 헤더(XXYYZZZ H) + CRC-8 체크섬 자동 생성 - **표준 프로토콜 준수**: 헤더(XXYYZZZ H) + CRC-8 체크섬 자동 생성
- **다중 데이터 처리**: 최종 결과(R), 실시간 스트리밍(S), 메시지(M) 구분 수신 - **다중 데이터 처리**: 최종 결과(R), 실시간 스트리밍(S), 메시지(M) 구분 수신
- **자동 재연결**: 연결 끊김 시 5초 간격 자동 재연결 시도 - **자동 재연결**: 연결 끊김 시 **1초(1000ms) 간격**으로 모든 통신(C28, ZMDI)의 자동 복구를 시도하여 사용자 개입 최소화
- **확장성**: ICommunication 인터페이스 기반으로 TCP/IP 등 통신 방식 변경 가능 - **확장성**: ICommunication 인터페이스 기반으로 TCP/IP 등 통신 방식 변경 가능
### 3. 입출력 모니터 (In/Out Monitor) ### 5. 입출력 모니터 (In/Out Monitor)
- INPUT / OUTPUT 신호 상태 확인 (LEFT/RIGHT 구분) - INPUT / OUTPUT 신호 상태 확인 (실제 DIO 보드 연동)
- **500ms 주기 자동 갱신**: 별도의 새로고침 버튼 없이 DIO 상태가 실시간 업데이트
- 페이지당 30개씩 데이터 표시 및 페이징 기능 (FIRST/PREV/NEXT/LAST) - 페이지당 30개씩 데이터 표시 및 페이징 기능 (FIRST/PREV/NEXT/LAST)
- 화면 닫기(Close) 시 자동 갱신 타이머 정지
### 4. 데이터 이력 조회 (Data) ### 6. 데이터 이력 조회 (Data)
- 기간별, 판정 결과별(OK/NG/전체), 시리얼 번호별 필터링 검색 - 기간별, 판정 결과별(OK/NG/전체), 시리얼 번호별 필터링 검색
- 일일 CSV 로그 자동 저장 (검사 완료 시 `Logs/yyyy-MM-dd.csv`에 즉시 기록) - 일일 CSV 로그 자동 저장 (검사 완료 시 `Logs/yyyy-MM-dd.csv`에 즉시 기록)
- 조회된 데이터를 별도 CSV 파일로 내보내기 가능 - 조회된 데이터를 별도 CSV 파일로 내보내기 가능
- 빈 화면에서도 엑셀 시트처럼 항상 표 격자(GridLines)가 유동적으로 유지되도록 표출 전용 패딩 적용 - 빈 화면에서도 엑셀 시트처럼 항상 표 격자(GridLines)가 유동적으로 유지되도록 최소 50행 표출
- 로그 파일이 없는 경우 더미 데이터 자동 생성 - 테스트용 더미 데이터 수동 로드 기능 (버튼 클릭으로 10건 샘플 데이터 생성)
### 5. 설정 관리 ### 7. 설정 관리
- **파라미터 설정**: SPEC UL/LL (sccm) 값 설정 및 저장 - **파라미터 설정**: SPEC UL/LL (sccm) 값 설정 및 저장
- **통신 설정**: 좌/우 메인(ZMDI) 장비 포트와 센서(Leak Sensor) 포트의 통신 속도를 각각 독립적으로 분리 설정 (ZMDI 기본 19200, 센서 기본 9600) - **통신 설정**: 좌/우 ZMDI 장비 포트(기본 COM9/COM8, 보드레이트 공유 19200)와 센서 포트(기본 COM1, 보드레이트 9600)를 각각 독립 설정
- **XML 설정 파일**: `config.xml`에 자동 저장/로드, 설정 변경 시 실시간 반영 - **XML 설정 파일**: `config.xml`에 자동 저장/로드, 설정 변경 시 `ConfigChanged` 이벤트를 통해 모든 통신 서비스(Sentinel C28, ZMDI 센서)와 자동 시험 프로세스가 재시작되어 즉시 반영
- **DIO 설정 파일**: `Settings/DioConfig.ini`에서 보드 타입, 입출력 포인트 이름/설명 관리
### 6. 일일 CSV 로그 자동 저장 ### 8. 일일 CSV 로그 자동 저장
- 검사 완료 시 `Logs/yyyy-MM-dd.csv` 파일에 자동 기록 - 검사 완료 시 `Logs/yyyy-MM-dd.csv` 파일에 자동 기록
- CSV 헤더: Date, Time, Channel, ID, Value, Judgment, Mode, LineNo, ProductType, SpecUL, SpecLL, Retest - CSV 헤더: Date, Time, Channel, ID, Value, Judgment, Mode, LineNo, ProductType, SpecUL, SpecLL, Retest
- CSV 값 이스케이프 처리 (쉼표/따옴표/줄바꿈 안전 처리) - CSV 값 이스케이프 처리 (쉼표/따옴표/줄바꿈 안전 처리)
- 시스템 텍스트 로그는 `yyyy-MM-dd_system.log`로 별도 관리 - 시스템 텍스트 로그는 `yyyy-MM-dd_system.log`로 별도 관리
### 7. 프로그램 안정성 ### 9. 프로그램 안정성
- 프로그램 종료 전 확인 대화상자 표시 - 프로그램 종료 전 확인 대화상자 표시
- 시리얼 포트, 타이머 등 리소스 종료 시 안전 해제 - 시리얼 포트, DIO 보드, 타이머 등 리소스 종료 시 안전 해제 (IDisposable 패턴)
- 모든 통신/파일 I/O 구간에 예외 처리 적용 - 모든 통신/파일 I/O 구간에 예외 처리 적용
- UI 로그 메모리 누수 방지 (최대 500줄 제한) - ZMDI 통신 명령 실패 시 최대 3회 재시도
- **포트 연결 알림**: 프로그램 초기화 또는 설정 변경 시 모든 포트(C28, ZMDI Left/Right)의 연결 상태를 즉시 확인하고, 하나라도 실패 시 메인 화면 오류 영역에 즉시 표시 (상세 오류 원인 및 포트 번호 포함)
- **1초 자동 복구**: 모든 통신 포트에 대해 1초 주기로 상시 자동 재연결 시도 (재진입 방지 포함)
- **통신 설정 변경 반영**: 설정 저장 시 즉시 통신 포트와 자동 시험 프로세스를 재시작하여 프로그램 재시작 없이 설정 변경 내용 적용 가능
## 📡 기기 통신 프로토콜 및 명령어 (Command Details) ## 📡 기기 통신 프로토콜 및 명령어 (Command Details)
프로그램에서 사용하는 주요 하드웨어 통신 명령어 및 절차입니다. 프로그램에서 사용하는 주요 하드웨어 통신 명령어 및 절차입니다.
### 1. ZMDI 센서 (ID 읽기 및 파싱) ### 1. ZMDI 센서 (ID 읽기 및 파싱)
ZMDI 센서와의 통신은 `19200 Baud, 8N1` 시리얼 통신을 사용하며, 총 4단계의 명령어 시퀀스로 구성됩니다. 모든 명령어는 `\r\n`을 포함하여 전송니다. ZMDI 센서와의 통신은 `19200 Baud, 8N1` 시리얼 통신을 사용하며, 총 4단계의 명령어 시퀀스로 구성됩니다. 모든 명령어는 `\r\n`을 포함하여 전송되며, 최대 3회 재시도합니다.
| 단계 | 역할 | 주요 명령어 리스트 | | 단계 | 역할 | 주요 명령어 리스트 |
| :--- | :--- | :--- | | :--- | :--- | :--- |
| **1단계** | 초기화 및 통신 확인 | `V`, `Pr_D7`, `Pr_D6`, `Pr_D5`, `r` | | **1단계** | 초기화 및 통신 확인 | `V`, `Pr_D7`, `Pr_D6`, `Pr_D5`, `r` |
| **2단계** | 메모리 접근 준비 | `tso31150` | | **2단계** | 메모리 접근 준비 | `tso31150` |
| **3단계** | 데이터 수집 설정 | `os_10`, `t11005`, `OWT7800272D1`, `OR_78002`, `OW_78003...` | | **3단계** | 데이터 수집 설정 | `os_10`, `t11005`, `OWT7800272D1`, `OR_78002`, `OW_780038AA55A`, `OW_780011A`, `OR_78002`, `OW_780038AFF00`, `OW_78001CF`, `OR_78004` |
| **4단계** | ID 메모리 읽기 | `OW_7800140`, `OR_78002`, `OW_7800141`, `OR_78002` | | **4단계** | ID 메모리 읽기 | `OW_7800140`, `OR_78002`, `OW_7800141`, `OR_78002`, `OW_7800142`, `OR_78002`, `x9c_990:x` |
- **읽기 종료**: 모든 데이터를 읽은 후 `x9c_990:x` 명령으로 세션을 종료합니다. - **읽기 종료**: 모든 데이터를 읽은 후 `x9c_990:x` 명령으로 세션을 종료합니다.
- **데이터 파싱**: `OR_78002`의 응답값들을 조합하여 12자리 이상의 `LowID`를 생성하고, 이를 디코딩 테이블에 따라 년/월/일/시리얼/라인/아이템 정보를 추출합니다. - **데이터 파싱**: `OR_78002`의 응답값들을 조합하여 12자리 이상의 `LowID`를 생성하고, 이를 디코딩 테이블에 따라 년/월/일/시리얼/라인/아이템 정보를 추출합니다.
- **불량 필터링**: 파싱된 `PrevResult` 필드로 이전 검사 결과를 확인하여 불량 제품을 필터링합니다.
--- ---
### 2. Sentinel C28 (Leak Test 장비) ### 2. Sentinel C28 (Leak Test 장비)
Sentinel C28과의 통신은 `115200 Baud, 8N1` 시리얼 통신을 사용하며, 전용 바이너리/텍스트 혼합 프로토콜을 사용합니다. Sentinel C28과의 통신은 `9600 Baud, 8N1` 시리얼 통신을 사용하며, 전용 바이너리/텍스트 혼합 프로토콜을 사용합니다.
- **명령어 구조**: `[CRC-8][Sequence][Length] [Type][Command]\r\n` - **명령어 구조**: `[CRC-8][Sequence][Length] [Type][Command]\r\n`
- `CRC-8`: 헤더와 페이로드를 포함한 8비트 체크섬 (2자리 Hex) - `CRC-8`: 헤더와 페이로드를 포함한 8비트 체크섬 (2자리 Hex)
@ -120,6 +165,31 @@ Sentinel C28과의 통신은 `115200 Baud, 8N1` 시리얼 통신을 사용하며
- `R (Result)`: 최종 검사 완료 후 수신되는 결과 데이터 (채널#, 시리얼, 측정값, 판정 등 포함) - `R (Result)`: 최종 검사 완료 후 수신되는 결과 데이터 (채널#, 시리얼, 측정값, 판정 등 포함)
- `S (Streaming)`: 시험 진행 중 실시간으로 수신되는 압력/유량 데이터 - `S (Streaming)`: 시험 진행 중 실시간으로 수신되는 압력/유량 데이터
- **SPEC 교차 검증**: C28 센서의 자체 판정(A=Accept, R=Reject)과 프로그램의 UL/LL 기반 판정을 비교하여 불일치 시 경고 메시지를 표시합니다.
---
### 3. ADLINK DIO 보드
ADLINK PCI DIO 보드와의 통신은 PCIS-DASK API(PCI-Dask.dll)를 사용합니다.
- **설정 파일**: `Settings/DioConfig.ini` (INI 형식)
- `[DIO]`: CompanyName
- `[BOARD_1]`: BoardType, BoardNumber
- `[BOARD_1_IN]`: 입력 포인트 매핑 (LEFT_START, RIGHT_START 등)
- `[BOARD_1_OUT]`: 출력 포인트 매핑 (LEFT_OK, RIGHT_OK, LEFT_NG, RIGHT_NG, PC_ON 등)
- **기본 입출력 신호**:
| 구분 | 포인트 이름 | 설명 |
| :--- | :--- | :--- |
| INPUT | `LEFT_START` | 좌측 시험 시작 신호 |
| INPUT | `RIGHT_START` | 우측 시험 시작 신호 |
| OUTPUT | `LEFT_OK` | 좌측 합격 출력 |
| OUTPUT | `RIGHT_OK` | 우측 합격 출력 |
| OUTPUT | `LEFT_NG` | 좌측 불합격 출력 |
| OUTPUT | `RIGHT_NG` | 우측 불합격 출력 |
| OUTPUT | `PC_ON` | PC 가동 신호 |
--- ---
## 🛠 유지보수 가이드 ## 🛠 유지보수 가이드
@ -129,8 +199,23 @@ Sentinel C28과의 통신은 `115200 Baud, 8N1` 시리얼 통신을 사용하며
- **CRC 알고리즘**: 제조사 사양서에 정확한 다항식이 명시되어 있다면 `Utils/SentinelCrc8.cs` 교체 필요 - **CRC 알고리즘**: 제조사 사양서에 정확한 다항식이 명시되어 있다면 `Utils/SentinelCrc8.cs` 교체 필요
- **화면 및 스타일**: `Views/``.xaml` 파일과 `App.xaml`에서 통합 관리 - **화면 및 스타일**: `Views/``.xaml` 파일과 `App.xaml`에서 통합 관리
- **설정 항목 추가**: `Models/AppConfig.cs`에 프로퍼티 추가 후 해당 설정 화면 UI 수정 - **설정 항목 추가**: `Models/AppConfig.cs`에 프로퍼티 추가 후 해당 설정 화면 UI 수정
- **DIO 포인트 변경**: `Settings/DioConfig.ini`에서 입출력 포인트 이름/설명을 수정하면 프로그램 재시작 시 반영
- **DIO 보드 타입 추가**: `Infrastructure/RealDioBoard.cs``GetCardTypeFromConfig()` 메서드에 새 보드 타입 매핑 추가
- **시험 프로세스 수정**: `Services/TestProcessService.cs``ProcessProc()` 메서드에서 시험 단계 추가/변경
- **ZMDI 명령 시퀀스 수정**: `Services/ZmdiSensorService.cs``_commandList1~4` 배열 수정
- **ID 디코딩 테이블 수정**: `Services/ZmdiSensorService.cs``_yearHexList`, `_monthList`, `_dayHexList` 등 업데이트
- **사용자 매뉴얼**: `Manual/` 폴더에서 각 화면별 사용 설명서 확인 및 수정 가능
## 🧪 테스트
`leak_test_project.Tests` 프로젝트에서 핵심 서비스의 단위 테스트를 실행할 수 있습니다.
```bash
dotnet test leak_test_project.Tests/leak_test_project.Tests.csproj
```
## 🚀 실행 방법 ## 🚀 실행 방법
1. Visual Studio에서 `leak_test_project.slnx` 파일을 엽니다. 1. Visual Studio에서 `leak_test_project.slnx` 파일을 엽니다.
2. `F5` 키를 눌러 실행하거나 빌드 후 `bin/Debug/leak_test_project.exe`를 실행합니다. 2. `F5` 키를 눌러 실행하거나 빌드 후 `bin/Debug/leak_test_project.exe`를 실행합니다.
3. 로그 파일은 실행 파일과 동일한 경로의 `Logs/` 폴더에 자동 생성됩니다. 3. 로그 파일은 실행 파일과 동일한 경로의 `Logs/` 폴더에 자동 생성됩니다.
4. DIO 설정 파일은 `Settings/DioConfig.ini`에 자동 생성됩니다 (없을 경우 기본값으로 생성).

BIN
leak_test_project.Tests/bin/Debug/net472/leak_test_project.Tests.dll

Binary file not shown.

BIN
leak_test_project.Tests/bin/Debug/net472/leak_test_project.Tests.pdb

Binary file not shown.

2
leak_test_project.Tests/obj/Debug/net472/leak_test_project.Tests.AssemblyInfo.cs

@ -13,7 +13,7 @@ using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("leak_test_project.Tests")] [assembly: System.Reflection.AssemblyCompanyAttribute("leak_test_project.Tests")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")] [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+463b017b2f6c3c93b007d983e892d11a3b467b19")]
[assembly: System.Reflection.AssemblyProductAttribute("leak_test_project.Tests")] [assembly: System.Reflection.AssemblyProductAttribute("leak_test_project.Tests")]
[assembly: System.Reflection.AssemblyTitleAttribute("leak_test_project.Tests")] [assembly: System.Reflection.AssemblyTitleAttribute("leak_test_project.Tests")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]

2
leak_test_project.Tests/obj/Debug/net472/leak_test_project.Tests.AssemblyInfoInputs.cache

@ -1 +1 @@
845b7ec27b60caf36b7a39a6306fad190b6fe978d5196d358c2ca638ded0577b 2ea65620aa90120c9b7dbe76d8e00969f1b68490ac09214d6e8be837d3b86854

BIN
leak_test_project.Tests/obj/Debug/net472/leak_test_project.Tests.dll

Binary file not shown.

BIN
leak_test_project.Tests/obj/Debug/net472/leak_test_project.Tests.pdb

Binary file not shown.

51
leak_test_project/Infrastructure/RealDioBoard.cs

@ -148,33 +148,44 @@ namespace leak_test_project.Infrastructure
{ {
while (!token.IsCancellationRequested) while (!token.IsCancellationRequested)
{ {
if (_cardNumber >= 0) try
{ {
ushort port = 0; if (_cardNumber >= 0)
uint readValue;
short ret = DASK.DI_ReadPort((ushort)_cardNumber, port, out readValue);
if (ret >= 0)
{ {
var inputPoints = new List<DioPoint>(_inputs.Values); ushort port = 0;
for (int i = 0; i < inputPoints.Count; i++) uint readValue;
short ret = DASK.DI_ReadPort((ushort)_cardNumber, port, out readValue);
if (ret >= 0)
{ {
var point = inputPoints[i]; var inputPoints = new List<DioPoint>(_inputs.Values);
bool isCurrentlyOn = (readValue & (1U << i)) != 0; for (int i = 0; i < inputPoints.Count; i++)
// If changed from OFF to ON
if (isCurrentlyOn && !point.Value)
{
point.Value = true;
Console.WriteLine($"[RealDioBoard] Input {point.Name} triggered (OFF→ON)");
InputChanged?.Invoke(this, new DioEventArgs(point.Name, true));
}
else if (!isCurrentlyOn && point.Value)
{ {
point.Value = false; var point = inputPoints[i];
bool isCurrentlyOn = (readValue & (1U << i)) != 0;
// If changed from OFF to ON
if (isCurrentlyOn && !point.Value)
{
point.Value = true;
Console.WriteLine($"[RealDioBoard] Input {point.Name} triggered (OFF→ON)");
InputChanged?.Invoke(this, new DioEventArgs(point.Name, true));
}
else if (!isCurrentlyOn && point.Value)
{
point.Value = false;
}
} }
} }
else
{
FileLogger.Log("ERROR", $"[RealDioBoard] DI_ReadPort Error: {ret}");
}
} }
} }
catch (Exception ex)
{
FileLogger.Log("ERROR", $"[RealDioBoard] Inner Polling Error: {ex.Message}");
}
// 10ms polling rate (approx) // 10ms polling rate (approx)
await Task.Delay(10, token); await Task.Delay(10, token);
@ -186,7 +197,7 @@ namespace leak_test_project.Infrastructure
} }
catch (Exception ex) catch (Exception ex)
{ {
Console.WriteLine($"[RealDioBoard] Polling error: {ex.Message}"); FileLogger.Log("ERROR", $"[RealDioBoard] Critical Polling Loop Error: {ex.Message}");
} }
} }

189
leak_test_project/MainWindow.xaml

@ -3,6 +3,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewmodels="clr-namespace:leak_test_project.ViewModels" xmlns:viewmodels="clr-namespace:leak_test_project.ViewModels"
Title="Pressure Leak Inspect System" Height="800" Width="1024" Title="Pressure Leak Inspect System" Height="800" Width="1024"
MinWidth="1150" MinHeight="700"
WindowState="Maximized" Background="#F0F0F0" WindowStyle="None"> WindowState="Maximized" Background="#F0F0F0" WindowStyle="None">
<Window.DataContext> <Window.DataContext>
@ -10,105 +11,105 @@
</Window.DataContext> </Window.DataContext>
<Grid> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="55"/> <!-- Increased slightly for visibility --> <RowDefinition Height="55"/> <!-- Increased slightly for visibility -->
<RowDefinition Height="*"/> <RowDefinition Height="*"/>
<RowDefinition Height="75"/> <!-- Increased slightly for visibility --> <RowDefinition Height="75"/> <!-- Increased slightly for visibility -->
</Grid.RowDefinitions> </Grid.RowDefinitions>
<!-- Top Bar: Unified Dark Blue Theme --> <!-- Top Bar: Unified Dark Blue Theme -->
<Border Grid.Row="0" Background="#2C3E50" BorderBrush="#1A252F" BorderThickness="0,0,0,2" MouseLeftButtonDown="TopBar_MouseLeftButtonDown"> <Border Grid.Row="0" Background="#2C3E50" BorderBrush="#1A252F" BorderThickness="0,0,0,2" MouseLeftButtonDown="TopBar_MouseLeftButtonDown">
<Grid> <Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Orientation="Horizontal" VerticalAlignment="Center" Margin="20,0,0,0"> <StackPanel Grid.Column="0" Orientation="Horizontal" VerticalAlignment="Center" Margin="20,0,0,0">
<TextBlock Text="Pressure Leak Inspect System" Foreground="White" FontSize="24" FontWeight="Bold"/> <TextBlock Text="Pressure Leak Inspect System" Foreground="White" FontSize="24" FontWeight="Bold"/>
</StackPanel> </StackPanel>
<!-- Info Box --> <!-- Info Box -->
<StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="0,0,20,0"> <StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="0,0,20,0">
<Border Background="#34495E" CornerRadius="4" Padding="15,5" BorderBrush="#1A252F" BorderThickness="1"> <Border Background="#34495E" CornerRadius="4" Padding="15,5" BorderBrush="#1A252F" BorderThickness="1" Margin="0,0,8,0">
<StackPanel> <TextBlock x:Name="txtCurrentDateTime" Text="2026-03-25 [수] 10:30:00" Foreground="White" FontSize="17" FontWeight="SemiBold" VerticalAlignment="Center"/>
<TextBlock x:Name="txtCurrentDateTime" Text="2026-03-25 [수] 10:30:00" Foreground="White" FontSize="11" FontWeight="SemiBold" HorizontalAlignment="Right"/> </Border>
<TextBlock Text="Ver. 1.1.0 Updated 2026-03-25" Foreground="#BDC3C7" FontSize="10" HorizontalAlignment="Right"/> <Border Background="#34495E" CornerRadius="4" Padding="15,5" BorderBrush="#1A252F" BorderThickness="1">
</StackPanel> <TextBlock Text="Ver. 1.0.0 | Updated 2026-04-03" Foreground="#BDC3C7" FontSize="11" VerticalAlignment="Center"/>
</Border> </Border>
</StackPanel> </StackPanel>
<!-- System Buttons --> <!-- System Buttons -->
<StackPanel Grid.Column="2" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="0,0,10,0"> <StackPanel Grid.Column="2" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="0,0,10,0">
<Button Width="40" Height="40" Background="Transparent" BorderThickness="0" Click="BtnMinimize_Click" ToolTip="최소화"> <Button Width="40" Height="40" Background="Transparent" BorderThickness="0" Click="BtnMinimize_Click" ToolTip="최소화">
<TextBlock Text="0" FontFamily="Marlett" FontSize="18" Foreground="White" VerticalAlignment="Center" HorizontalAlignment="Center"/> <TextBlock Text="0" FontFamily="Marlett" FontSize="18" Foreground="White" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Button> </Button>
<Button x:Name="btnMaximize" Width="40" Height="40" Background="Transparent" BorderThickness="0" Click="BtnMaximize_Click" ToolTip="창 모드"> <Button x:Name="btnMaximize" Width="40" Height="40" Background="Transparent" BorderThickness="0" Click="BtnMaximize_Click" ToolTip="창 모드">
<TextBlock x:Name="txtMaximizeIcon" Text="2" FontFamily="Marlett" FontSize="18" Foreground="White" VerticalAlignment="Center" HorizontalAlignment="Center"/> <TextBlock x:Name="txtMaximizeIcon" Text="2" FontFamily="Marlett" FontSize="18" Foreground="White" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Button> </Button>
<Button Width="40" Height="40" Background="Transparent" BorderThickness="0" Command="{Binding ExitCommand}" ToolTip="프로그램 종료"> <Button Width="40" Height="40" Background="Transparent" BorderThickness="0" Command="{Binding ExitCommand}" ToolTip="프로그램 종료">
<TextBlock Text="r" FontFamily="Marlett" FontSize="18" Foreground="#E74C3C" VerticalAlignment="Center" HorizontalAlignment="Center"/> <TextBlock Text="r" FontFamily="Marlett" FontSize="18" Foreground="#E74C3C" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Button> </Button>
</StackPanel> </StackPanel>
</Grid> </Grid>
</Border> </Border>
<!-- Main Content Area --> <!-- Main Content Area -->
<ContentControl Grid.Row="1" Content="{Binding CurrentViewModel}" /> <ContentControl Grid.Row="1" Content="{Binding CurrentViewModel}" />
<!-- Bottom Menu: Unified Aesthetics --> <!-- Bottom Menu: Unified Aesthetics -->
<Border Grid.Row="2" Background="#D5DBDB" BorderBrush="#AEB6BF" BorderThickness="0,2,0,0"> <Border Grid.Row="2" Background="#D5DBDB" BorderBrush="#AEB6BF" BorderThickness="0,2,0,0">
<Grid> <Grid>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" Margin="15,0"> <StackPanel Orientation="Horizontal" VerticalAlignment="Center" Margin="15,0">
<StackPanel.Resources> <StackPanel.Resources>
<Style TargetType="Button"> <Style TargetType="Button">
<Setter Property="Width" Value="140"/> <Setter Property="Width" Value="140"/>
<Setter Property="Height" Value="55"/> <Setter Property="Height" Value="55"/>
<Setter Property="Margin" Value="0,0,10,0"/> <Setter Property="Margin" Value="0,0,10,0"/>
<Setter Property="Background" Value="#F2F4F4"/> <Setter Property="Background" Value="#F2F4F4"/>
<Setter Property="BorderBrush" Value="#85929E"/> <Setter Property="BorderBrush" Value="#85929E"/>
<Setter Property="BorderThickness" Value="1.5"/> <Setter Property="BorderThickness" Value="1.5"/>
<Style.Triggers> <Style.Triggers>
<Trigger Property="IsMouseOver" Value="True"> <Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#E5E8E8"/> <Setter Property="Background" Value="#E5E8E8"/>
</Trigger> </Trigger>
</Style.Triggers> </Style.Triggers>
</Style> </Style>
<Style TargetType="TextBlock"> <Style TargetType="TextBlock">
<Setter Property="FontSize" Value="14"/> <Setter Property="FontSize" Value="14"/>
<Setter Property="FontWeight" Value="Bold"/> <Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Foreground" Value="#2C3E50"/> <Setter Property="Foreground" Value="#2C3E50"/>
<Setter Property="HorizontalAlignment" Value="Center"/> <Setter Property="HorizontalAlignment" Value="Center"/>
</Style> </Style>
</StackPanel.Resources> </StackPanel.Resources>
<Button Command="{Binding NavigateInOutCommand}"> <Button Command="{Binding NavigateInOutCommand}">
<StackPanel> <StackPanel>
<TextBlock Text="I/O Monitor" FontSize="14"/> <TextBlock Text="I/O Monitor" FontSize="14"/>
<TextBlock Text="(입출력 감시)" FontSize="10" FontWeight="Normal" Foreground="#566573"/> <TextBlock Text="(입출력 감시)" FontSize="10" FontWeight="Normal" Foreground="#566573"/>
</StackPanel> </StackPanel>
</Button> </Button>
<Button Command="{Binding NavigateDataCommand}"> <Button Command="{Binding NavigateDataCommand}">
<StackPanel> <StackPanel>
<TextBlock Text="Data" FontSize="14"/> <TextBlock Text="Data" FontSize="14"/>
<TextBlock Text="(측정 데이터)" FontSize="10" FontWeight="Normal" Foreground="#566573"/> <TextBlock Text="(측정 데이터)" FontSize="10" FontWeight="Normal" Foreground="#566573"/>
</StackPanel> </StackPanel>
</Button> </Button>
<Button Command="{Binding NavigateParametersCommand}"> <Button Command="{Binding NavigateParametersCommand}">
<StackPanel> <StackPanel>
<TextBlock Text="Parameters" FontSize="14"/> <TextBlock Text="Parameters" FontSize="14"/>
<TextBlock Text="(시험 설정)" FontSize="10" FontWeight="Normal" Foreground="#566573"/> <TextBlock Text="(시험 설정)" FontSize="10" FontWeight="Normal" Foreground="#566573"/>
</StackPanel> </StackPanel>
</Button> </Button>
<Button Command="{Binding NavigateCommunicationCommand}"> <Button Command="{Binding NavigateCommunicationCommand}">
<StackPanel> <StackPanel>
<TextBlock Text="Comm. Settings" FontSize="14"/> <TextBlock Text="Comm. Settings" FontSize="14"/>
<TextBlock Text="(통신 설정)" FontSize="10" FontWeight="Normal" Foreground="#566573"/> <TextBlock Text="(통신 설정)" FontSize="10" FontWeight="Normal" Foreground="#566573"/>
</StackPanel> </StackPanel>
</Button> </Button>
</StackPanel> </StackPanel>
</Grid> </Grid>
</Border> </Border>
</Grid> </Grid>
</Window> </Window>

4
leak_test_project/Manual/Communication_Manual.md

@ -47,13 +47,15 @@ Sentinel C28 기기는 실제 리크 테스트를 수행하고 측정값을 전
## 5. 설정 저장 및 적용 ## 5. 설정 저장 및 적용
1. 모든 포트와 속도 설정을 완료한 후 하단의 **[설정 저장]** 버튼을 클릭합니다. 1. 모든 포트와 속도 설정을 완료한 후 하단의 **[설정 저장]** 버튼을 클릭합니다.
2. 설정값은 루트 폴더의 `config.xml` 파일에 즉시 저장됩니다. 2. 설정값은 루트 폴더의 `config.xml` 파일에 즉시 저장됩니다.
3. 저장과 동시에 통신 서비스가 재시작되어 변경된 설정이 적용됩니다. 3. 저장과 동시에 모든 통신 서비스(Sentinel C28, ZMDI 센서)와 자동 시험 프로세스가 재시작되어 변경된 설정이 즉시 적용됩니다.
--- ---
## 6. 문제 해결 (Troubleshooting) ## 6. 문제 해결 (Troubleshooting)
### 통신 실패(NG) 발생 시 체크리스트 ### 통신 실패(NG) 발생 시 체크리스트
- **포트 자동 복구 (Auto-Reconnect)**: 모든 통신 포트(Sentinel C28, ZMDI 좌/우)는 연결이 끊어질 경우 **1초(1000ms) 주기**로 연결을 자동 시도합니다. 케이블을 다시 연결하면 별도의 조치 없이 1초 이내에 정상 상태로 복구됩니다.
- **포트 연결 상태 확인**: 프로그램 시작 또는 설정 저장 시 각 포트의 연결 상태를 즉시 확인하며, 실패 시 메인 화면 오류 영역에 관련 정보를 표시합니다.
- **물리적 연결**: 케이블이 장치와 PC에 견고하게 연결되어 있는지 확인하십시오. - **물리적 연결**: 케이블이 장치와 PC에 견고하게 연결되어 있는지 확인하십시오.
- **포트 충돌**: 선택한 COM 포트가 다른 프로그램에서 사용 중이지 않은지 확인하십시오. - **포트 충돌**: 선택한 COM 포트가 다른 프로그램에서 사용 중이지 않은지 확인하십시오.
- **보드레이트 불일치**: 장치 본체 설정의 BaudRate와 프로그램의 설정값이 일치하는지 확인하십시오. - **보드레이트 불일치**: 장치 본체 설정의 BaudRate와 프로그램의 설정값이 일치하는지 확인하십시오.

2
leak_test_project/Manual/HomeView_Manual.md

@ -55,6 +55,8 @@ HomeView는 좌측(LEFT)과 우측(RIGHT) 두 개의 독립적인 채널로 구
- 현재 시험의 진행 단계(`시작 대기`, `센서 정보 읽는 중`, `LEAK 시험중`, `시험 완료` 등)를 파란색으로 표시합니다. - 현재 시험의 진행 단계(`시작 대기`, `센서 정보 읽는 중`, `LEAK 시험중`, `시험 완료` 등)를 파란색으로 표시합니다.
2. **오류 메시지 (Error Message)** 2. **오류 메시지 (Error Message)**
- 통신 실패, 센서 데이터 길이 부족 등 비정상 상황 발생 시 상세한 원인을 빨간색으로 표시합니다. - 통신 실패, 센서 데이터 길이 부족 등 비정상 상황 발생 시 상세한 원인을 빨간색으로 표시합니다.
- **포트 연결 오류 및 자동 복구**: 포트 연결 실패 시 즉시 표시되며, 시스템이 **1초 주기**로 자동 재연결을 시도합니다. 케이블을 다시 연결하면 최대 1초 내에 오류가 사라지고 정상 상태로 복구됩니다.
- **다중 오류 표시**: 여러 포트가 동시에 연결 실패한 경우 줄바꿈으로 모든 오류를 동시에 확인할 수 있습니다.
--- ---

3
leak_test_project/Manual/IO_Monitor_Manual.md

@ -40,8 +40,9 @@ I/O Monitor는 시스템의 **디지털 입력(Digital Input)** 및 **디지털
## 4. 주요 기능 ## 4. 주요 기능
### 4.1. 실시간 모니터링 (Real-time Monitoring) ### 4.1. 실시간 모니터링 (Real-time Monitoring)
- 하드웨어 DIO 보드로부터 실시간으로 상태를 읽어와 LED 아이콘에 즉시 반영합니다. - 하드웨어 DIO 보드로부터 **500ms 주기(0.5초)**로 상태를 자동으로 읽어와 LED 아이콘에 반영합니다.
- 별도의 새로고침 버튼 없이 자동으로 상태가 업데이트됩니다. - 별도의 새로고침 버튼 없이 자동으로 상태가 업데이트됩니다.
- 화면을 닫으면(Close 버튼) 자동 갱신 타이머가 정지됩니다.
### 4.2. 페이징 처리 (Paging) ### 4.2. 페이징 처리 (Paging)
- 많은 수의 I/O 접점을 효율적으로 확인하기 위해 페이징(Page) 방식을 사용합니다. - 많은 수의 I/O 접점을 효율적으로 확인하기 위해 페이징(Page) 방식을 사용합니다.

18
leak_test_project/Services/SentinelC28Service.cs

@ -36,8 +36,8 @@ namespace leak_test_project.Services
if (!isConnected && _shouldBeConnected) StartReconnectTimer(); if (!isConnected && _shouldBeConnected) StartReconnectTimer();
}; };
// 5초마다 연결 상태를 확인하고 재연결 시도 // 1초(1000ms)마다 연결 상태를 확인하고 재연결 시도
_reconnectTimer = new Timer(5000); _reconnectTimer = new Timer(1000);
_reconnectTimer.AutoReset = false; // 재진입 방지 _reconnectTimer.AutoReset = false; // 재진입 방지
_reconnectTimer.Elapsed += (s, e) => { _reconnectTimer.Elapsed += (s, e) => {
if (_shouldBeConnected && !_communication.IsOpen) if (_shouldBeConnected && !_communication.IsOpen)
@ -56,10 +56,12 @@ namespace leak_test_project.Services
}; };
} }
public void Connect() public bool Connect()
{ {
_shouldBeConnected = true; _shouldBeConnected = true;
_communication.Open(); bool opened = _communication.Open();
if (!opened) StartReconnectTimer();
return opened;
} }
public void Disconnect() public void Disconnect()
@ -86,12 +88,12 @@ namespace leak_test_project.Services
if (!_communication.Write($"{crc}{payload}\r\n")) if (!_communication.Write($"{crc}{payload}\r\n"))
{ {
Console.WriteLine("[Service] Failed to send command: Communication channel closed."); FileLogger.Log("ERROR", "[SentinelC28] Failed to send command: Communication channel closed.");
} }
_sequence = (_sequence >= 255) ? 1 : _sequence + 1; _sequence = (_sequence >= 255) ? 1 : _sequence + 1;
} catch (Exception ex) { } catch (Exception ex) {
Console.WriteLine($"[Service] Error sending command: {ex.Message}"); FileLogger.Log("ERROR", $"[SentinelC28] Error sending command: {ex.Message}");
} }
} }
@ -120,11 +122,11 @@ namespace leak_test_project.Services
break; break;
case 'M': // 일반 메시지 case 'M': // 일반 메시지
Console.WriteLine($"[C28 Message] {body}"); FileLogger.Log("INFO", $"[SentinelC28 Message] {body}");
break; break;
} }
} catch (Exception ex) { } catch (Exception ex) {
Console.WriteLine($"[Service] Error parsing received data: {ex.Message}"); FileLogger.Log("ERROR", $"[SentinelC28] Error parsing received data: {ex.Message}");
} }
} }

64
leak_test_project/Services/ZmdiSensorService.cs

@ -1,22 +1,26 @@
using System; using System;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Timers;
using leak_test_project.Infrastructure; using leak_test_project.Infrastructure;
using leak_test_project.Models; using leak_test_project.Models;
using leak_test_project.Utils;
namespace leak_test_project.Services namespace leak_test_project.Services
{ {
/// <summary> /// <summary>
/// ZMDI 센서와 시리얼 통신하여 제품 ID를 읽고 파싱하는 서비스. /// ZMDI 센서와 시리얼 통신하여 제품 ID를 읽고 파싱하는 서비스.
/// 레거시 ClsSensorReader를 새 아키텍처로 포팅. /// 자동 재연결(Auto-Reconnect) 및 예외 처리 포함.
/// 동기식 통신이므로 반드시 백그라운드 스레드에서 호출해야 합니다.
/// </summary> /// </summary>
public class ZmdiSensorService public class ZmdiSensorService : IDisposable
{ {
private readonly ICommunication _comm; private readonly ICommunication _comm;
private readonly object _commSync = new object(); private readonly object _commSync = new object();
private readonly int _sensorIndex; // 0=LEFT, 1=RIGHT private readonly int _sensorIndex; // 0=LEFT, 1=RIGHT
private System.Timers.Timer _reconnectTimer;
private bool _shouldBeConnected = false;
// 레거시 명령 시퀀스 (ClsSensorReader의 commandList1~4) 그대로 유지 // 레거시 명령 시퀀스 (ClsSensorReader의 commandList1~4) 그대로 유지
private readonly string[] _commandList1 = { "V", "Pr_D7", "Pr_D6", "Pr_D5", "r" }; private readonly string[] _commandList1 = { "V", "Pr_D7", "Pr_D6", "Pr_D5", "r" };
private readonly string[] _commandList2 = { "tso31150" }; private readonly string[] _commandList2 = { "tso31150" };
@ -48,10 +52,62 @@ namespace leak_test_project.Services
/// <summary>오류 메시지 이벤트</summary> /// <summary>오류 메시지 이벤트</summary>
public event EventHandler<string> ErrorMessage; public event EventHandler<string> ErrorMessage;
/// <summary>연결 상태 변경 이벤트</summary>
public event EventHandler<bool> ConnectionChanged;
public ZmdiSensorService(ICommunication communication, int sensorIndex) public ZmdiSensorService(ICommunication communication, int sensorIndex)
{ {
_comm = communication; _comm = communication;
_sensorIndex = sensorIndex; _sensorIndex = sensorIndex;
_comm.ConnectionStatusChanged += (s, isConnected) => {
ConnectionChanged?.Invoke(this, isConnected);
if (!isConnected && _shouldBeConnected) StartReconnectTimer();
};
// 1초(1000ms)마다 연결 상태를 확인하고 재연결 시도
_reconnectTimer = new System.Timers.Timer(1000);
_reconnectTimer.AutoReset = false; // 재진입 방지
_reconnectTimer.Elapsed += (s, e) => {
if (_shouldBeConnected && !_comm.IsOpen)
{
if (!_comm.Open())
{
if (_shouldBeConnected) _reconnectTimer.Start();
}
}
else if (_shouldBeConnected)
{
_reconnectTimer.Start();
}
};
}
public bool Connect()
{
_shouldBeConnected = true;
bool opened = _comm.Open();
if (!opened) StartReconnectTimer();
return opened;
}
public void Disconnect()
{
_shouldBeConnected = false;
_reconnectTimer?.Stop();
_comm.Close();
}
private void StartReconnectTimer()
{
if (_reconnectTimer != null && !_reconnectTimer.Enabled) _reconnectTimer.Start();
}
public void Dispose()
{
Disconnect();
_reconnectTimer?.Dispose();
_reconnectTimer = null;
} }
/// <summary> /// <summary>
@ -131,7 +187,7 @@ namespace leak_test_project.Services
} }
catch (Exception ex) catch (Exception ex)
{ {
Console.WriteLine($"[ZMDI] ReadSensor exception: {ex.Message}"); FileLogger.Log("ERROR", $"[ZMDI] ReadSensor exception: {ex.Message}");
ErrorMessage?.Invoke(this, $"센서 데이터 오류 (Exception: {ex.Message})\r\n[수신값]: {recvData.Trim()}"); ErrorMessage?.Invoke(this, $"센서 데이터 오류 (Exception: {ex.Message})\r\n[수신값]: {recvData.Trim()}");
return null; return null;
} }

53
leak_test_project/ViewModels/HomeViewModel.cs

@ -145,13 +145,29 @@ namespace leak_test_project.ViewModels
public void ApplyConfig() public void ApplyConfig()
{ {
CleanupCommunication(); CleanupAll();
var config = ConfigManager.Current; var config = ConfigManager.Current;
InitializeCommunication(config); InitializeCommunication(config);
InitializeTestProcess(config);
} }
private void CleanupCommunication() private void CleanupAll()
{ {
// 1. 시험 프로세스 정지 (스레드 종료 및 이벤트 해제)
_testProcess?.Dispose();
_testProcess = null;
// 2. ZMDI 시리얼 포트 및 서비스 해제
_leftZmdi?.Dispose();
_rightZmdi?.Dispose();
_leftZmdiSerial?.Dispose();
_rightZmdiSerial?.Dispose();
_leftZmdi = null;
_rightZmdi = null;
_leftZmdiSerial = null;
_rightZmdiSerial = null;
// 3. Sentinel C28 해제
_sentinelService?.Disconnect(); _sentinelService?.Disconnect();
_sentinelSerial?.Dispose(); _sentinelSerial?.Dispose();
_sentinelService = null; _sentinelService = null;
@ -165,29 +181,43 @@ namespace leak_test_project.ViewModels
_sentinelService = new SentinelC28Service(_sentinelSerial); _sentinelService = new SentinelC28Service(_sentinelSerial);
_sentinelService.RawDataReceived += (s, data) => { _sentinelService.RawDataReceived += (s, data) => {
// Raw 데이터는 UI 로그 보다는 파일 로그나 디버그 창으로 전송 가능
System.Diagnostics.Debug.WriteLine($"[SENTINEL RAW] {data}"); System.Diagnostics.Debug.WriteLine($"[SENTINEL RAW] {data}");
}; };
_sentinelService.OnStreamingParsed += (s, data) => UpdateMeasurement(data); _sentinelService.OnStreamingParsed += (s, data) => UpdateMeasurement(data);
_sentinelService.OnFinalResultParsed += (s, data) => ProcessFinalResult(data); _sentinelService.OnFinalResultParsed += (s, data) => ProcessFinalResult(data);
_sentinelService.Connect(); if (!_sentinelService.Connect())
{
string msg = $"리크 센서 포트 연결 실패 ({config.SensorPort})";
LeftError = msg;
RightError = msg;
}
} }
private void InitializeTestProcess(AppConfig config) private void InitializeTestProcess(AppConfig config)
{ {
// DIO 보드 초기화 (MainViewModel에서 생성된 보드 사용) // DIO 보드 초기화 (MainViewModel에서 생성된 보드 사용)
// _dioBoard는 생성자에서 이미 할당됨.
if (_dioBoard == null) return; if (_dioBoard == null) return;
// _dioBoard.Initialize(); // MainViewModel이나 Factory에서 이미 수행됨.
// ZMDI 센서 서비스 (LEFT/RIGHT) // ZMDI 센서 서비스 (LEFT/RIGHT)
_leftZmdiSerial = new SerialProvider(config.LeftPort, config.ZmdiBaudRate); _leftZmdiSerial = new SerialProvider(config.LeftPort, config.ZmdiBaudRate);
_rightZmdiSerial = new SerialProvider(config.RightPort, config.ZmdiBaudRate); _rightZmdiSerial = new SerialProvider(config.RightPort, config.ZmdiBaudRate);
_leftZmdi = new ZmdiSensorService(_leftZmdiSerial, 0); _leftZmdi = new ZmdiSensorService(_leftZmdiSerial, 0);
_rightZmdi = new ZmdiSensorService(_rightZmdiSerial, 1); _rightZmdi = new ZmdiSensorService(_rightZmdiSerial, 1);
if (!_leftZmdi.Connect())
{
string msg = $"ZMDI 센서 포트 연결 실패 ({config.LeftPort})";
LeftError = string.IsNullOrEmpty(LeftError) ? msg : $"{LeftError}\n{msg}";
}
if (!_rightZmdi.Connect())
{
string msg = $"ZMDI 센서 포트 연결 실패 ({config.RightPort})";
RightError = string.IsNullOrEmpty(RightError) ? msg : $"{RightError}\n{msg}";
}
_leftZmdi.ProgressMessage += (s, msg) => _dispatcher.Invoke(() => LeftStatus = msg); _leftZmdi.ProgressMessage += (s, msg) => _dispatcher.Invoke(() => LeftStatus = msg);
_leftZmdi.ErrorMessage += (s, msg) => _dispatcher.Invoke(() => { _leftZmdi.ErrorMessage += (s, msg) => _dispatcher.Invoke(() => {
LeftError = msg; LeftError = msg;
@ -318,8 +348,9 @@ namespace leak_test_project.ViewModels
RightValue = data.MeasuredValue.ToString("F3"); RightValue = data.MeasuredValue.ToString("F3");
else else
{ {
// 채널 정보가 없으면 기본적으로 양쪽 모두 또는 로그에 기록 // 채널 정보가 없으면 양쪽 모두 갱신
LeftValue = data.MeasuredValue.ToString("F3"); LeftValue = data.MeasuredValue.ToString("F3");
RightValue = data.MeasuredValue.ToString("F3");
} }
}); });
} }
@ -422,14 +453,8 @@ namespace leak_test_project.ViewModels
public void Dispose() public void Dispose()
{ {
ConfigManager.ConfigChanged -= OnConfigChanged; ConfigManager.ConfigChanged -= OnConfigChanged;
CleanupAll();
_testProcess?.Dispose();
_dioBoard?.Dispose(); _dioBoard?.Dispose();
_leftZmdiSerial?.Dispose();
_rightZmdiSerial?.Dispose();
CleanupCommunication();
} }
} }
} }

32
leak_test_project/ViewModels/InOutViewModel.cs

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Windows.Input; using System.Windows.Input;
using System.Windows.Threading;
using leak_test_project.Models; using leak_test_project.Models;
using leak_test_project.Services; using leak_test_project.Services;
using leak_test_project.ViewModels.Core; using leak_test_project.ViewModels.Core;
@ -9,10 +10,11 @@ using leak_test_project.ViewModels.Core;
namespace leak_test_project.ViewModels namespace leak_test_project.ViewModels
{ {
/// <summary> /// <summary>
/// In/Out Monitor 화면의 데이터 페이징 로직을 담당하는 ViewModel. /// In/Out Monitor 화면의 데이터 페이징 및 실시간 갱신 로직을 담당하는 ViewModel.
/// </summary> /// </summary>
public class InOutViewModel : ObservableObject public class InOutViewModel : ObservableObject, IDisposable
{ {
private readonly IoBoardService _ioService;
private readonly List<InOutItem> _allInputs; private readonly List<InOutItem> _allInputs;
private readonly List<InOutItem> _allOutputs; private readonly List<InOutItem> _allOutputs;
private const int PageSize = 30; private const int PageSize = 30;
@ -20,6 +22,8 @@ namespace leak_test_project.ViewModels
private int _inputCurrentPage = 0; private int _inputCurrentPage = 0;
private int _outputCurrentPage = 0; private int _outputCurrentPage = 0;
private readonly DispatcherTimer _refreshTimer;
private List<InOutItem> _currentInputs = new List<InOutItem>(); private List<InOutItem> _currentInputs = new List<InOutItem>();
public List<InOutItem> CurrentInputs { get => _currentInputs; set => SetProperty(ref _currentInputs, value); } public List<InOutItem> CurrentInputs { get => _currentInputs; set => SetProperty(ref _currentInputs, value); }
@ -43,6 +47,7 @@ namespace leak_test_project.ViewModels
public InOutViewModel(Action navigateHome, IoBoardService ioService) public InOutViewModel(Action navigateHome, IoBoardService ioService)
{ {
_ioService = ioService;
_allInputs = ioService.Inputs; _allInputs = ioService.Inputs;
_allOutputs = ioService.Outputs; _allOutputs = ioService.Outputs;
@ -56,10 +61,26 @@ namespace leak_test_project.ViewModels
OutputNextCommand = new RelayCommand(o => { if ((_outputCurrentPage + 1) * PageSize < _allOutputs.Count) { _outputCurrentPage++; UpdateOutputs(); } }); OutputNextCommand = new RelayCommand(o => { if ((_outputCurrentPage + 1) * PageSize < _allOutputs.Count) { _outputCurrentPage++; UpdateOutputs(); } });
OutputLastCommand = new RelayCommand(o => { _outputCurrentPage = Math.Max(0, (_allOutputs.Count - 1) / PageSize); UpdateOutputs(); }); OutputLastCommand = new RelayCommand(o => { _outputCurrentPage = Math.Max(0, (_allOutputs.Count - 1) / PageSize); UpdateOutputs(); });
CloseCommand = new RelayCommand(o => navigateHome?.Invoke()); CloseCommand = new RelayCommand(o => {
Dispose();
navigateHome?.Invoke();
});
UpdateInputs(); UpdateInputs();
UpdateOutputs(); UpdateOutputs();
// 500ms 주기로 DIO 상태 자동 갱신
_refreshTimer = new DispatcherTimer
{
Interval = TimeSpan.FromMilliseconds(500)
};
_refreshTimer.Tick += (s, e) =>
{
_ioService.RefreshState();
UpdateInputs();
UpdateOutputs();
};
_refreshTimer.Start();
} }
private void UpdateInputs() private void UpdateInputs()
@ -71,5 +92,10 @@ namespace leak_test_project.ViewModels
{ {
CurrentOutputs = _allOutputs.Skip(_outputCurrentPage * PageSize).Take(PageSize).ToList(); CurrentOutputs = _allOutputs.Skip(_outputCurrentPage * PageSize).Take(PageSize).ToList();
} }
public void Dispose()
{
_refreshTimer?.Stop();
}
} }
} }

4
leak_test_project/Views/DataView.xaml

@ -142,7 +142,7 @@
GridLinesVisibility="All" HorizontalGridLinesBrush="#ECF0F1" VerticalGridLinesBrush="#ECF0F1" GridLinesVisibility="All" HorizontalGridLinesBrush="#ECF0F1" VerticalGridLinesBrush="#ECF0F1"
AlternatingRowBackground="#F9FBFC" RowHeight="35" AlternatingRowBackground="#F9FBFC" RowHeight="35"
CanUserSortColumns="True" CanUserReorderColumns="False" CanUserSortColumns="True" CanUserReorderColumns="False"
BorderThickness="0" SelectionMode="Single" SelectionChanged="DataGrid_SelectionChanged"> BorderThickness="0" SelectionMode="Extended" SelectionChanged="DataGrid_SelectionChanged">
<DataGrid.Columns> <DataGrid.Columns>
<DataGridTextColumn Header="날짜" Binding="{Binding InspectDate}" Width="130"/> <DataGridTextColumn Header="날짜" Binding="{Binding InspectDate}" Width="130"/>
<DataGridTextColumn Header="재시험" Binding="{Binding Retest}" Width="70"/> <DataGridTextColumn Header="재시험" Binding="{Binding Retest}" Width="70"/>
@ -178,7 +178,7 @@
</Style> </Style>
</DataGridTextColumn.ElementStyle> </DataGridTextColumn.ElementStyle>
</DataGridTextColumn> </DataGridTextColumn>
<DataGridTextColumn Header="비고" Width="*"/> <DataGridTextColumn Header="비고" Width="*" MinWidth="300"/>
</DataGrid.Columns> </DataGrid.Columns>
</DataGrid> </DataGrid>
</Border> </Border>

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

Binary file not shown.

4
leak_test_project/bin/Release/config.xml

@ -5,6 +5,6 @@
<ZmdiBaudRate>19200</ZmdiBaudRate> <ZmdiBaudRate>19200</ZmdiBaudRate>
<SensorPort>COM3</SensorPort> <SensorPort>COM3</SensorPort>
<SensorBaudRate>9600</SensorBaudRate> <SensorBaudRate>9600</SensorBaudRate>
<SpecUL>0.6</SpecUL> <SpecUL>0.1</SpecUL>
<SpecLL>-0.25</SpecLL> <SpecLL>-0.15</SpecLL>
</AppConfig> </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:Transform Algorithm="urn:schemas-microsoft-com:HashTransforms.Identity" />
</dsig:Transforms> </dsig:Transforms>
<dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha256" /> <dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha256" />
<dsig:DigestValue>CsrEJe4rTy6CHSF/iN4MvW+2RB9TB5Xx/+uSD4CYW7k=</dsig:DigestValue> <dsig:DigestValue>+tT5TcFav5TEeRJogPVqrD2i+n0zc3NErhzPuBsdAok=</dsig:DigestValue>
</hash> </hash>
</dependentAssembly> </dependentAssembly>
</dependency> </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> </dependentAssembly>
</dependency> </dependency>
<dependency> <dependency>
<dependentAssembly dependencyType="install" allowDelayedBinding="true" codebase="leak_test_project.exe" size="163792"> <dependentAssembly dependencyType="install" allowDelayedBinding="true" codebase="leak_test_project.exe" size="166864">
<assemblyIdentity name="leak_test_project" version="1.0.0.0" language="neutral" processorArchitecture="msil" /> <assemblyIdentity name="leak_test_project" version="1.0.0.0" language="neutral" processorArchitecture="msil" />
<hash> <hash>
<dsig:Transforms> <dsig:Transforms>
<dsig:Transform Algorithm="urn:schemas-microsoft-com:HashTransforms.Identity" /> <dsig:Transform Algorithm="urn:schemas-microsoft-com:HashTransforms.Identity" />
</dsig:Transforms> </dsig:Transforms>
<dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha256" /> <dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha256" />
<dsig:DigestValue>GG2gAseBUorlVcMCNhyGWUc4PX1BrLRis6grr7abydM=</dsig:DigestValue> <dsig:DigestValue>qAkKP87U3yb9EpU29R51sPc1OgjB2lM+2tz7F86u2qI=</dsig:DigestValue>
</hash> </hash>
</dependentAssembly> </dependentAssembly>
</dependency> </dependency>

BIN
leak_test_project/bin/Release/leak_test_project.pdb

Binary file not shown.

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

@ -1,2 +1,62 @@
 //------------------------------------------------------------------------------
// <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.

12
leak_test_project/obj/Release/MainWindow.g.cs

@ -1,4 +1,4 @@
#pragma checksum "..\..\MainWindow.xaml" "{8829d00f-11b8-4213-878b-770e8597ac16}" "6AF2BA3E5F60C061A914FC5DF6ABFF42B0A79490DBE682D50DF18D57A83F92AD" #pragma checksum "..\..\MainWindow.xaml" "{8829d00f-11b8-4213-878b-770e8597ac16}" "AC6B63D342AAA60FA134A9381169A7E50CCCC4A249937C592D6EBCFD954C2193"
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// <auto-generated> // <auto-generated>
// 이 코드는 도구를 사용하여 생성되었습니다. // 이 코드는 도구를 사용하여 생성되었습니다.
@ -49,7 +49,7 @@ namespace leak_test_project {
#line hidden #line hidden
#line 47 "..\..\MainWindow.xaml" #line 48 "..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.Button btnMaximize; internal System.Windows.Controls.Button btnMaximize;
@ -57,7 +57,7 @@ namespace leak_test_project {
#line hidden #line hidden
#line 48 "..\..\MainWindow.xaml" #line 49 "..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock txtMaximizeIcon; internal System.Windows.Controls.TextBlock txtMaximizeIcon;
@ -96,7 +96,7 @@ namespace leak_test_project {
{ {
case 1: case 1:
#line 20 "..\..\MainWindow.xaml" #line 21 "..\..\MainWindow.xaml"
((System.Windows.Controls.Border)(target)).MouseLeftButtonDown += new System.Windows.Input.MouseButtonEventHandler(this.TopBar_MouseLeftButtonDown); ((System.Windows.Controls.Border)(target)).MouseLeftButtonDown += new System.Windows.Input.MouseButtonEventHandler(this.TopBar_MouseLeftButtonDown);
#line default #line default
@ -107,7 +107,7 @@ namespace leak_test_project {
return; return;
case 3: case 3:
#line 44 "..\..\MainWindow.xaml" #line 45 "..\..\MainWindow.xaml"
((System.Windows.Controls.Button)(target)).Click += new System.Windows.RoutedEventHandler(this.BtnMinimize_Click); ((System.Windows.Controls.Button)(target)).Click += new System.Windows.RoutedEventHandler(this.BtnMinimize_Click);
#line default #line default
@ -116,7 +116,7 @@ namespace leak_test_project {
case 4: case 4:
this.btnMaximize = ((System.Windows.Controls.Button)(target)); this.btnMaximize = ((System.Windows.Controls.Button)(target));
#line 47 "..\..\MainWindow.xaml" #line 48 "..\..\MainWindow.xaml"
this.btnMaximize.Click += new System.Windows.RoutedEventHandler(this.BtnMaximize_Click); this.btnMaximize.Click += new System.Windows.RoutedEventHandler(this.BtnMaximize_Click);
#line default #line default

12
leak_test_project/obj/Release/MainWindow.g.i.cs

@ -1,4 +1,4 @@
#pragma checksum "..\..\MainWindow.xaml" "{8829d00f-11b8-4213-878b-770e8597ac16}" "6AF2BA3E5F60C061A914FC5DF6ABFF42B0A79490DBE682D50DF18D57A83F92AD" #pragma checksum "..\..\MainWindow.xaml" "{8829d00f-11b8-4213-878b-770e8597ac16}" "AC6B63D342AAA60FA134A9381169A7E50CCCC4A249937C592D6EBCFD954C2193"
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// <auto-generated> // <auto-generated>
// 이 코드는 도구를 사용하여 생성되었습니다. // 이 코드는 도구를 사용하여 생성되었습니다.
@ -49,7 +49,7 @@ namespace leak_test_project {
#line hidden #line hidden
#line 47 "..\..\MainWindow.xaml" #line 48 "..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.Button btnMaximize; internal System.Windows.Controls.Button btnMaximize;
@ -57,7 +57,7 @@ namespace leak_test_project {
#line hidden #line hidden
#line 48 "..\..\MainWindow.xaml" #line 49 "..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock txtMaximizeIcon; internal System.Windows.Controls.TextBlock txtMaximizeIcon;
@ -96,7 +96,7 @@ namespace leak_test_project {
{ {
case 1: case 1:
#line 20 "..\..\MainWindow.xaml" #line 21 "..\..\MainWindow.xaml"
((System.Windows.Controls.Border)(target)).MouseLeftButtonDown += new System.Windows.Input.MouseButtonEventHandler(this.TopBar_MouseLeftButtonDown); ((System.Windows.Controls.Border)(target)).MouseLeftButtonDown += new System.Windows.Input.MouseButtonEventHandler(this.TopBar_MouseLeftButtonDown);
#line default #line default
@ -107,7 +107,7 @@ namespace leak_test_project {
return; return;
case 3: case 3:
#line 44 "..\..\MainWindow.xaml" #line 45 "..\..\MainWindow.xaml"
((System.Windows.Controls.Button)(target)).Click += new System.Windows.RoutedEventHandler(this.BtnMinimize_Click); ((System.Windows.Controls.Button)(target)).Click += new System.Windows.RoutedEventHandler(this.BtnMinimize_Click);
#line default #line default
@ -116,7 +116,7 @@ namespace leak_test_project {
case 4: case 4:
this.btnMaximize = ((System.Windows.Controls.Button)(target)); this.btnMaximize = ((System.Windows.Controls.Button)(target));
#line 47 "..\..\MainWindow.xaml" #line 48 "..\..\MainWindow.xaml"
this.btnMaximize.Click += new System.Windows.RoutedEventHandler(this.BtnMaximize_Click); this.btnMaximize.Click += new System.Windows.RoutedEventHandler(this.BtnMaximize_Click);
#line default #line default

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

Binary file not shown.

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

@ -1,4 +1,4 @@
#pragma checksum "..\..\..\Views\DataView.xaml" "{8829d00f-11b8-4213-878b-770e8597ac16}" "D1F2D654C7757653BF2CBCA4196ECE79CF1D243F00B5F870E08DA488B33B02FF" #pragma checksum "..\..\..\Views\DataView.xaml" "{8829d00f-11b8-4213-878b-770e8597ac16}" "3817A6627B503D085EDFEB1BF6AD88D4A23D1775086B1CA3BC0ADB5F4D94D266"
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// <auto-generated> // <auto-generated>
// 이 코드는 도구를 사용하여 생성되었습니다. // 이 코드는 도구를 사용하여 생성되었습니다.

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

@ -1,4 +1,4 @@
#pragma checksum "..\..\..\Views\DataView.xaml" "{8829d00f-11b8-4213-878b-770e8597ac16}" "D1F2D654C7757653BF2CBCA4196ECE79CF1D243F00B5F870E08DA488B33B02FF" #pragma checksum "..\..\..\Views\DataView.xaml" "{8829d00f-11b8-4213-878b-770e8597ac16}" "3817A6627B503D085EDFEB1BF6AD88D4A23D1775086B1CA3BC0ADB5F4D94D266"
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// <auto-generated> // <auto-generated>
// 이 코드는 도구를 사용하여 생성되었습니다. // 이 코드는 도구를 사용하여 생성되었습니다.

2
leak_test_project/obj/Release/leak_test_project.application

@ -14,7 +14,7 @@
<dsig:Transform Algorithm="urn:schemas-microsoft-com:HashTransforms.Identity" /> <dsig:Transform Algorithm="urn:schemas-microsoft-com:HashTransforms.Identity" />
</dsig:Transforms> </dsig:Transforms>
<dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha256" /> <dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha256" />
<dsig:DigestValue>CsrEJe4rTy6CHSF/iN4MvW+2RB9TB5Xx/+uSD4CYW7k=</dsig:DigestValue> <dsig:DigestValue>+tT5TcFav5TEeRJogPVqrD2i+n0zc3NErhzPuBsdAok=</dsig:DigestValue>
</hash> </hash>
</dependentAssembly> </dependentAssembly>
</dependency> </dependency>

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> </dependentAssembly>
</dependency> </dependency>
<dependency> <dependency>
<dependentAssembly dependencyType="install" allowDelayedBinding="true" codebase="leak_test_project.exe" size="163792"> <dependentAssembly dependencyType="install" allowDelayedBinding="true" codebase="leak_test_project.exe" size="166864">
<assemblyIdentity name="leak_test_project" version="1.0.0.0" language="neutral" processorArchitecture="msil" /> <assemblyIdentity name="leak_test_project" version="1.0.0.0" language="neutral" processorArchitecture="msil" />
<hash> <hash>
<dsig:Transforms> <dsig:Transforms>
<dsig:Transform Algorithm="urn:schemas-microsoft-com:HashTransforms.Identity" /> <dsig:Transform Algorithm="urn:schemas-microsoft-com:HashTransforms.Identity" />
</dsig:Transforms> </dsig:Transforms>
<dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha256" /> <dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha256" />
<dsig:DigestValue>GG2gAseBUorlVcMCNhyGWUc4PX1BrLRis6grr7abydM=</dsig:DigestValue> <dsig:DigestValue>qAkKP87U3yb9EpU29R51sPc1OgjB2lM+2tz7F86u2qI=</dsig:DigestValue>
</hash> </hash>
</dependentAssembly> </dependentAssembly>
</dependency> </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.

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\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\Views\HomeView.xaml;;
FC:\Users\COMPUTER1\Desktop\mobi\leak_test_project\leak_test_project\MainWindow.xaml;; FC:\Users\COMPUTER1\Desktop\mobi\leak_test_project\leak_test_project\MainWindow.xaml;;

Loading…
Cancel
Save