From 70739252bb2211460a4506688c49c1fcccec7fff Mon Sep 17 00:00:00 2001 From: NohDoyeon241104 Date: Thu, 18 Jun 2026 09:38:33 +0900 Subject: [PATCH] =?UTF-8?q?docs:=20=EC=9E=91=EC=97=85=EC=9D=BC=EC=A7=80?= =?UTF-8?q?=C2=B7=ED=98=84=EC=9E=A5update=ED=8C=8C=EC=9D=BC=C2=B7=EB=B6=84?= =?UTF-8?q?=EC=84=9D=20=EB=AC=B8=EC=84=9C=20=EB=B0=8F=20=EC=95=84=ED=82=A4?= =?UTF-8?q?=ED=85=8D=EC=B2=98=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - docs/작업일지: 06-17, 06-18 작업일지 추가 (조명 컨트롤러 연결, 현장 PC 원격 접속, 현장 소스 반영) - docs/현장update파일: 현장 반영용 변경 파일(SequenceSetting.cs, UCScannerObjectTeachingControlBoxViewModel.cs) 및 README 정리 - docs/분석: 260617 분석 문서(Laser Ready DI 변경, Axis Position Interlock, PLC DeviceMap, EROOM Light RS232 등) 추가 - docs/architecture.md, CLAUDE.md, harness-ptop.md 갱신 - PLC 인터페이스 엑셀 및 기존파일비교 자료 추가 Co-Authored-By: Claude Opus 4.8 --- CLAUDE.md | 51 +- ... 니셀 PtoP 단동기 Laser Interface_260617.xlsx | Bin 0 -> 52178 bytes .../유니셀 PtoP 단동기 Laser Interface_260617.xlsx | Bin 0 -> 52178 bytes docs/architecture.md | 230 ++++++++ docs/harness-ptop.md | 2 +- .../UCScannerObjectTeachingControlBoxViewModel.cs | 472 +++++++++++++++++ .../06/17/143131_laser_ready_di9_analysis.md | 187 +++++++ ..._axis_position_interlock_setting_location.md | 162 ++++++ ...sequencesetting_axis_interlock_properties.md | 86 +++ .../17/150031_laser_ready_di12_di24_change.md | 122 +++++ .../17/162010_io_interface_map_path_source.md | 103 ++++ .../06/17/165341_plc_devicemap_update_260617.md | 67 +++ ...2_plc_map_io_direction_and_enum_alignment.md | 89 ++++ .../06/17/171642_plc_map_update_checklist.md | 48 ++ .../06/17/184011_eroom_light_rs232_analysis.md | 264 ++++++++++ docs/작업일지/2026_06_17.md | 55 ++ docs/작업일지/2026_06_18.md | 37 ++ docs/작업일지/README.md | 6 + docs/현장update파일/README.md | 74 +++ docs/현장update파일/SequenceSetting.cs | 85 +++ .../UCScannerObjectTeachingControlBoxViewModel.cs | 489 ++++++++++++++++++ 21 files changed, 2591 insertions(+), 38 deletions(-) create mode 100644 Doc/LGPRI문서/유니셀 PtoP 단동기 Laser Interface_260617.xlsx create mode 100644 docs/PtoP 유니셀 용접기 PLC 인터페이스_260617/유니셀 PtoP 단동기 Laser Interface_260617.xlsx create mode 100644 docs/architecture.md create mode 100644 docs/기존파일비교/UCScannerObjectTeachingControlBoxViewModel.cs create mode 100644 docs/분석/2026/06/17/143131_laser_ready_di9_analysis.md create mode 100644 docs/분석/2026/06/17/143900_axis_position_interlock_setting_location.md create mode 100644 docs/분석/2026/06/17/144442_sequencesetting_axis_interlock_properties.md create mode 100644 docs/분석/2026/06/17/150031_laser_ready_di12_di24_change.md create mode 100644 docs/분석/2026/06/17/162010_io_interface_map_path_source.md create mode 100644 docs/분석/2026/06/17/165341_plc_devicemap_update_260617.md create mode 100644 docs/분석/2026/06/17/171312_plc_map_io_direction_and_enum_alignment.md create mode 100644 docs/분석/2026/06/17/171642_plc_map_update_checklist.md create mode 100644 docs/분석/2026/06/17/184011_eroom_light_rs232_analysis.md create mode 100644 docs/작업일지/2026_06_17.md create mode 100644 docs/작업일지/2026_06_18.md create mode 100644 docs/작업일지/README.md create mode 100644 docs/현장update파일/README.md create mode 100644 docs/현장update파일/SequenceSetting.cs create mode 100644 docs/현장update파일/UCScannerObjectTeachingControlBoxViewModel.cs diff --git a/CLAUDE.md b/CLAUDE.md index 7604223..151a569 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,50 +1,27 @@ # ILSOS 프로젝트 — Claude 초기 컨텍스트 -## 프로젝트 문서 +## 목차 -@import ./docs/project-overview.md — 목표, 플랫폼, 빌드 구성, 머신 모듈 소개 - -@import ./docs/solution-structure.md — 전체 솔루션 구조 및 주요 레이어 - -@import ./docs/ptop-architecture.md — PTOP 폴더 구조, ID 정의, 3계층 아키텍처 - -@import ./docs/aurora-devices.md — 22개 하드웨어 디바이스 드라이버 목록 - -@import ./docs/build-output.md — 머신별 빌드 출력 경로 - -@import ./docs/harness-ptop.md — PTOP 분석 팀 구성 및 호출 방법 +1. [프로젝트 문서](#1-프로젝트-문서) +2. [협업 규칙](#2-협업-규칙) +3. [PTOP 분석 하네스](#3-ptop-분석-하네스) --- -## 협업 규칙 +## 1. 프로젝트 문서 + +@import ./docs/architecture.md — 프로젝트 개요, 솔루션 구조, PTOP 아키텍처, 디바이스, 빌드, 하네스 통합 문서 + +--- + +## 2. 협업 규칙 @import ./rules.md --- -## 하네스: ILSOS_PrismaticRD_PTOP 분석 팀 +## 3. PTOP 분석 하네스 -**목표:** ILSOS_PrismaticRD_PTOP(PtoP)의 3계층 아키텍처, 구현 상세, 빌드 최적화를 종합 분석하고 개선안 제시 +ILSOS_PrismaticRD_PTOP(PtoP) 종합 분석 팀 구성 — 목표, 팀 구성, 호출 방법, 구성 요소, 변경 이력은 아래 문서 참고. -**팀 구성:** -- PTOP Lead (아키텍처 리더) -- PTOP Developer (구현 전문가) -- Build/DevOps Engineer (빌드 최적화) - -**트리거:** 다음 요청 시 `/orchestrate-ptop-harness` 스킬 사용 -- "PTOP 프로젝트 전체 분석" -- "PTOP 아키텍처와 구현 분석" -- "PTOP 상태 점검" -- "PTOP 빌드 최적화" -- "PTOP 팀 조율" - -**구성 요소:** -- Agents: `.ubisam_agent/agents/ptop-{lead|developer}.md`, `.ubisam_agent/agents/build-devops-engineer.md` -- Skills: `.ubisam_agent/skills/analyze-ptop-{architecture|implementation}/SKILL.md`, `.ubisam_agent/skills/optimize-ptop-build/SKILL.md`, `.ubisam_agent/skills/orchestrate-ptop-harness/SKILL.md` -- Workspace: `_workspace/0X_*.md` (분석 결과) - -**변경 이력:** - -| 날짜 | 변경 내용 | 대상 | 사유 | -|------|----------|------|------| -| 2026-06-16 | 초기 구성: PTOP 분석 팀 구축 | agents/, skills/ | ILSOS_PrismaticRD_PTOP이 핵심 활성 모듈, 종합 분석 필요 | +@import ./docs/harness-ptop.md — PTOP 분석 하네스(팀 구성, 트리거, 구성 요소, 변경 이력) diff --git a/Doc/LGPRI문서/유니셀 PtoP 단동기 Laser Interface_260617.xlsx b/Doc/LGPRI문서/유니셀 PtoP 단동기 Laser Interface_260617.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..709c1a7bb0cd7bf9415de42b824f6b079deb2812 GIT binary patch literal 52178 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 _7iZ`PM+Zv8K)rr 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 literal 0 HcmV?d00001 diff --git a/docs/PtoP 유니셀 용접기 PLC 인터페이스_260617/유니셀 PtoP 단동기 Laser Interface_260617.xlsx b/docs/PtoP 유니셀 용접기 PLC 인터페이스_260617/유니셀 PtoP 단동기 Laser Interface_260617.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..709c1a7bb0cd7bf9415de42b824f6b079deb2812 GIT binary patch literal 52178 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 _7iZ`PM+Zv8K)rr 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 literal 0 HcmV?d00001 diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..51e7694 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,230 @@ +# ILSOS 프로젝트 아키텍처 + +> CLAUDE.md의 "프로젝트 문서" 섹션에서 import하던 개별 문서들을 통합한 아키텍처 문서입니다. +> 세부 내용은 각 원본 문서를 참고하세요. + +## 목차 + +1. [프로젝트 개요](#1-프로젝트-개요) +2. [솔루션 구조](#2-솔루션-구조) +3. [ILSOS_PrismaticPTOP 핵심 구조](#3-ilsos_prismaticptop-핵심-구조) +4. [AuroraDevices 모듈](#4-auroradevices-모듈) +5. [빌드 출력 경로](#5-빌드-출력-경로) +6. [PTOP 분석 하네스](#6-ptop-분석-하네스) + +--- + +## 1. 프로젝트 개요 + +> 원본: [`project-overview.md`](./project-overview.md) + +### ILSOS - OC5 PilotLine RectangleTypeWelder + +**목표:** 각형 배터리 용접 자동화 시스템 + +**플랫폼 및 기술:** +- OS: Windows 10 / Windows 11 +- 프레임워크: .NET Framework 4.8.1 +- IDE: Visual Studio 2022 +- UI 프레임워크: WPF (Aurora 프레임워크 기반) +- 솔루션 파일: `ILSOS_All.sln` + +**빌드 구성:** +- Debug | x64 +- Release | x64 +- Simulation | x64 +- UserDefineUnit | x64 + +**주요 머신 모듈:** +- **ILSOS_PrismaticPTOP**: 현재 활성 모듈 (주요 개발 대상) +- **ILSOS_PrismaticScanner**: 참고 용도의 레거시 소스 +- ILSOS_PrismaticGun: 미사용 +- ILSOS_PrismaticCleanLaser: 미사용 + +**주의사항:** +- WeldingComponents와 Aurora 관련 코드 수정 시 반드시 사전 승인 필요 +- 소스의 `config/` 디렉토리는 분석 시 참고할 것 +- 모든 파일 수정 시 `docs/CODING_RULES.md` 확인 필수 +- 한글 포함 파일 작성/수정 시 UTF-8 인코딩 준수 + +--- + +## 2. 솔루션 구조 + +> 원본: [`solution-structure.md`](./solution-structure.md) + +### ILSOS_All.sln 구성 + +``` +ILSOS_All.sln +├── Machines/ +│ ├── ILSOS_PrismaticPTOP/ ← 현재 활성 모듈 (3계층 아키텍처) +│ ├── ILSOS_PrismaticScanner/ ← 참고 대상 (레거시) +│ ├── ILSOS_PrismaticGun/ ← 미사용 +│ └── ILSOS_PrismaticCleanLaser/ ← 미사용 +├── AuroraDevices/ ← 하드웨어 디바이스 드라이버 (22개 프로젝트) +├── AuroraComponents/ ← WPF UI 공통 컴포넌트 +├── AuroraVision/ ← 비전 시스템 (MIL 기반) +├── WeldingComponents/ ← 용접 공통 기능 +│ ├── WeldingCommon +│ └── LASAGUI +└── External/ ← Aurora 프레임워크 바이너리 +``` + +### 주요 특징 + +- **모듈화:** 각 머신 모듈은 독립적인 디바이스 제어 및 시퀀스 구현 +- **공유 레이어:** AuroraDevices, AuroraComponents 등으로 코드 재사용 +- **표준 규칙:** WeldingComponents는 수정 불가 (승인 필수) +- **설정 참고:** 각 머신의 `config/` 디렉토리에서 설정 확인 가능 + +--- + +## 3. ILSOS_PrismaticPTOP 핵심 구조 + +> 원본: [`ptop-architecture.md`](./ptop-architecture.md) + +### 폴더 구조 및 주요 컴포넌트 + +``` +ILSOS_PrismaticPTOP/ +├── EquipDef.cs ← ID 상수 정의 (Device/Motor/Sequence/Recipe) +├── EquipmentSetup.cs ← 통합 제어 (초기화, 이벤트 구독, 시퀀스 시작) +├── Alarm/ ← 알람 코드 및 매니저 +├── Device/ +│ ├── ACS/ ← ACS 모션 제어 (X/Y축) +│ └── PLC/ ← PLC 통신 로직 +├── Models/ ← 장비 특화 모델 +├── Process/ ← ProcessManager, CellInfo, WeldingData +├── Recipes/ ← MainRecipe, RMSHandler +├── Sequences/ +│ ├── Monitoring/ ← 알람/상태 모니터링 시퀀스 +│ ├── SeqAutoRunPrismaticGunStageA/B.cs +│ └── SubSequence/ +│ ├── StageA/ ← Load, Weld, Unload, HeightMeas +│ └── StageB/ ← Load, Weld, Unload, HeightMeas +├── settings/ ← 세팅 정의 클래스 +├── Tools/ ← 공통 유틸리티 +└── Views/ ← WPF View/ViewModel +``` + +### 주요 ID 정의 (EquipDef.cs) + +| 카테고리 | 예시 | +|---------|------| +| **DeviceIds** | ACS, MassFlowMeterStageA/B, HeightSensor, AJIN | +| **MotorIds** | ALineX/Y/Z, BLineX/Y/Z | +| **RecipeIds** | StageAMainRecipe, StageBMainRecipe | +| **SequenceIds** | SeqLoad/Weld/Unload/HeightMeas × StageA/B | + +### 아키텍처 패턴 (3계층) + +``` +WPF UI Layer (Views/ + ViewModels) + ↓ +Machine Logic Layer (EquipmentSetup + Sequences + Process) + ↓ +Device Abstraction (AuroraDevices — 디바이스별 독립 모듈) + ↓ +Hardware Layer (ACS.SPiiPlusNET, Melsec, Serial/USB) +``` + +**기술 스택:** +- **이벤트 기반:** System.Reactive (Rx.NET) +- **의존성 주입:** AuroraEnv 글로벌 환경 +- **메시지 큐:** String / Double / Int / JSON / LBAD 타입 + +--- + +## 4. AuroraDevices 모듈 + +> 원본: [`aurora-devices.md`](./aurora-devices.md) + +AuroraDevices는 하드웨어 디바이스 제어를 담당하는 22개 프로젝트 모음입니다. + +| 프로젝트 | 역할 | 비고 | +|---------|------|------| +| AuroraDeviceCommon | 디바이스 공통 기본 클래스 | 모든 디바이스 상속 | +| AuroraACS | ACS SPiiPlus 모션 컨트롤러 | X/Y축 모션 제어 | +| AuroraMotionBoard | Ajin 모션 보드 | Z축 모션 제어 | +| AuroraLaser | 레이저 제어 (Gun 타입) | 고정 레이저 | +| AuroraLaserScanner | 레이저 스캐너 제어 | 동적 스캐닝 | +| AuroraPLC | Mitsubishi Melsec PLC 통신 | 외부 신호 제어 | +| AuroraIO | 디지털 I/O | 입출력 신호 | +| AuroraHeightSensor | Keyence 높이 센서 | 높이 측정 | +| AuroraCellTracker | 팰릿/셀 추적 시스템 | 셀 인식 및 위치 추적 | +| AuroraChiller | 냉각 장치 | 온도 제어 | +| AuroraOCT | Trumpf OCT 스캐너 | 옵티컬 측정 | + +**설계 원칙:** +- 각 디바이스는 독립적인 제어 로직 구현 +- 통합 제어는 머신 모듈의 EquipmentSetup에서 담당 +- 의존성 주입(DI) 패턴으로 느슨한 결합 유지 + +--- + +## 5. 빌드 출력 경로 + +> 원본: [`build-output.md`](./build-output.md) + +### 머신별 빌드 디렉토리 + +| 머신 모듈 | 출력 경로 | 상태 | +|---------|---------|------| +| ILSOS_PrismaticPTOP | `_out_prismatic_ptop/` | 활성 | +| ILSOS_PrismaticScanner | `_out_prismatic_scanner/` | 참고 | +| ILSOS_PrismaticGun | `_out_prismatic_gun/` | 미사용 | +| ILSOS_PrismaticCleanLaser | `_out_prismatic_cleanlaser/` | 미사용 | + +### 빌드 구성별 출력 + +각 빌드 구성(Debug, Release, Simulation, UserDefineUnit)은 플랫폼(x64)별로 독립적인 출력 디렉토리 생성. + +예: `_out_prismatic_ptop/Debug/x64/`, `_out_prismatic_ptop/Release/x64/` + +### 주의사항 + +- 빌드 출력 디렉토리는 `.gitignore`에 포함 +- 빌드 전 이전 출력물이 있으면 자동 정리 +- 각 머신 모듈은 독립적으로 빌드 가능 + +--- + +## 6. PTOP 분석 하네스 + +> 원본: [`harness-ptop.md`](./harness-ptop.md) + +### 목표 + +ILSOS_PrismaticRD_PTOP(PtoP)의 3계층 아키텍처, 구현 상세, 빌드 최적화를 종합 분석하고 개선안 제시 + +### 팀 구성 + +- **PTOP Lead**: 아키텍처 리더 +- **PTOP Developer**: 구현 전문가 +- **Build/DevOps Engineer**: 빌드 최적화 담당 + +### 호출 방법 + +다음 요청 시 `/orchestrate-ptop-harness` 스킬 사용: +- "PTOP 프로젝트 전체 분석" +- "PTOP 아키텍처와 구현 분석" +- "PTOP 상태 점검" +- "PTOP 빌드 최적화" +- "PTOP 팀 조율" + +### 구성 요소 + +**Agents:** +- `.ubisam_agent/agents/ptop-lead.md` +- `.ubisam_agent/agents/ptop-developer.md` +- `.ubisam_agent/agents/build-devops-engineer.md` + +**Skills:** +- `.ubisam_agent/skills/analyze-ptop-architecture/SKILL.md` +- `.ubisam_agent/skills/analyze-ptop-implementation/SKILL.md` +- `.ubisam_agent/skills/optimize-ptop-build/SKILL.md` +- `.ubisam_agent/skills/orchestrate-ptop-harness/SKILL.md` + +**Workspace:** +- `_workspace/0X_*.md` (분석 결과 저장) diff --git a/docs/harness-ptop.md b/docs/harness-ptop.md index 79cd04e..fe919c3 100644 --- a/docs/harness-ptop.md +++ b/docs/harness-ptop.md @@ -39,4 +39,4 @@ ILSOS_PrismaticRD_PTOP(PtoP)의 3계층 아키텍처, 구현 상세, 빌드 최 | 날짜 | 변경 내용 | 대상 | 사유 | |------|----------|------|------| -| 2026-06-16 | 초기 구성 | agents/, skills/ | PTOP이 핵심 활성 모듈 | +| 2026-06-16 | 초기 구성: PTOP 분석 팀 구축 | agents/, skills/ | ILSOS_PrismaticRD_PTOP이 핵심 활성 모듈, 종합 분석 필요 | diff --git a/docs/기존파일비교/UCScannerObjectTeachingControlBoxViewModel.cs b/docs/기존파일비교/UCScannerObjectTeachingControlBoxViewModel.cs new file mode 100644 index 0000000..37ded23 --- /dev/null +++ b/docs/기존파일비교/UCScannerObjectTeachingControlBoxViewModel.cs @@ -0,0 +1,472 @@ +using Aurora; +using Aurora.Api; +using Aurora.View; +using AuroraIO; +using AuroraLaser; +using AuroraLaserScanner; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Reactive.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Input; +using System.Windows.Threading; +using VisionCommon; +using WeldingCommon.Models; +using WeldingCommon.Sequences; +using WeldingCommon.Settings; +using WeldingCommon.Utils; +using static AuroraLaser.Ekspla.NL202; + +namespace WeldingCommon.Views +{ + public class UCScannerObjectTeachingControlBoxViewModel : NotifyPropertyChangedBase + { + private readonly Logger Log = new Logger("UCScannerObjectTeachingControlBoxViewModel"); + + public ICommand CMD_VisibleChanged { get; } + + public ICommand CMD_RunScoutNormal { get; } + public ICommand CMD_StopScoutNormal { get; } + + public ICommand CMD_RunScoutFlyingVision { get; } + public ICommand CMD_StopScoutFlyingVision { get; } + + public ICommand CMD_RunTeachingRun { get; } + public ICommand CMD_StopTeachingRun { get; } + + public ICommand CMD_LaserGuideBeam { get; } + public ICommand CMD_LaserReadyCheck { get; } + public ICommand CMD_ImportObjectFromDXF { get; } + + private readonly IAuroraEnvironment _Aurora; + private SystemParameter _SystemParameter; + //private readonly EquipmentInfo _EquipmentInfo; + + private readonly Scanner _Scanner; + private readonly Laser _Laser; + private readonly IODevice _IODevice; + private readonly LaserConfig _LaserConfig; + + private ISequence _SeqScoutNormal; + private bool _IsSequenceRunning_ScoutNormal; + public bool IsSequenceRunning_ScoutNormal { get => _IsSequenceRunning_ScoutNormal; set => SetProperty(ref _IsSequenceRunning_ScoutNormal, value); } + + private ISequence _SeqScoutFlyingVision; + private bool _IsSequenceRunning_ScoutFlyingVision; + public bool IsSequenceRunning_ScoutFlyingVision { get => _IsSequenceRunning_ScoutFlyingVision; set => SetProperty(ref _IsSequenceRunning_ScoutFlyingVision, value); } + + private ISequence _SeqTeachingRun; + + private bool _IsSequenceRunning_TeachingRun; + public bool IsSequenceRunning_TeachingRun { get => _IsSequenceRunning_TeachingRun; set => SetProperty(ref _IsSequenceRunning_TeachingRun, value); } + + private bool _IsSelectedScoutNormal; + public bool IsSelectedScoutNormal { get => _IsSelectedScoutNormal; set => SetProperty(ref _IsSelectedScoutNormal, value); } + + private bool _IsGuidBeam; + public bool IsGuidBeam { get => _IsGuidBeam; set => SetProperty(ref _IsGuidBeam, value); } + private bool _IsLaserReady; + public bool IsLaserReady { get => _IsLaserReady; set => SetProperty(ref _IsLaserReady, value); } + + // Object Select Type + private bool _IsObjectSelectAll; + public bool IsObjectSelectAll { get => _IsObjectSelectAll; set => SetProperty(ref _IsObjectSelectAll, value); } + + private readonly DispatcherTimer _MonitorTimer; + + private SequenceDialog _SequenceDialog; + + private WinSequenceDiagram _WinSequenceDiagramScoutNormal; + private WinSequenceDiagram _WinSequenceDiagramScoutFlyingVision; + private WinSequenceDiagram _WinSequenceDiagramTeachingRun; + private WinSequenceDiagram _WinSequenceDiagramComponentTeachingRun; + + public UCScannerObjectTeachingControlBoxViewModel(IAuroraEnvironment aurora) + { + _Aurora = aurora; + + _SystemParameter = aurora.SettingEntry[CommonSettingIds.SystemParameter] as SystemParameter; + + _SeqScoutNormal = _Aurora.SequenceEntry[CommonSequenceIds.SeqScoutNormal]; + _SeqScoutFlyingVision = _Aurora.SequenceEntry[CommonSequenceIds.SeqScoutFlyingVision]; + + _Scanner = _Aurora.DeviceManager[CommonDeviceIds.Scanner] as Scanner; + _Laser = _Aurora.DeviceManager[CommonDeviceIds.Laser] as Laser; + _IODevice = _Aurora.DeviceManager[CommonDeviceIds.IODevice] as IODevice; + + // Trumpf 레이저인 경우에는 ComponentTeachingRun 실행 + _LaserConfig = _Aurora.DeviceManager.GetDeviceConfig(CommonDeviceIds.Laser) as LaserConfig; + _SeqTeachingRun = _Aurora.SequenceEntry[CommonSequenceIds.SeqComponentTeachingRun]; + + InitVariable(); + + CMD_VisibleChanged = new DelegateCommand(VisibleChanged); + + CMD_RunScoutNormal = new DelegateCommand(PerformCmdRunScoutNormal); + CMD_StopScoutNormal = new DelegateCommand(PerformCmdStopScoutNormal); + CMD_RunScoutFlyingVision = new DelegateCommand(PerformCmdRunScoutFlyingVision); + CMD_StopScoutFlyingVision = new DelegateCommand(PerformCmdStopScoutFlyingVision); + CMD_RunTeachingRun = new DelegateCommand(PerformCmdRunTeachingRun); + CMD_StopTeachingRun = new DelegateCommand(PerformCmdStopTeachingRun); + + CMD_LaserGuideBeam = new DelegateCommand(PerformCmdLaserGuideBeam); + CMD_LaserReadyCheck = new DelegateCommand(PerformCmdLaserReadyCheck); + CMD_ImportObjectFromDXF = new DelegateCommand(PerformCmdImportObjectFromDXF); + + _Aurora.EventObservable.AsObservable(AuroraSysEvent.SYS_SETTING_CHANGED) + .ObserveOnDispatcher() + .Subscribe(OnSettingReloaded); + + _MonitorTimer = new DispatcherTimer(); + _MonitorTimer.Tick += OnMonitoring; + _MonitorTimer.Interval = TimeSpan.FromMilliseconds(100); + + } + + private void InitVariable() + { + if (_SystemParameter.ScoutType == eScoutType.Normal) { + IsSelectedScoutNormal = true; + } else { + IsSelectedScoutNormal = false; + } + + IsSequenceRunning_ScoutNormal = false; + IsSequenceRunning_ScoutFlyingVision = false; + IsSequenceRunning_TeachingRun = false; + + IsGuidBeam = false; + IsLaserReady = false; + + IsObjectSelectAll = true; + } + + private void VisibleChanged(object obj) + { + bool visible = (bool)obj; + if (visible) { + _MonitorTimer.Start(); + } else { + _MonitorTimer.Stop(); + } + } + + private void OnSettingReloaded(object obj) + { + _SystemParameter = _Aurora.SettingEntry[CommonSettingIds.SystemParameter] as SystemParameter; + if (_SystemParameter.ScoutType == eScoutType.Normal) { + IsSelectedScoutNormal = true; + } else { + IsSelectedScoutNormal = false; + } + } + + private void OnMonitoring(object sender, EventArgs e) + { + // TODO: Laser Device 에 구현된 내용에 따르면, 일부 Laser Device 는 구현이 되어 있지 않은 상태. 항상 false + if (_Laser.GetPilotLaserStatus()) { + IsGuidBeam = true; + } else { + IsGuidBeam = false; + } + + if (_Laser.GetLaserReady()) { + IsLaserReady = true; + + // TODO: PRIBAT_L1, PRIBAT_L2 는 IO에 LaserOnLamp 를 켜고 끄는게 필요함. + // TODO: 우선 주석 처리 해 둠. 레이저 Type 별로 달라서 그런건지 확인하고, 어떻게 공통으로 구현할지를 생각해서 추가해 두자 + //_iODevice._iOUnit.WriteDoBit(Convert.ToInt32(ePDOAddress.LaserOnLampOn), true); + + } else { + IsLaserReady = false; + // TODO: PRIBAT_L1, PRIBAT_L2 는 IO에 LaserOnLamp 를 켜고 끄는게 필요함. + // TODO: 우선 주석 처리 해 둠. 레이저 Type 별로 달라서 그런건지 확인하고, 어떻게 공통으로 구현할지를 생각해서 추가해 두자 + //_iODevice._iOUnit.WriteDoBit(Convert.ToInt32(ePDOAddress.LaserOnLampOn), true); + } + + // TODO: PRIBAT_L1, PRIBAT_L2 는 Blow 의 상태 check 가 들어 있음. 장비 한정이므로 이를 check 하는 것을 modelItem 이나 setting 으로 빼서 진행되도록 함. + } + + private void PerformCmdRunScoutNormal(object obj) + { + var result = MessageUtil.ShowYesNoMessageDialog("Scanner Scout", "Start Scanner Scout Normal Sequence?"); + if (result == false) { + return; + } + + if (VisionRecipeManager.GetFovInfos().Count == 0) { + MessageUtil.ShowWarningToast(string.Format("Please check the Vision Recipe of the currently active Recipe.")); + return; + } + + if (_SeqScoutNormal.RunState == SequenceRunState.Running) { + MessageUtil.ShowWarningToast(string.Format("이미 시퀀스가 Start 상태입니다.", "SeqScoutNormal"), Log); + return; + } + + var scoutSeqParams = new ScoutSeqParams(VisionRecipeManager.GetFovInfos()[0], eDisplayType.Machine_B); + var param = new SeqScoutNormal_InitParam() { + IsRealTimeScout = false, + ScoutSeqParams = scoutSeqParams, + }; + + PopupSequenceDiagram(ref _WinSequenceDiagramScoutNormal, CommonSequenceIds.SeqScoutNormal, param); + + IsSequenceRunning_ScoutNormal = true; + _SeqScoutNormal.Start(param, OnCompleteScoutNormal); + + } + + private void OnCompleteScoutNormal(SequenceActionResult res) + { + if (res != null) { + if (res.Param is SeqScoutNormal_OutParam result && result.Success == true) { + IsSequenceRunning_ScoutNormal = false; + MessageUtil.ShowSuccessToast(string.Format("Success SeqScoutNormal")); + + CloseSequenceDiagram(_WinSequenceDiagramScoutNormal); + return; + } + } + + if (_Laser.GetLaserReady()) { + _Laser.LaserOff(); + } + + IsSequenceRunning_ScoutNormal = false; + MessageUtil.ShowWarningToast(string.Format("Fail SeqScoutNormal")); + } + + private void PerformCmdStopScoutNormal(object obj) + { + if (_SeqScoutNormal.RunState == SequenceRunState.Idle) { + return; + } + + IsSequenceRunning_ScoutNormal = false; + _SeqScoutNormal.Stop(); + } + + private void PerformCmdRunScoutFlyingVision(object obj) + { + var result = MessageUtil.ShowYesNoMessageDialog("Flying Vision", "Start Scanner Scout Flying Vision Sequence?"); + if (result == false) { + return; + } + + if (VisionRecipeManager.GetFovInfos().Count == 0) { + MessageUtil.ShowWarningToast(string.Format("Please check the Vision Recipe of the currently active Recipe.")); + return; + } + + if (_SeqScoutFlyingVision.RunState == SequenceRunState.Running) { + MessageUtil.ShowWarningToast(string.Format("이미 시퀀스가 Start 상태입니다.", "SeqScoutFlyingVision"), Log); + return; + } + + var scoutSeqParams = new ScoutSeqParams(VisionRecipeManager.GetFovInfos()[0], eDisplayType.Machine_B); + var param = new SeqScoutFlyingVision_InitParam() { + IsRealTimeScout = false, + ScoutSeqParams = scoutSeqParams, + SequenceAutoRunCount = 0, + WeldingScannerListNo = eListNo.LIST2, + }; + + PopupSequenceDiagram(ref _WinSequenceDiagramScoutFlyingVision, CommonSequenceIds.SeqScoutFlyingVision, param); + + IsSequenceRunning_ScoutFlyingVision = true; + _SeqScoutFlyingVision.Start(param, OnCompleteScoutFlyingVision); + + } + + private void OnCompleteScoutFlyingVision(SequenceActionResult res) + { + if (res != null) { + if (res.Param is SeqScoutFlyingVision_OutParam result && result.Success == true) { + IsSequenceRunning_ScoutFlyingVision = false; + MessageUtil.ShowSuccessToast(string.Format("Success SeqScoutFlyingVision")); + + CloseSequenceDiagram(_WinSequenceDiagramScoutFlyingVision); + return; + } + } + + if (_Laser.GetLaserReady()) { + _Laser.LaserOff(); + } + + IsSequenceRunning_ScoutFlyingVision = false; + MessageUtil.ShowWarningToast(string.Format("Fail SeqScoutFlyingVision")); + } + + private void PerformCmdStopScoutFlyingVision(object obj) + { + if (_SeqScoutFlyingVision.RunState == SequenceRunState.Idle) { + return; + } + + IsSequenceRunning_ScoutFlyingVision = false; + _SeqScoutFlyingVision.Stop(); + } + + private void PerformCmdRunTeachingRun(object obj) + { + var result = MessageUtil.ShowYesNoMessageDialog("Teaching Run", "Start TeachingRun Sequence?"); + if (result == false) { + return; + } + + if (IsLaserReady == false) { + MessageUtil.ShowWarningToast(string.Format("Please check Laser Ready.")); + return; + } + + // TODO: PRIBAT_L1, PRIBAT_L2 에서 Suction 과 CLAMP 상태 등에 따라 Laser 가 나가지 않도록 해야 함. + // TODO: 이 부분은, Delegate 를 사용해서, Interlock 을 각 장비별로 설정할 수 있도록 해주는게 좋겠음. + + if (_SeqTeachingRun.RunState == SequenceRunState.Running) { + MessageUtil.ShowWarningToast(string.Format("이미 시퀀스가 Start 상태입니다.", "SeqTeachingRun"), Log); + return; + } + + // TODO: NeedSetLaserTiming 는 트럼프 레이저 를 사용할 때, UploadWeldingList Sequence 에서 LaserTiming Set 을 해야 할때 사용함. + // TODO: SMW 와 LHS 는 트럼프 레이저를 사용하지 않으므로 false, PRIBAT_L1 과 PRIBAT_L2 할때 LaserType 을 읽어서 설정하는 것으로 추가하자 + + var currentModelInfo = EquipmentSpecificModelUtil.GetCurrentRecipeEquipmentSpecificModel(); + if (currentModelInfo.equipmentSpecificModel == null) { + MessageUtil.ShowWarningToast(string.Format("선택된 model 이 없습니다.", "SeqTeachingRun")); + return; + } + + var intervalTimeBlowOn = 0; + var intervalTimeBlowOff = 0; + + if (currentModelInfo.equipmentSpecificModel.UseBlow) { + intervalTimeBlowOn = currentModelInfo.equipmentSpecificModel.IntervalTimeBlowOn; + intervalTimeBlowOff = currentModelInfo.equipmentSpecificModel.IntervalTimeBlowOff; + } + + var param = new SeqTeachingRun_InitParam() { + IsObjectSelectAll = IsObjectSelectAll, + + //UseStream = _EquipmentInfo.UseStream, + UseStream = currentModelInfo.equipmentSpecificModel.UseStream, + + UseBlow = currentModelInfo.equipmentSpecificModel.UseBlow, + IntervalTimeBlowOn = intervalTimeBlowOn, + IntervalTimeBlowOff = intervalTimeBlowOff, + + NeedSetLaserTiming = false, + }; + + PopupSequenceDiagram(ref _WinSequenceDiagramTeachingRun, CommonSequenceIds.SeqComponentTeachingRun, param); + + IsSequenceRunning_TeachingRun = true; + _SeqTeachingRun.Start(param, OnCompleteTeachingRun); + + } + + private void OnCompleteTeachingRun(SequenceActionResult res) + { + if (res != null) { + if (res.Param is SeqTeachingRun_OutParam result && result.Success == true) { + IsSequenceRunning_TeachingRun = false; + MessageUtil.ShowSuccessToast(string.Format("Success SeqTeachingRun")); + + CloseSequenceDiagram(_WinSequenceDiagramTeachingRun); + return; + } + } + + if (_Laser.GetLaserReady()) { + _Laser.LaserOff(); + } + + IsSequenceRunning_TeachingRun = false; + MessageUtil.ShowWarningToast(string.Format("Fail SeqTeachingRun")); + } + + private void PerformCmdStopTeachingRun(object obj) + { + IsSequenceRunning_TeachingRun = false; + if (_SeqTeachingRun.RunState == SequenceRunState.Idle) { + return; + } + + _SeqTeachingRun.Stop(); + } + + + private void PopupSequenceDiagram(ref WinSequenceDiagram winSequenceDiagram, string seqId, object param) + { + if (winSequenceDiagram == null) { + winSequenceDiagram = new WinSequenceDiagram(seqId); + winSequenceDiagram.Closing += ClosingEvent; + winSequenceDiagram.SetParam(new SeqDrawerParam() { + SequenceId = seqId, + DiagramType = eDiagramType.Tree, + LineType = eLineType.Straight, + IsShowSubSequence = false, + VisibleKeys = new List() + }); + + winSequenceDiagram.Owner = Application.Current.MainWindow; + winSequenceDiagram.Width = 600; + winSequenceDiagram.SequenceParam = param; + winSequenceDiagram.Show(); + } else { + winSequenceDiagram.SequenceParam = param; + winSequenceDiagram.WindowState = WindowState.Normal; + winSequenceDiagram.Show(); + } + } + private void CloseSequenceDiagram(WinSequenceDiagram winSequenceDiagram) + { + Application.Current.Dispatcher.Invoke(() => { + if (winSequenceDiagram != null) { + winSequenceDiagram.Close(); + } + }); + } + private void ClosingEvent(object sender, CancelEventArgs e) + { + e.Cancel = true; + (sender as Window).Hide(); + } + + private void PerformCmdLaserGuideBeam(object obj) + { + if (!_Laser.GetPilotLaserSetBitStatus()) { + _Laser.PilotLaserOn(); + } else { + _Laser.PilotLaserOff(); + } + } + + private void PerformCmdLaserReadyCheck(object obj) + { + if (!_Laser.GetLaserReady()) { + _Laser.LaserOn(); //실질적으로 Laser Ready On을 의미함 + } else { + _Laser.LaserOff(); + } + } + + private void PerformCmdImportObjectFromDXF(object obj) + { + System.Windows.Forms.FileDialog dlg = new System.Windows.Forms.OpenFileDialog(); + dlg.InitialDirectory = @"D:\share\jungwoo.jin\모바일글라스도면 외 (3) 파일_20241028"; + dlg.DefaultExt = "dxf"; + dlg.Filter = "DXF 도면 파일 (*.dxf)|*.dxf"; + dlg.Title = "DXF 도면 열기"; + + if (dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK) { + CommonHelper.GetMainPage().DxfParser(dlg.FileName); + } + } + } +} diff --git a/docs/분석/2026/06/17/143131_laser_ready_di9_analysis.md b/docs/분석/2026/06/17/143131_laser_ready_di9_analysis.md new file mode 100644 index 0000000..b7b0945 --- /dev/null +++ b/docs/분석/2026/06/17/143131_laser_ready_di9_analysis.md @@ -0,0 +1,187 @@ +# Recipe View Laser Ready (DI 9) — IsLaserReady 조건 분석 + +**분석 일시:** 2026-06-17 14:31:31 +**대상:** `WeldingComponents/WeldingCommon/Views/Recipes/UCScannerObjectTeachingControlBoxViewModel.cs` +**비교 대상:** `docs/기존파일비교/UCScannerObjectTeachingControlBoxViewModel.cs` (이전 MOTF 프로젝트 버전) + +--- + +## 0. 요청 내용 (사용자) + +- 현장 RTC5보드는 갈보모터 X, Y 피드백값을 가져오는 게 없어서 DEVICE 상에서 COMMAND 값을 불러오도록 체크 해제 후 진행. 정확한 피드백값은 확인 불가한 상태. +- Laser 동작은 I/O로 하며, 프로그램 내에서 사용할 때 Simulation으로 동작한다. +- Recipe View에서 Laser Ready 부분이 DI(9)를 보고 있는데, 해당 BIT가 Laser를 IO On 해도 번들을 켜도 안 켜져서 전선 다시 확인 중인 상태. simulation이어서 true가 되긴 해야 하지만 한번 확인해볼 것. 이전 MOTF 작업하던 프로젝트랑 비교하기. +- **요청 1:** 현재 프로젝트의 `IsLaserReady` 변수 조건과 `docs/기존파일비교`의 같은 파일명 버전을 비교해서 달라진 점 분석. +- **요청 2:** IO(9)를 보고 있는 모든 코드 위치 파악. +- **요청 3:** 시뮬일 경우 버튼을 누르면 IsLaserReady 상태값이 변경되도록 하는 방안 검토 (DI(9)가 TRUE가 되지 않아 테스트가 막힘). — *코드 수정은 보류, 방안만 정리.* + +--- + +## 1. 결론 요약 + +- 점등이 안 되는 직접 원인은 **IsLaserReady 판정 소스가 바뀐 것**: + - 기존(MOTF): `_Laser.GetLaserReady()` (Laser 디바이스 상태, simulation이면 true) + - 현재: `_IODevice.ReadDI(9)` (**실제 하드웨어 DI 9번 비트**) +- 현재 코드는 시뮬레이션 Laser 디바이스가 아니라 **물리 DI(9)** 를 읽으므로, 배선으로 DI(9)가 실제로 들어오지 않으면 simulation이어도 true가 되지 않음. +- 작업일지에 적은 "simulation이면 true여야 한다"는 **기존 코드 기준**이며, 현재 코드는 그렇게 동작하지 않음. +- DI(9)는 "레이저 Ready 출력 → 장비 입력" 신호이므로 **OnMonitoring이 DI(9)를 읽는 것 자체는 설계상 정상.** 점등 미동작은 코드가 아니라 **레이저 Ready 출력 → DI(9) 결선** 문제 → 전선 확인이 맞는 방향. + +--- + +## 2. 코드 비교 — IsLaserReady 판정부 + +### 2.1 OnMonitoring() + +**기존(MOTF):** +```csharp +if (_Laser.GetLaserReady()) { + IsLaserReady = true; +} else { + IsLaserReady = false; +} +``` + +**현재:** +```csharp +try +{ + if (_IODevice.ReadDI(9)) // ← 물리 DI 9번을 직접 읽음 + { + IsLaserReady = true; + } + else + { + IsLaserReady = false; + } +} +catch (Exception ex) +{ + Log.E($"[OnMonitoring] DI 읽기 실패: {ex.Message}"); + IsLaserReady = false; +} +``` + +### 2.2 PerformCmdLaserReadyCheck() — Ready Check 버튼 + +**기존(MOTF):** +```csharp +if (!_Laser.GetLaserReady()) { + _Laser.LaserOn(); // 실질적으로 Laser Ready On +} else { + _Laser.LaserOff(); +} +``` + +**현재:** +```csharp +if(toggleIO) +{ + _IODevice._iOUnit.WriteDoBit(9, true); // ← DO 9 출력 + _IODevice._iOUnit.WriteDoBit(13, true); // ← DO 13 출력 + toggleIO = false; +} +else +{ + _IODevice._iOUnit.WriteDoBit(9, false); + _IODevice._iOUnit.WriteDoBit(13, false); + toggleIO = true; +} +``` + +### 2.3 부수 차이 (동작엔 직접 영향 적음) +- 필드 선언: 기존 `private readonly IODevice _IODevice;` → 현재 `private IODevice _IODevice;` (readonly 제거). 추가 필드 `public bool toggleIO = true;`. +- using 추가: `WeldingCommon`, `WeldingCommon.Devices.Laser`, `using static ...PAIX_PrismaticScanner`. +- 기존 OnMonitoring의 LaserOnLamp `WriteDoBit` 주석 블록은 현재 제거됨. +- `PerformCmdRunTeachingRun`: 기존은 `PopupSequenceDiagram(...)` 호출, 현재는 주석 처리. `OnCompleteTeachingRun`도 기존은 성공 처리/`ShowWarningToast` 활성, 현재는 주석 처리. + +--- + +## 3. 채널 충돌 — 버튼(DO9) vs 모니터링(DI9) + +``` +[버튼] WriteDoBit(9,true) ──▶ DO 9 (출력) + │ ?? 물리 배선 필요 ?? +[모니터] ReadDI(9) ◀─────────── DI 9 (입력) +``` + +- 버튼이 쓰는 것은 **DO9/DO13 출력**, 모니터링이 읽는 것은 **DI9 입력** → 별개 채널. +- DO9에 써도 DI9 입력으로 되돌아오려면 물리 배선(루프백/외부 결선)이 필요. 이 결선이 없으면 버튼을 눌러도 IsLaserReady가 안 켜짐 → 지금 전선 확인 중인 이슈와 일치. + +--- + +## 4. IO(9)를 보고 있는 모든 코드 위치 + +리터럴 `9`로 IO 9번에 접근하는 코드는 **한 파일, 3곳뿐**: + +`WeldingComponents/WeldingCommon/Views/Recipes/UCScannerObjectTeachingControlBoxViewModel.cs` + +| 라인 | 코드 | 종류 | 의미 | +|---|---|---|---| +| 182 | `if (_IODevice.ReadDI(9))` | DI 9 읽기 | Laser Ready 판정 (모니터링) | +| 462 | `_IODevice._iOUnit.WriteDoBit(9, true)` | DO 9 쓰기 | Ready Check 버튼 ON | +| 468 | `_IODevice._iOUnit.WriteDoBit(9, false)` | DO 9 쓰기 | Ready Check 버튼 OFF | + +> 같은 버튼이 인접 라인에서 `WriteDoBit(13, …)`도 함께 씀. + +### enum 매핑상 9번의 실제 의미 (`AuroraDevices/AuroraIO/Vendor/PAIX/PAIXIO.cs`) + +DI와 DO는 별개이고, 9번이 가리키는 게 서로 다름: + +``` +ePDIAddress (입력) ePDOAddress (출력) + DoorLockOpen4 = 7 WorkLEDOn = 7 + (8 - 미지정/미사용) (8 - 미지정) + (9 - 미지정/미사용) ◀DI9 ResetSwitchLamp = 9 ◀DO9 + DoorLockCloseMonitoring=10 ... + ModeKeySolOn = 13 ◀DO13 +``` + +- **DI 9번**: `ePDIAddress`에 이름이 없는 raw 비트 (Laser Ready 정식 주소로 매핑돼 있지 않음) +- **DO 9번**: `ResetSwitchLamp` (리셋 스위치 램프) +- **DO 13번**: `ModeKeySolOn` (모드키 솔레노이드) +- → 버튼이 켜는 DO9/13은 enum상 레이저와 무관한 비트이며, DI9도 미매핑 raw 입력. 코드 레벨에서 임시 매핑된 자리. +- DO9(`ResetSwitchLamp`)·DO13(`ModeKeySolOn`)을 enum 이름으로 호출하는 코드는 없음 → IO 9번 관련 코드는 위 3줄이 전부. + +--- + +## 5. 시뮬일 때 버튼으로 IsLaserReady 토글하는 방안 (수정 보류 — 방안만) + +선택안: **버튼이 IsLaserReady를 직접 토글.** 단, `OnMonitoring()`이 100ms마다 `ReadDI(9)`로 덮어쓰므로 시뮬일 때 모니터링 덮어쓰기를 같이 막아야 함. + +- 시뮬 판별은 `_IODevice.IsSimul` 로 가능 (Device 기반 public 속성, 외부 사용 예: `SeqAlarmMonitor.cs:466` `_IO.IsSimul`). + +**(1) `OnMonitoring()` — 시뮬일 때 DI(9) 읽기 건너뛰기** +```csharp +if (!_IODevice.IsSimul) +{ + try + { + if (_IODevice.ReadDI(9)) { IsLaserReady = true; } + else { IsLaserReady = false; } + } + catch (Exception ex) { Log.E($"[OnMonitoring] DI 읽기 실패: {ex.Message}"); IsLaserReady = false; } +} +``` + +**(2) `PerformCmdLaserReadyCheck()` — 시뮬일 때 직접 토글** +```csharp +if (_IODevice.IsSimul) +{ + IsLaserReady = !IsLaserReady; + return; +} +// 실장: 기존 DO9/13 Write 로직 그대로 +``` + +- 시뮬: 버튼 → IsLaserReady 토글, 모니터링 미관여 → 점등 확인 가능 +- 현장: 기존 로직 그대로 (DI9 읽기 / DO9·13 Write) — 변경 없음 + +> **상태:** 위 방안은 검토만 완료. 실제 코드 수정은 적용하지 않음 (사용자 보류 요청). + +--- + +## 6. 확인/해결 방향 + +- DI(9) 판정을 유지하려면 **레이저 Ready 출력 → DI(9) 물리 결선**을 확인할 것 (전선 점검이 맞는 방향). +- 시뮬레이션에서 동작 확인이 목적이면 5절 방안 적용을 검토. +- 버튼이 출력하는 DO9(`ResetSwitchLamp`)·DO13(`ModeKeySolOn`)이 실제 레이저 Enable 배선과 맞는지 확인 후, 맞지 않으면 버튼 동작을 의미에 맞게 재정의 필요. diff --git a/docs/분석/2026/06/17/143900_axis_position_interlock_setting_location.md b/docs/분석/2026/06/17/143900_axis_position_interlock_setting_location.md new file mode 100644 index 0000000..0a598a2 --- /dev/null +++ b/docs/분석/2026/06/17/143900_axis_position_interlock_setting_location.md @@ -0,0 +1,162 @@ +# PLC 전축 위치값 모니터링 InterLock — Setting 추가 위치 분석/추천 + +**분석 일시:** 2026-06-17 14:39:00 +**대상:** PTOP 프로젝트 Settings 구조 +**목표:** PLC에서 받는 전축(全軸) 위치값을 모니터링하기 위한 기준값(범위)을 Setting에 미리 저장하고, 특정 범위를 벗어나면 InterLock을 거는 기능. 저장할 Setting 클래스/위치/그룹명 추천. + +--- + +## 0. 요청 내용 (사용자) + +- PLC에서 전축 위치값을 모니터링할 값을 미리 Setting에 저장해두고, 특정 범위를 벗어나면 InterLock을 걸려고 함. +- Settings에 여러 Setting 클래스들이 있는데 **어느 클래스 어느 위치에 추가**하는 게 좋을지 추천. +- 적당한 위치가 없으면 **어느 클래스에 어떤 그룹명**으로 추가하면 되는지도 추천해서 docs 분석에 기록. + +--- + +## 1. 결론 요약 + +- **기존 클래스 중 "딱 맞는" 자리는 없음.** 전축 위치값의 범위(Min/Max) InterLock은 *축마다 반복되는 구조화 데이터*라, 평면(scalar) 속성 위주인 `SystemParameter`/`SequenceSetting`에 그대로 넣기엔 부적합. +- **추천(권장): 신규 전용 Setting 클래스 신설.** + - 클래스: `AxisPositionInterlockSetting : ISettingItem` + - 위치: `WeldingComponents/WeldingCommon/Settings/AxisPositionInterlockSetting.cs` + - 등록 ID: `CommonSettingIds.AxisPositionInterlock = "AxisPositionInterlock"` (EquipDefCommon.cs) + - 등록: `CommonEquipmentSetup.RegisterSettings()`에 `register(CommonSettingIds.AxisPositionInterlock, new AxisPositionInterlockSetting());` + - 그룹명(Category): `"Axis Position Interlock"` + - 축별 항목 구조는 기존 `TeachingPositionSetting.SettingPosition`(PrismaticGun) 패턴을 차용. +- **간이 대안(축 수가 적고 빠른 적용이 목적):** `SequenceSetting`에 `[Category("Axis Position Interlock")]` 그룹으로 전역 On/Off + 축별 Min/Max scalar 추가. 단, 축 수 확장에 약함. + +--- + +## 2. 현재 Settings 구조 (확인 결과) + +### 2.1 Setting 클래스 패턴 +- 모든 Setting은 `Aurora.Api.ISettingItem` 구현. +- 프로퍼티에 `PropertyTools.DataAnnotations`의 `[Category("그룹명")]`, `[DisplayName(...)]`, `[Browsable]`, `[ReadOnly]` 부착 → Setting UI에 그룹/표시명으로 노출. + +### 2.2 등록 흐름 +- ID 상수: `WeldingComponents/WeldingCommon/EquipDefCommon.cs` 의 `class CommonSettingIds` + ```csharp + public const string SystemParameter = "SystemParameterILSOS"; + public const string DoorInterlock = "DoorInterlock"; + public const string Sequence = "Sequence"; + // ... + ``` +- 인스턴스 등록: `WeldingComponents/WeldingCommon/CommonEquipmentSetup.cs` 의 `RegisterSettings(SettingRegister register)` + ```csharp + register(CommonSettingIds.SystemParameter, new SystemParameter()); + register(CommonSettingIds.LaserPowerCalParam, new LaserPowerCalParam()); + register(CommonSettingIds.Sequence, new SequenceSetting()); + register(CommonSettingIds.DoorInterlock, new DoorInterlockSetting()); + register(CommonSettingIds.RmsDatas, new SettingRmsDataList()); + ``` +- 소비(읽기): 시퀀스/뷰에서 `AuroraEnv.SettingEntry[CommonSettingIds.XXX] as XXX`, + 모니터 시퀀스에서는 `context.Settings[CommonSettingIds.Sequence] as SequenceSetting` (예: `SeqStatusMonitor.StartNode`). + +### 2.3 후보 클래스 평가 +| 클래스 | 위치 | 성격 | 전축 위치 InterLock 적합성 | +|---|---|---|---| +| `SystemParameter` | WeldingCommon/Settings | 전역 평면 파라미터 다수, `[Category]` 그룹 많음 | 축별 반복 구조엔 부적합. 단일 전역 토글 정도만 가능 | +| `SequenceSetting` | WeldingCommon/Settings | 모니터링 On/Off 토글 모음("Alarm Monitoring Device" 등), 모니터 시퀀스가 직접 읽음 | **전역 Enable 토글** 자리로는 적합. 축별 Min/Max 보관엔 부적합 | +| `DoorInterlockSetting` | WeldingCommon/Views/DoorInterlock | Door/Motor 인터락 전용(비번/해제 등) | 의미는 "인터락"으로 가깝지만 Door 도메인 전용 → 혼입 비권장 | +| `TeachingPositionSetting` | **PrismaticGun**/settings | 축별 X/Y/Z 위치 + ±Limit 배열(`SettingPosition[]`) | **구조 선례로 가장 유사**. 단 PrismaticGun 전용이라 PTOP에서 그대로 못 씀 → 패턴만 차용 | + +> 즉, "축별 범위" 구조의 정확한 선례는 `TeachingPositionSetting` 하나뿐이고, PTOP 공통 영역에는 대응 클래스가 없음. + +### 2.4 PLC 전축 위치값 매핑 현황 +- `Machines/ILSOS_PrismaticRD_PTOP/Device/PLC/PLCController.Address.cs` 의 `eWordInput`은 + Cell/Lot ID·Page·Step·Slot 위주이고 **축 위치값 워드는 아직 미정의(대부분 `EMPTY_xx`)**. +- 따라서 본 기능은 (1) Setting 신설 + (2) **PLC `eWordInput`에 축 위치 워드 주소 매핑 추가**가 함께 필요. +- 위치값 읽기는 `PLCController.Logic.cs`의 `ReadWord(eWordInput)` / `ReadWords(start,end)` 사용. + +--- + +## 3. 권장안 — 신규 전용 Setting 클래스 + +### 3.1 클래스 (스케치) +`WeldingComponents/WeldingCommon/Settings/AxisPositionInterlockSetting.cs` +```csharp +namespace WeldingCommon.Settings +{ + public class AxisPositionInterlockSetting : ISettingItem + { + [Category("Axis Position Interlock")] + [DisplayName("전축 위치 InterLock 사용")] + public bool UseAxisPositionInterlock { get; set; } = false; + + // 축별 범위 항목 (TeachingPositionSetting.SettingPosition 패턴 차용) + public AxisInterlockItem[] Axes { get; set; } + + public AxisPositionInterlockSetting() + { + // 전축 정의: 장비 축 개수에 맞춰 초기화 + Axes = new[] + { + new AxisInterlockItem("AxisX"), + new AxisInterlockItem("AxisY"), + new AxisInterlockItem("AxisZ"), + // ... 전축 + }; + } + } + + public class AxisInterlockItem : NotifyPropertyChangedBase + { + public string Name { get; set; } + public bool Use { get; set; } = true; // 축별 On/Off + public double Min { get; set; } // 하한 + public double Max { get; set; } // 상한 + // 필요 시: PLC 워드 주소 인덱스, AlarmLevel(Heavy/Light) 등 추가 + + public AxisInterlockItem() { } + public AxisInterlockItem(string name) { Name = name; } + } +} +``` + +### 3.2 ID 상수 추가 +`WeldingComponents/WeldingCommon/EquipDefCommon.cs` → `class CommonSettingIds` +```csharp +public const string AxisPositionInterlock = "AxisPositionInterlock"; +``` + +### 3.3 등록 +`WeldingComponents/WeldingCommon/CommonEquipmentSetup.cs` → `RegisterSettings()` +```csharp +register(CommonSettingIds.AxisPositionInterlock, new AxisPositionInterlockSetting()); +``` + +### 3.4 InterLock 판정 로직 위치 +- 모니터 시퀀스에서 주기적으로 PLC 축 위치를 읽어 Min/Max 비교 → 벗어나면 InterLock/알람. +- 권장 위치: `Machines/ILSOS_PrismaticRD_PTOP/Sequences/Monitoring/SeqStatusMonitor.cs` (또는 `SeqAlarmMonitor.cs`). + - `StartNode`에서 `context.Settings[CommonSettingIds.AxisPositionInterlock] as AxisPositionInterlockSetting` 캐싱. + - `UpdateStatus`/`UpdateIO` 루프에서 `ReadWord(eWordInput.축위치)` ↔ `Axes[i].Min/Max` 비교. + - 위반 시 `AlarmManager`로 알람 발생 + 필요 시 `_plc.WriteBit(...)`로 인터락 신호 출력(기존 `PC_HEAVY_ALARM_STATUS` 패턴 참조). + +--- + +## 4. 간이 대안 (빠른 적용용) + +축 수가 적고 구조화가 과하다면, `SequenceSetting`에 그룹만 추가: +```csharp +[Category("Axis Position Interlock")] +public bool UseAxisPositionInterlock { get; set; } = false; +public double AxisXMin { get; set; } +public double AxisXMax { get; set; } +public double AxisYMin { get; set; } +public double AxisYMax { get; set; } +// ... +``` +- 장점: 신규 클래스/등록 불필요(이미 모니터가 `CommonSettingIds.Sequence`로 읽음). +- 단점: 축 수 확장/가독성 약함. "SequenceSetting 변경 시 재부팅 필요"(클래스 상단 주석) 제약 있음. + +--- + +## 5. 최종 추천 + +1. **권장:** 신규 `AxisPositionInterlockSetting`(WeldingCommon/Settings) + `CommonSettingIds.AxisPositionInterlock` + `CommonEquipmentSetup.RegisterSettings()` 등록, 그룹명 **"Axis Position Interlock"**, 축별 항목은 `TeachingPositionSetting.SettingPosition` 패턴 차용. +2. 함께 필요: PLC `eWordInput`에 전축 위치 워드 주소 매핑 추가. +3. 판정/인터락은 `SeqStatusMonitor`(또는 `SeqAlarmMonitor`)에서 Setting을 읽어 Min/Max 비교 후 `AlarmManager`/`WriteBit`로 처리. +4. 빠른 PoC가 목적이면 4절 간이 대안으로 `SequenceSetting`에 "Axis Position Interlock" 그룹 추가도 가능(확장성은 낮음). + +> 상태: 위치/구조 추천만 정리. 실제 코드 추가는 미적용. diff --git a/docs/분석/2026/06/17/144442_sequencesetting_axis_interlock_properties.md b/docs/분석/2026/06/17/144442_sequencesetting_axis_interlock_properties.md new file mode 100644 index 0000000..68db3fd --- /dev/null +++ b/docs/분석/2026/06/17/144442_sequencesetting_axis_interlock_properties.md @@ -0,0 +1,86 @@ +# SequenceSetting "Axis Position Interlock" 그룹 추가 — 속성 설계 분석 + +**분석 일시:** 2026-06-17 14:44:42 +**대상 소스:** `WeldingComponents/WeldingCommon/Settings/SequenceSetting.cs` (`SequenceSetting` 클래스) +**관련 분석:** `docs/분석/2026/06/17/143900_axis_position_interlock_setting_location.md` (간이 대안 절) +**목표:** 전축 위치(섹터 각도) 모니터링 InterLock 기준값을 `SequenceSetting`에 그룹으로 추가. + +--- + +## 0. 확정 조건 (사용자) + +- **위치:** `SequenceSetting`에 `[Category("Axis Position Interlock")]` 그룹만 추가 (신규 클래스 신설 안 함). +- **속성:** 수직값 / 수평값을 **단일 기준값 + 허용오차** 형태로 등록 (Min/Max 아님). +- **단위:** 섹터값이라 **도(°, degree)**. +- **Enable 토글:** 추가 안 함 (`UseAxisPositionInterlock` 없음). +- **상태:** 코드 수정은 미적용. 분석/설계만 기록. + +--- + +## 1. 추가 위치 (소스) + +- 파일: `WeldingComponents/WeldingCommon/Settings/SequenceSetting.cs` +- 클래스: `public class SequenceSetting : ISettingItem` +- 추가 지점: 클래스 마지막 속성 `GroupRamingUse`(파일 기준 약 L69) **아래**에 신규 그룹 추가. +- 등록은 이미 되어 있음 — `CommonEquipmentSetup.RegisterSettings()`에서 + `register(CommonSettingIds.Sequence, new SequenceSetting());` (추가 등록 불필요). +- 소비(읽기): 모니터 시퀀스에서 `context.Settings[CommonSettingIds.Sequence] as SequenceSetting` + (예: `Machines/ILSOS_PrismaticRD_PTOP/Sequences/Monitoring/SeqStatusMonitor.cs` `StartNode`). + +--- + +## 2. 제안 속성 (스케치 — 미적용) + +```csharp +[Category("Axis Position Interlock")] +[DisplayName("수직 기준값 [deg]")] +public double VerticalSector { get; set; } = 0.0; + +[DisplayName("수직 허용오차 [deg]")] +public double VerticalTolerance { get; set; } = 0.0; + +[DisplayName("수평 기준값 [deg]")] +public double HorizontalSector { get; set; } = 0.0; + +[DisplayName("수평 허용오차 [deg]")] +public double HorizontalTolerance { get; set; } = 0.0; +``` + +> 속성명(`VerticalSector` / `HorizontalSector` 등)은 제안값. 명명 선호에 따라 조정 가능. + +--- + +## 3. 판정 로직 (참고) + +기준값 ± 허용오차 범위로 InterLock 판정: +``` +하한 = VerticalSector - VerticalTolerance +상한 = VerticalSector + VerticalTolerance +if (현재수직각도 < 하한 || 현재수직각도 > 상한) → InterLock +// 수평도 동일하게 HorizontalSector ± HorizontalTolerance +``` +- 위치: `SeqStatusMonitor`(또는 `SeqAlarmMonitor`)의 루프 노드에서 PLC 각도값과 비교. +- 위반 시 `AlarmManager`로 알람 발생 + 필요 시 `_plc.WriteBit(...)` 인터록 신호 + (기존 `PC_HEAVY_ALARM_STATUS` 출력 패턴 참조). + +--- + +## 4. 주의점 + +1. **재부팅 필요** — `SequenceSetting.cs` 상단 주석 *"SequenceSetting 이 변경되면, 재부팅 필요함."* + → 이 그룹 값을 변경하면 재부팅이 전제됨. +2. **PLC 각도 워드 미매핑** — `Machines/ILSOS_PrismaticRD_PTOP/Device/PLC/PLCController.Address.cs`의 + `eWordInput`에 전축(수직/수평) 섹터 각도 워드 주소가 아직 없음(대부분 `EMPTY_xx`). + → 실제 모니터링하려면 워드 주소 매핑 추가가 선행돼야 함. 읽기는 `PLCController.Logic.cs`의 + `ReadWord(eWordInput)` 사용. + +--- + +## 5. 적용 시 체크리스트 (요약) + +- [ ] `SequenceSetting.cs` 에 `Axis Position Interlock` 그룹 4속성 추가 (수직/수평 × 기준값/허용오차, 단위 deg) +- [ ] PLC `eWordInput` 에 수직/수평 섹터 각도 워드 주소 매핑 +- [ ] `SeqStatusMonitor`(또는 `SeqAlarmMonitor`)에 기준값±허용오차 판정 + 알람/인터록 처리 +- [ ] 설정 변경 후 재부팅 안내(설계상 제약) + +> 상태: 설계/위치 분석만. 실제 코드 추가는 미적용. diff --git a/docs/분석/2026/06/17/150031_laser_ready_di12_di24_change.md b/docs/분석/2026/06/17/150031_laser_ready_di12_di24_change.md new file mode 100644 index 0000000..9a60f67 --- /dev/null +++ b/docs/분석/2026/06/17/150031_laser_ready_di12_di24_change.md @@ -0,0 +1,122 @@ +# Laser Ready 판정 변경 — DI(9) → DI(12) & DI(24) 동시 조건 + +**분석 일시:** 2026-06-17 15:00:31 (15:10 정확한 매핑으로 갱신) +**대상:** `WeldingComponents/WeldingCommon/Views/Recipes/UCScannerObjectTeachingControlBoxViewModel.cs` +**관련:** `docs/분석/2026/06/17/143131_laser_ready_di9_analysis.md` (이전 DI9 분석) + +--- + +## 0. 요청/설명 (사용자) + +- 토글 버튼(`PerformCmdLaserReadyCheck`)을 누르면 **Control (9, 13)** 이 켜지고 꺼짐 (현행 `WriteDoBit(9)`, `WriteDoBit(13)` 유지). +- `OnMonitoring`에서는 현재 **DI(9)만** 보고 있는데, 9번이 아니라 **12번과 24번 둘 다(AND)** 보는 조건으로 변경. +- 확정 사항: **(1) AND 조건 맞음. (2) DI 24는 현장 배선 기준이 맞음(아래 enum 불일치 참고).** + +--- + +## 1. 매핑 정정 — 진짜 enum은 PAIXIO가 아니라 PAIX_PrismaticScanner + +처음엔 범용 `AuroraIO/Vendor/PAIX/PAIXIO.cs`의 `ePDIAddress`(DI12=StartSwitch, DI24=FanAlarm8)로 봤으나, +이 ViewModel은 상단에 `using static WeldingCommon.Devices.Laser.PAIX_PrismaticScanner;` 가 있고 +**레이저 전용 IO 매핑**(`WeldingComponents/WeldingCommon/Devices/Laser/LaserIOControl.cs` 내 `PAIX_PrismaticScanner`)을 사용함. +머신측 `Machines/ILSOS_PrismaticRD_PTOP/Device/IO/PAIX_PrismaticScanner.cs` 도 동일 enum. + +### 1.1 입력 `ePrismaticDIAddress` +| DI | 의미 | +|---|---| +| 9 | CoreLaser_Laser_Ready | +| 10 | CoreLaser_Error_State | +| 11 | CoreLaser_Laser_Output_State | +| **12** | **CoreLaser_Control_Status** | +| 13 | RingLaser_Laser_Ready | +| 14 | RingLaser_Error_State | +| 15 | RingLaser_Laser_Output_State | +| 23(enum) / **24(현장)** | **RingLaser_Control_Status** | + +### 1.2 출력 `ePrismaticDOAddress` +| DO | 의미 | +|---|---| +| 9 | CoreLaser_Control | +| 10 | CoreLaser_ErrorClearing | +| 13 | RingLaser_Control | +| 14 | RingLaser_ErrorClearing | +| 17 | EX_Red_Control | + +### 1.3 의미 해석 +- 토글 버튼: **DO9 = CoreLaser_Control**, **DO13 = RingLaser_Control** → Core·Ring 레이저 Control 출력 ON/OFF. +- 변경 후 모니터링: 그 피드백인 **Control_Status** 입력 2개를 봄 + - **DI12 = CoreLaser_Control_Status** + - **DI24 = RingLaser_Control_Status** (현장 배선 기준) +- 즉 "Core·Ring 두 레이저의 Control Status가 **모두 ON**" 일 때 `IsLaserReady = true`. + +> 참고: 기존 DI9는 **CoreLaser_Laser_Ready** 단독(Core만, Ring 미반영)이었음. +> 또한 `LaserIOControl.GetLaserReady()`는 `CoreLaser_Laser_Ready(9) && RingLaser_Laser_Ready(13)`로 *Laser_Ready* 비트를 봄(이번 변경은 *Control_Status* 비트 기준이라 다름). + +--- + +## 2. enum 불일치 (중요) + +`PAIX_PrismaticScanner.ePrismaticDIAddress`에 **`RingLaser_Control_Status = 23`** 으로 되어 있으나, +**현장 실제 DI는 24**. → enum 값이 outdated. + +- 영향 파일(둘 다 동일 enum): + - `WeldingComponents/WeldingCommon/Devices/Laser/LaserIOControl.cs` (L35) + - `Machines/ILSOS_PrismaticRD_PTOP/Device/IO/PAIX_PrismaticScanner.cs` (L28) + - (참고: `Machines/ILSOS_PrismaticScanner/Device/IO/PAIX_PrismaticScanner.cs` 도 동일) +- 권장: enum을 `RingLaser_Control_Status = 24` 로 정정. (정정 시 enum 상수로 깔끔하게 참조 가능) + +--- + +## 3. 수정 방안 (적용 예정) + +### 3.1 `OnMonitoring()` — DI 9 → (DI 12 && DI 24) +```csharp +try +{ + // Laser Ready: Core·Ring Control Status 둘 다 ON 일 때만 Ready + // DI 12 = CoreLaser_Control_Status, DI 24 = RingLaser_Control_Status(현장) + if (_IODevice.ReadDI(12) && _IODevice.ReadDI(24)) + { + IsLaserReady = true; + } + else + { + IsLaserReady = false; + } +} +catch (Exception ex) +{ + Log.E($"[OnMonitoring] DI 읽기 실패: {ex.Message}"); + IsLaserReady = false; +} +``` + +### 3.2 `PerformCmdLaserReadyCheck()` — 변경 없음 +- DO 9(CoreLaser_Control)·13(RingLaser_Control) 토글 유지. + +### 3.3 변경 요약 +| 위치 | 현재 | 변경 후 | +|---|---|---| +| `OnMonitoring()` | `ReadDI(9)` (CoreLaser_Laser_Ready) | `ReadDI(12) && ReadDI(24)` (Core·Ring Control_Status) | +| `PerformCmdLaserReadyCheck()` | `WriteDoBit(9/13)` | 변경 없음 | + +--- + +## 4. 구현 옵션 (가독성) + +- **옵션 A (최소 변경):** 리터럴 `ReadDI(12) && ReadDI(24)` + 주석으로 의미 표기. `_IODevice`가 base `IODevice` 타입이라 그대로 사용 가능. (즉시 적용 용이) +- **옵션 B (정공법):** enum `RingLaser_Control_Status = 24` 정정 후, `_IODevice`를 `PAIX_PrismaticScanner`로 캐스팅하여 + `ReadDiBit(ePrismaticDIAddress.CoreLaser_Control_Status) && ReadDiBit(ePrismaticDIAddress.RingLaser_Control_Status)` 로 상수 참조. + - 장점: 매직넘버 제거, 의미 명확. 단점: 캐스팅/타입 의존 추가. +- 권장: 우선 옵션 A로 적용하되, **enum 23→24 정정은 별도로 반드시 반영**(다른 코드가 23으로 참조 시 버그 가능성). + +--- + +## 5. 결론 / 적용 체크리스트 + +- [ ] `OnMonitoring()` 판정: `ReadDI(9)` → `ReadDI(12) && ReadDI(24)` (AND 확정) +- [ ] 버튼은 DO9/13 그대로 유지 +- [ ] `PAIX_PrismaticScanner.ePrismaticDIAddress.RingLaser_Control_Status` **23 → 24** 정정(LaserIOControl.cs / 머신 PAIX 파일) +- [ ] 의미: Core Control Status(12) AND Ring Control Status(24) 모두 ON → Laser Ready + +> 상태: 분석/수정 방안만 기록. 실제 코드 수정은 미적용. diff --git a/docs/분석/2026/06/17/162010_io_interface_map_path_source.md b/docs/분석/2026/06/17/162010_io_interface_map_path_source.md new file mode 100644 index 0000000..d5ee814 --- /dev/null +++ b/docs/분석/2026/06/17/162010_io_interface_map_path_source.md @@ -0,0 +1,103 @@ +# IO Interface Map(DI/DO 데이터 맵) 경로 — 소스상 설정 위치 분석 + +**분석 일시:** 2026-06-17 16:20:10 +**대상:** IODevice가 읽는 DI/DO 데이터 맵(CSV) 경로가 소스 어디서 설정되는가 +**관련:** `docs/분석/2026/06/17/150031_laser_ready_di12_di24_change.md` (DI12/24 판정 변경) + +--- + +## 0. 질문 (사용자) + +- `OnMonitoring`에서 읽는 DI 12 / 24 같은 IO의 **데이터 맵(CSV)** 의 실제 로드 경로가 소스상 어디서 설정되는지. + +--- + +## 1. 결론 + +- **실제 경로 문자열 값은 소스에 하드코딩돼 있지 않음.** +- 소스에서 확인되는 것은 (1) 경로를 **읽는 메커니즘**, (2) 경로 **속성 정의**, (3) 디바이스 **등록 시 기본값(빈 값)** 까지. +- 실제 경로는 **저장된 디바이스 Config(설정)** 에 들어 있으며, **Setting → Device(IODevice) Config 화면의 `IOInterfaceMapDI` 필드**(또는 디스크의 해당 config 파일)에서 지정됨. +- 영속화/역직렬화는 Aurora 프레임워크 DLL이 처리 → 이 레포 소스에는 경로 문자열 자체가 없음. + +--- + +## 2. 소스 흐름 (확인된 부분) + +### ① 디바이스 등록 — 기본 config 주입 (경로 미지정) +`Machines/ILSOS_PrismaticRD_PTOP/EquipmentSetup.cs:153` +```csharp +registry.Register(CommonDeviceIds.IODevice, + () => new PAIX_PrismaticScanner(AuroraEnv, CommonDeviceIds.IODevice, isSimul), + new IOUnitConfig()); // 기본 IOUnitConfig (경로 빈 값) +``` + +### ② config의 경로 속성 — 기본값 빈 문자열 +`AuroraDevices/AuroraIO/IOUnitConfig.cs` +```csharp +internal class IOUnitConnConfig : IConnConfig +{ + [InputFilePath] + public string IOInterfaceMapDI { get; set; } = ""; // DI 맵 경로 + [InputFilePath] + public string IOInterfaceMapDO { get; set; } = ""; // DO 맵 경로 + ... +} +``` +- `[InputFilePath]` 어트리뷰트 → Setting 화면에서 파일 경로 지정 필드로 노출. + +### ③ 활성화 시 그 경로로 CSV 로드 +`AuroraDevices/AuroraIO/IODevice.cs` +```csharp +protected override int DoActivate(IDeviceConfig config) { + _connConfig = config.ConnConfig; // L148 + ... + LoadIOTable(); // → MappingDIO() +} +// MappingDIO(): +var m_connConfig = _connConfig as IOUnitConnConfig; +fileName = m_connConfig.IOInterfaceMapDI; // 실제 로드 경로 +StreamReader sr = new StreamReader(fileName, ...); +``` +- 읽기 진입: `ReadDI(int)` → `_iOUnit.ReadDiBit(index)` (`IODevice.cs:212`). + +--- + +## 3. 데이터 맵(CSV) 실제 위치 (현장/레포) + +- 레포 복사본: `Doc/ILSOSConfig/ILSOS_PrismaticScanner/_device/_io/` +- 현장 런타임: `D:\ILSOSConfig\ILSOS_PrismaticScanner\_device\_io\` +- 기본 파일: `DI.csv`, `DO.csv` + 라인/공정별 변형 다수 + (`DI_LINE_CC-CAP_ASSY_WELDING.csv`, `DI_PRIBAT_L2.csv`, `DI_SMW.csv`, `DI_SEALPIN.csv`, `DI_PRE_WELDING.csv`, `DI_PILOT_LINE_ANODE_WELDING.csv`, `DI_PILOT LINE CATHODE WELDING.csv` 등) +- 어떤 파일이 로드되는지는 ②의 `IOInterfaceMapDI` 값(=저장된 Config)이 결정. + +### 주의 — 기본 `DI.csv`의 12/24/25는 레이저 Control Status가 아님 +``` +12,12,START SWITCH,START SWITCH +24,24,FAN 8 ALARM,FAN 8 ALARM +25,25,LASER Ready,LASER Ready +``` +- 기본 `DI.csv` 기준 12=START SWITCH, 24=FAN 8 ALARM, 25=LASER Ready. +- 코드(`PAIX_PrismaticScanner.ePrismaticDIAddress`)는 12=CoreLaser_Control_Status, 23=RingLaser_Control_Status로 다르게 보고 있음 → **enum과 실제 로드되는 CSV 맵이 불일치할 수 있음.** + +--- + +## 4. 정리표 + +| 알고 싶은 것 | 소스 확인 | 위치 | +|---|---|---| +| 경로를 읽는 코드 | O | `IODevice.cs` `MappingDIO()` / `ReadDI()` | +| 경로 속성 정의 | O | `IOUnitConfig.cs` `IOInterfaceMapDI` | +| 디바이스 등록(기본값) | O | `EquipmentSetup.cs:153` | +| 실제 경로 문자열 값 | X (소스에 없음) | 저장된 디바이스 Config / Setting UI | + +--- + +## 5. [TODO / 추후 확인할 것] + +- [ ] **현장 PC에서 실제 로드되는 DI/DO 맵 파일 확정**: Setting → Device → IODevice Config 의 `IOInterfaceMapDI` / `IOInterfaceMapDO` 값 확인 (또는 `D:\ILSOSConfig\ILSOS_PrismaticScanner\_device\_io\` 에서 실제 사용 파일). +- [ ] **해당 CSV의 DI 12 / 24 줄 의미 확인**: 레이저 Core/Ring Control Status가 맞는지, 아니면 START SWITCH / FAN 8 ALARM 인지. + - 만약 기본 `DI.csv`(12=START SWITCH, 24=FAN 8 ALARM)가 로드 중이면, `ReadDI(12) && ReadDI(24)` 판정이 의도와 다르게 동작 → CSV 맵 또는 인덱스 재확인 필요. +- [ ] **enum vs CSV 정합성**: `PAIX_PrismaticScanner.ePrismaticDIAddress`(12=CoreLaser_Control_Status, 23=RingLaser_Control_Status)와 현장 CSV 맵이 일치하는지 대조. 불일치 시 어느 쪽을 기준으로 맞출지 결정. +- [ ] (참고) `RingLaser_Control_Status` enum 값은 현재 23으로 두기로 함(현장 표기 23, 조건은 24 리터럴 사용). CSV 확인 후 최종 정합성 점검. + +> 상태: 소스 추적/위치 분석만. 실제 현장 Config 값 확인은 미완(위 TODO). diff --git a/docs/분석/2026/06/17/165341_plc_devicemap_update_260617.md b/docs/분석/2026/06/17/165341_plc_devicemap_update_260617.md new file mode 100644 index 0000000..ab3d92f --- /dev/null +++ b/docs/분석/2026/06/17/165341_plc_devicemap_update_260617.md @@ -0,0 +1,67 @@ +# PLC DeviceMap 갱신 (260617 Laser Interface 반영) + +**작업 일시:** 2026-06-17 16:53:41 +**근거 문서:** `docs/PtoP 유니셀 용접기 PLC 인터페이스_260617/유니셀 PtoP 단동기 Laser Interface_260617.xlsx` +**대상 경로:** `D:\ILSOSConfig\ILSOS_PrismaticRD_PTOP\External\PLC` + +--- + +## 0. 작업 요약 + +- 기존 `PLC_DeviceMap_*` 각 bit/word 파일을 `260617_backup` 으로 백업. +- 위 Excel(시트 M70K=Bit, D70K=Word)을 분석해 4개 맵 파일의 **내부 매핑(Comment)만** 갱신. 파일명/주소행 범위 유지. +- 네이밍: 기존 코드 스타일(`PC_*` / `PLC_*`) 유지. 양극(+)은 `_P` 접미사. 음/양극 모두 Excel대로 채움. + +## 1. Excel 구조 → 파일 매핑 + +| Excel 블록 | 방향 | 대상 파일 | 네이밍 | +|---|---|---|---| +| M70K 좌(70000~) | Laser→PLC (PC 출력) | Bit_Output_M | `PC_*` | +| M70K 우(70500~) | PLC→Laser (PC 입력) | Bit_Input_M | `PLC_*` | +| D70K 좌(70000~) | Laser1→PLC (PC 출력) | Word_Output_D | `PC_*` | +| D70K 우(70500~) | PLC→Laser1 (PC 입력) | Word_Input_D | `PLC_*` | + +- Excel 메모: "음/양극 레이저 하나로 사용 중이라 음극만 사용 하기로" (70101 셀). → 단, 요청에 따라 양극(+)도 전부 매핑함. + +## 2. 생성 결과 (매핑된 주소) + +### ① Bit_Output_M (M70000~70143) +- (-) 70000 LIVE, 70001 AUTO_STATUS, 70002 AUTO_COMPLETE, 70003 MANUAL_COMPLETE, 70004 LIGHT_AL, 70005 HEAVY_AL, 70008 PAGE_CHANGE_COMPLETE, 70009 RECIPE_CHANGE_COMPLETE, 70016~70018 WELD READY_ACK/BUSY/COMPLETE +- (+) 70100~70105, 70108, 70116~70118 WELD_01, 70119~70121 WELD_02, 70124 WELD_MONI_COMPLETE, 70125~70128 WELD_CH2(+MoniComplete), 70133~70135 M_POWER, 70137~70139 PARA(READ_ACK/SET_ACK/CHANGE_REQ) + +### ② Bit_Input_M (M70500~70643) +- (-) 70500~70509(LIVE,AUTO,AUTO_REQ,MANUAL_REQ,LIGHT_AL,HEAVY_AL,DOOR,EMO,PAGE_CHANGE_REQ,RECIPE_CHANGE_REQ), 70516~70518 WELD REQ/WAIT/COMP_ACK +- (+) 70600~70608, 70610 DISPLACE_USE, 70616~70618 WELD_01, 70619~70621 WELD_02, 70624 WELD_MONI_COMP_ACK, 70625~70628 WELD_CH2(+MoniCompAck), 70630 LASER2_ERROR_RESET, 70632~70635 LASER2_M_POWER, 70637~70639 LASER2_PC_PARA + +### ③ Word_Output_D (D70000~70099) +- 70000 PC_ErrorCode, 70001 PC_PageNo, 70002 PC_RecipeNo + +### ④ Word_Input_D (D70500~70599) +- 70500 PLCErrorCode, 70501 PLC_PageNo, 70502 PLC_RecipeNo, 70510 PLC_Head_Theta_Position + +## 3. 기존 대비 주요 변경 (delta) + +- **70502 (Word_Input): PLC_StepNo → PLC_RecipeNo** (의미 변경) +- **70509 (Bit_Input): PLC_RMS_USED → PLC_RECIPE_CHANGE_REQ** (의미 변경) +- 신규: Recipe_CHG(70009/70509), Weld_02·Weld_CH2·M_Power·PC_Para(양극), DisplaceUse(70610), Head_Theta_Position(70510), PC_RecipeNo(70002/70502) +- 신규 스펙에서 빠진 기존 항목: PC_WELD_NG_JUDGE(70020), PC_RMS_PARAMETER_READ_REQUEST_ACK(70037), PLC_ALARM_RESET(70530), PLC_RMS_PARAMETER_READ_REQUEST(70537) +- 기존 파일 버그 수정: Word_Input 70585~70599 가 `M`으로 오기 → `D`로 정정. + +## 4. [TODO / 추후 확인할 것] — 코드 정합성 ⚠️ + +PLC 맵(주소→의미)이 바뀌었으므로, 주소를 enum으로 읽는 코드와 **불일치** 가능성. 반드시 대조 필요: + +- [ ] **`Machines/ILSOS_PrismaticRD_PTOP/Device/PLC/PLCController.Address.cs`** + - `eWordInput.PLC_STEP_NO = 2` (D70502) → 새 맵은 **RecipeNo**. StepNo를 코드가 D70502로 읽으면 의미 깨짐. (StepNo 주소 재배정 or 코드 수정 필요) + - `eBitInput.PLC_RMS_USED = 0x09` (M70509) → 새 맵은 **RECIPE_CHANGE_REQ**. `SeqStatusMonitor.UpdateIO`가 `PLC_RMS_USED` 읽음 → 충돌. + - `eBitInput.PLC_ALARM_RESET = 0x1E`(M70530), `PLC_RMS_PARAMETER_READ_REQUEST = 0x25`(M70537) → 새 맵엔 없음. 사용처(`SeqStatusMonitor`, RMS) 확인. + - `eBitOutput.PC_WELD_NG_JUDGE = 0x14`(M70020), `PC_RMS_PARAMETER_READ_REQUEST_ACK = 0x25`(M70037) → 새 맵엔 없음. +- [ ] 신규 양극(+)/Laser2 신호(Weld_02, Weld_CH2, M_Power, PC_Para 등) 사용 여부 및 enum 추가 검토. +- [ ] **70628 Excel 라벨 중복**: Excel J열에서 70627·70628 모두 "Weld_CH2Comp_ACK"로 표기됨. 70628은 출력측(70128 Weld_CH2MoniComp) 대칭으로 보아 `PLC_WELD_CH2_MONI_COMP_ACK_P`로 매핑함 → **현장/PLC 측에 실제 의미 확인 필요.** +- [ ] "음극만 사용" 메모 vs 양극 전부 매핑 — 실제 운영에서 양극 사용 여부 확정. + +## 5. 백업 + +- `D:\ILSOSConfig\ILSOS_PrismaticRD_PTOP\External\PLC\260617_backup\` 에 기존 `PLC_DeviceMap_*` (복사본 포함) 6개 보관. + +> 상태: 4개 CSV 갱신 완료(라이브 config). 코드 정합성(4절)은 미확인 — 추후 작업. diff --git a/docs/분석/2026/06/17/171312_plc_map_io_direction_and_enum_alignment.md b/docs/분석/2026/06/17/171312_plc_map_io_direction_and_enum_alignment.md new file mode 100644 index 0000000..66d04cd --- /dev/null +++ b/docs/분석/2026/06/17/171312_plc_map_io_direction_and_enum_alignment.md @@ -0,0 +1,89 @@ +# PLC 맵 ↔ 소스 enum 정합성 분석 (Input/Output 검증 + StepNo + 배수) + +**분석 일시:** 2026-06-17 17:13:12 +**연계:** `docs/분석/2026/06/17/165341_plc_devicemap_update_260617.md` (맵 갱신 작업) +**대상 소스:** +- `WeldingComponents/WeldingCommon/Devices/PLC/PLCController.Address.cs` (base: `eCommonBit/WordInput/Output`) +- `WeldingComponents/WeldingCommon/Devices/PLC/PLCController.Config.cs` (base 로직) +- `Machines/ILSOS_PrismaticRD_PTOP/Device/PLC/PLCController.Address.cs` (machine: `eBit/WordInput/Output`) +- `Machines/ILSOS_PrismaticRD_PTOP/Device/PLC/PLCContoller.Config.cs` (생성자만) + +> 상태: **분석/판단만**. 소스·맵 추가 수정은 미적용. + +--- + +## 1. Input/Output 뒤바뀜 여부 → **안 바뀜 (정상)** + +판단 기준 = "기존 Input/Output 파일이 담던 방향" 과 "Excel 블록 방향" 대조: + +| 구분 | 기존 CSV | Excel 블록 | 방향 | 결론 | +|---|---|---|---|---| +| **Output** | `Bit_Output_M` = M70000~ , `PC_*` | 좌측 70000 = **"Laser→PLC"** | PC가 PLC로 **씀(OUT)** | 70000 = Output ✓ | +| **Input** | `Bit_Input_M` = M70500~ , `PLC_*` | 우측 70500 = **"PLC→Laser"** | PC가 PLC에서 **읽음(IN)** | 70500 = Input ✓ | + +- "Laser" = 우리(레이저측) PC. Laser→PLC = 우리가 PLC로 출력 = **Output**. PLC→Laser = 우리가 읽는 입력 = **Input**. +- 이번에 생성한 4파일도 동일 규칙(Output=70000블록 PC_*, Input=70500블록 PLC_*)으로 작성됨 → **방향 정상, 파일명 안 바뀜.** +- Word도 동일: `Word_Output_D`=D70000(PC_*), `Word_Input_D`=D70500(PLC_*). ✓ + +--- + +## 2. enum index = (주소 − base) 오프셋 (★ 배수/읽기 핵심) + +- CSV 파서(`ParsePLCBitWordAddressFromCSV`)는 행 순서대로 index 0,1,2… 부여. 행이 연속(M70500,70501,…)이므로 + **enum 값(offset) = (M주소 − base)**. + - 예: `PLC_WELD_READY_REQ = 0x10`(16) ↔ M70516, `PLC_AUTO_STATUS_P = 0x65`(101) ↔ M70601. ✓ +- 즉 소스 enum은 "주소"가 아니라 "오프셋"을 들고 있음. 맵에서 주소 의미가 바뀌면 **같은 오프셋을 읽는 코드가 다른 신호를 읽게 됨.** + +### 배수(읽기 블록) 우려 — 확인 필요 +- Bit는 word(16bit) 단위로 읽힘 → 대량 read count는 16의 배수. +- 이번 신규 최대 오프셋: **Bit In 139**(M70639 Laser2 PC_Para CHG_ACK), **Bit Out 139**(M70139), **Word In 10**(D70510 Head_Theta), **Word Out 2**(D70002). +- offset 139는 word #8(128~143) 안 → Bit는 **최소 9 word(144bit)** 읽어야 갱신됨. Word In은 **최소 11 word** 읽어야 D70510 갱신. +- [확인필요] PLCBase/통신의 **read count(블록 길이)** 가 위 최대 오프셋을 포함하는지. 작으면 고오프셋(특히 Laser2 130~139, Head_Theta 10)이 **안 읽힘**. (사용자 지적 "배수라 못 읽을 수도") + +--- + +## 3. StepNo "왜?" — 코드가 D70502를 StepNo로 읽음 + +- 코드: `Machines/ILSOS_PrismaticRD_PTOP/Sequences/SeqAutoRunPrismaticScanner.cs:422` + `_TotalWeldingStep = ReadWord(eWordInput.PLC_STEP_NO)` → **offset 2 = D70502** 를 "총 용접 step" 으로 사용. +- 기존 `Word_Input` CSV: D70502 = PLC_StepNo (코드와 일치). +- **신규 Excel: D70502 = PLC_RecipeNo** 로 재배정 → 같은 offset 2를 코드는 StepNo로 읽음 → `_TotalWeldingStep` 이 **RecipeNo 값을 읽게 됨(오동작)**. +- 판단: PLC 인터페이스가 StepNo를 빼고 offset 2에 RecipeNo를 넣은 것. **StepNo 신호 위치(또는 폐지 여부)와 RecipeNo offset 확정 필요.** (CSV 코멘트만으론 코드 동작 안 바뀜 — 실제 PLC가 그 주소에 무엇을 싣는지가 핵심) + +--- + +## 4. 신규 맵 ↔ 코드 enum 충돌 (사용처 확인됨) + +신규 Excel 맵이 **기존 사용 중인 신호를 빈칸/재배정** 한 것들: + +| 신호(offset/주소) | 코드 사용처 | 신규 맵 | 영향 | +|---|---|---|---| +| `PLC_RMS_USED` (0x09 / M70509) | `SeqStatusMonitor.cs:106` `Status.RMSUse` | 70509=RECIPE_CHANGE_REQ | RMSUse가 Recipe변경요청 비트를 읽음 ⚠️ | +| `PLC_ALARM_RESET` (0x1E / M70530) | `SeqStatusMonitor.cs:89` | 70530 빈칸 | 알람리셋 신호 안 들어옴 ⚠️ | +| `PLC_RMS_PARAMETER_READ_REQUEST` (0x25 / M70537) | `PLCController.Config.cs:114`, `RMSHandler.cs:69` | 70537 빈칸 | RMS 파라미터 read req 동작 안 함 ⚠️ | +| `PC_WELD_NG_JUDGE` (0x14 / M70020) | `SeqAutoRunPrismaticScanner.cs:429/896` write | 70020 빈칸 | NG 판정 PLC로 안 나감 ⚠️ | +| `PC_RMS_PARAMETER_READ_REQUEST_ACK` (0x25 / M70037) | `RMSHandler.cs:63/70/76` write | 70037 빈칸 | RMS ACK 안 나감 ⚠️ | + +- 신규 추가 신호(코드 enum 없음): Recipe_CHG(70009/70509), DisplaceUse(70610), Weld_02(70619~621), WeldMoni(70624), Weld_CH2(70625~628), Laser2 M_Power/PC_Para(70630~639), Head_Theta(D70510), RecipeNo(D70002/70502). + +--- + +## 5. 정합성 작업 시 방향 (제안 — 미적용) + +- enum 2계층 모두 정렬 필요: + 1. **base** `eCommonBit/WordInput/Output` (WeldingCommon) — Live/AutoReq/RMS 등 공통 로직. + 2. **machine** `eBit/WordInput/Output` (PrismaticRD_PTOP) — StepNo/NG_JUDGE/ALARM_RESET/RMS_USED 등. +- 기존 소스 최대한 유지 + **enum 값(offset)만** 신규 맵에 맞게 재매핑. 단: + - 빠진 신호(RMS, ALARM_RESET, NG_JUDGE)를 신규 맵에 **다시 넣을지 / 코드에서 제거할지** = PLC 인터페이스 측 확정 필요. + - StepNo offset 확정(폐지 or 이동). + - read count(배수)가 신규 최대 offset(139, Word 10) 포함하도록 확인. +- ⚠️ 동일 enum이 4개 머신(PrismaticRD_PTOP / PrismaticScanner / PrismaticCleanLaser / PrismaticGun)에 중복 존재 → PTOP만 바꿀지 공통 정책인지 결정. + +--- + +## 6. [TODO] + +- [ ] PLC 측에: StepNo 폐지/이동 여부, 빠진 RMS·ALARM_RESET·NG_JUDGE 주소 확정. +- [ ] read count(블록 길이/배수) 가 offset 139·Word10 포함하는지 검증. +- [ ] 확정 후 base + machine enum offset 재매핑 (기존 코드 유지, 값만). +- [ ] 70502(StepNo→RecipeNo), 70509(RMS_USED→RecipeCHG) 의 코드 영향 반영. diff --git a/docs/분석/2026/06/17/171642_plc_map_update_checklist.md b/docs/분석/2026/06/17/171642_plc_map_update_checklist.md new file mode 100644 index 0000000..1a1adf2 --- /dev/null +++ b/docs/분석/2026/06/17/171642_plc_map_update_checklist.md @@ -0,0 +1,48 @@ +# PLC DeviceMap 갱신 — 추후 확인 체크리스트 (260617) + +**작성:** 2026-06-17 17:16:42 +**상태:** 분석/맵파일 갱신만 완료. **소스(enum) 수정·코드 반영은 미적용 (보류).** + +## 관련 자료 (이미 남긴 분석) +- 맵 갱신 작업·델타: `docs/분석/2026/06/17/165341_plc_devicemap_update_260617.md` +- 방향/StepNo/enum/배수 분석: `docs/분석/2026/06/17/171312_plc_map_io_direction_and_enum_alignment.md` +- 근거 Excel: `docs/PtoP 유니셀 용접기 PLC 인터페이스_260617/유니셀 PtoP 단동기 Laser Interface_260617.xlsx` +- 백업: `D:\ILSOSConfig\ILSOS_PrismaticRD_PTOP\External\PLC\260617_backup\` + +--- + +## 이미 완료한 것 (참고) +- [x] 기존 `PLC_DeviceMap_*` 6개 → `260617_backup` 백업 +- [x] Excel(M70K/D70K) 분석, 4개 CSV 내부 매핑 갱신(파일명·범위 유지, 코드 스타일 네이밍, 음/양극 전부) +- [x] Input/Output 방향 검증 → **안 바뀜(정상)** 확인 +- [x] Word_Input 70585~70599 `M`오기 → `D` 정정 + +--- + +## 추후 확인 체크리스트 (PLC 인터페이스 측 협의 필요) + +### A. 의미 재배정된 주소 (코드 영향 큼) +- [ ] **D70502: StepNo → RecipeNo** — `SeqAutoRunPrismaticScanner.cs:422` 가 `eWordInput.PLC_STEP_NO`(offset 2)로 `_TotalWeldingStep` 읽음. StepNo **폐지? 다른 offset 이동?** 확정. RecipeNo offset 확정. +- [ ] **M70509: PLC_RMS_USED → RECIPE_CHANGE_REQ** — `SeqStatusMonitor.cs:106` `Status.RMSUse` 가 offset 9 읽음. RMS_USED 위치 재확정. + +### B. 신규 맵에서 빠진(빈칸) 기존 사용 신호 — 코드 제거 vs 주소 재부여 결정 +- [ ] **PLC_ALARM_RESET** (M70530) — `SeqStatusMonitor.cs:89` 사용. 빠지면 알람리셋 동작 안 함. +- [ ] **PLC_RMS_PARAMETER_READ_REQUEST** (M70537) — `PLCController.Config.cs:114`, `RMSHandler.cs:69` 사용. +- [ ] **PC_WELD_NG_JUDGE** (M70020) — `SeqAutoRunPrismaticScanner.cs:429/896` write. 빠지면 NG 판정 PLC 미전달. +- [ ] **PC_RMS_PARAMETER_READ_REQUEST_ACK** (M70037) — `RMSHandler.cs:63/70/76` write. + +### C. 신규 추가 신호 — enum 추가/사용 여부 검토 +- [ ] Recipe_CHG(70009/70509), DisplaceUse(70610), Weld_02(70619~621), WeldMoni(70624), Weld_CH2(70625~628), Laser2 M_Power/PC_Para(70630~639), Head_Theta_Position(D70510), RecipeNo(D70002/70502) +- [ ] **70628 Excel 라벨 중복**("Weld_CH2Comp_ACK" 70627·70628 동일) — 출력 대칭상 70628=MoniComp_ACK로 매핑함. 실제 의미 확인. + +### D. 읽기 블록(배수) 검증 +- [ ] PLC **read count(블록 길이)** 가 신규 최대 offset 포함하는지 — Bit In/Out **offset 139**(M70639/M70139), **Word In offset 10**(D70510). Bit는 16비트(word) 배수로 읽힘 → 최소 9 word(144bit) / Word In 11 word 필요. 작으면 Laser2(130~139)·Head_Theta 미갱신. + +### E. enum 정렬 범위/정책 +- [ ] **enum 2계층** 모두 정렬: base `eCommonBit/WordInput/Output`(WeldingCommon) + machine `eBit/WordInput/Output`(PrismaticRD_PTOP). 기존 소스 유지, **offset 값만** 재매핑. +- [ ] 동일 enum이 **4개 머신**(PrismaticRD_PTOP / PrismaticScanner / PrismaticCleanLaser / PrismaticGun)에 중복 — PTOP만 변경할지, 공통 적용할지 정책 결정. + +--- + +## 결론 +- 맵 CSV 갱신은 반영됨. **소스 enum/코드는 위 A~E 확정 후** 수정 예정. 지금은 보류. diff --git a/docs/분석/2026/06/17/184011_eroom_light_rs232_analysis.md b/docs/분석/2026/06/17/184011_eroom_light_rs232_analysis.md new file mode 100644 index 0000000..ebde20e --- /dev/null +++ b/docs/분석/2026/06/17/184011_eroom_light_rs232_analysis.md @@ -0,0 +1,264 @@ +# 현장 조명(EROOM) PC 제어 — 소스 구조 분석 및 수정 가이드 + +**분석 일시:** 2026-06-17 18:40:11 +**요청자:** 안동준 과장님 요청사항 — *"조명을 PC에서 조정할 수 있도록"* +**대상 조명:** 현장 EROOM 조명 (제조사: **이룸인더스트리**, eroomi.com) +**연결 상태:** PC ↔ 조명 컨트롤러 **RS232 9핀** 직결 +**목적:** 현재 조명을 무엇이 제어하는지 파악하고, 나중에 PC 제어로 수정하기 위한 기준 문서 + +--- + +## 0. 요청 내용 (사용자) + +- 현장 조명에 **MANUAL / REMOTE** 두 모드가 있고, **REMOTE**로 두면 PC와 통신 가능할 텐데 현재는 **소스 내에서 연결이 안 된 상태**. +- 조명을 조정하는 게 무엇인지 분석해서 docs에 정리(나중에 수정 예정). +- 조명은 **EROOM 조명**이며 PC에 **RS232 9핀 포트로 연결**되어 있음. → *장치관리자에서 포트 확인해야 하나?* +- Device에 **`Light`** 라는 디바이스로 등록되어 있을 것. + +--- + +## 1. 결론 요약 + +1. 소스에는 이미 **`Light` 디바이스**가 등록되어 있다. + - 위치: `WeldingComponents/WeldingCommon/CommonEquipmentSetup.cs:226` + - 등록: `CommonDeviceIds.Light` → `new LightDevice(..., eLight.ERI, isSimul)` + - 즉 **드라이버 타입이 `eLight.ERI`(ERI 4채널)** 로 고정되어 있다. +2. 조명 통신은 **RS232 시리얼** 기반이다. (`LightConfig` 의 `SerialConnConfig`) + - 기본값: **PortNum=1, BaudRate=19200, 8-N-1, ReadTimeout=500** +3. **"연결 안 됨"의 유력한 원인 3가지**: + - (a) **드라이버 불일치**: 현재 `eLight.ERI` 드라이버는 ERI사 ASCII 프로토콜(`setbrightness ch val\r` 등)을 쓴다. EROOM 컨트롤러의 프로토콜과 **명령어 포맷이 다르면** 같은 포트에 연결돼도 동작하지 않는다. + - (b) **시리얼 설정 불일치**: EROOM(EUV 계열) 컨트롤러는 **38,400bps** 사양이 확인된다. 현재 소스 기본값은 **19200**. → BaudRate/포트번호 불일치 가능. + - (c) **컨트롤러 모드**: 컨트롤러 전면 스위치가 **MANUAL**이면 RS232 명령을 무시한다. → 반드시 **REMOTE**로 둬야 PC 명령이 먹는다. +4. 따라서 PC 제어를 살리려면 **① 컨트롤러 REMOTE 전환 → ② COM 포트/Baud 맞추기 → ③ (필요 시) EROOM 전용 드라이버 추가** 순서로 접근한다. + +--- + +## 2. 장치관리자 COM 포트 확인 (사용자 질문 답변) + +**네, 먼저 장치관리자에서 포트를 확인하는 게 맞습니다.** + +- RS232 9핀이 메인보드 시리얼이든 USB-to-Serial 젠더든, Windows에서는 **`포트(COM & LPT)`** 항목에 **`COMx`** 로 잡힙니다. +- 확인 순서: + 1. `장치관리자` → `포트(COM & LPT)` 펼치기 → 조명 케이블 연결/분리 시 나타났다 사라지는 항목이 해당 포트. + 2. 그 항목 더블클릭 → `포트 설정` 탭에서 **비트/초(Baud), 데이터 비트, 패리티, 정지 비트**가 컨트롤러 사양과 일치하는지 확인. + 3. 잡힌 **COM 번호**를 소스의 `LightConfig.ConnConfig.PortNum` 과 맞춰야 함. +- USB-Serial인 경우 칩셋 드라이버(예: FTDI, Prolific, CH340)가 설치돼 있어야 COM 포트가 잡힙니다. 안 잡히면 드라이버부터 설치. +- 포트가 다른 프로그램(터미널 등)에서 점유 중이면 프로그램에서 열 수 없으니, 점검용 터미널은 닫고 테스트. + +> 점검 팁: 장치관리자에서 COM 번호/Baud를 확인한 뒤, 시리얼 터미널(예: 테라텀)로 컨트롤러를 **REMOTE 모드**에 두고 EROOM 매뉴얼 명령을 직접 쳐서 조명이 반응하는지부터 확인하면, 배선/포트 문제인지 소스 문제인지 분리할 수 있다. + +--- + +## 3. 소스 구조 — 조명을 제어하는 경로 + +### 3.1 디바이스 등록 (시작점) + +`WeldingComponents/WeldingCommon/CommonEquipmentSetup.cs:226` +```csharp +registry.Register(CommonDeviceIds.Light, + () => new LightDevice(CommonDeviceIds.Light, eLight.ERI, isSimul), // ← 드라이버 타입 ERI 고정 + new LightConfig(), + device => new UCLight() { DataContext = new UCLightViewModel(AuroraEnv, CommonDeviceIds.Light, _SequenceSetting.LightValue) }); +``` + +### 3.2 디바이스 본체 / 드라이버 선택 + +`AuroraDevices/AuroraLight/LightDevice.cs` +- `eLight` enum: **`LvsPn`, `ALT_4CH`, `ALT_8CH`, `ERI`** — **EROOM 없음.** +- `DoActivate()` 에서 타입에 따라 실제 드라이버 인스턴스 생성: + - `eLight.ERI` → `new ERILight(DeviceId, IsSimul, 4)` (4채널) +- 연결: `_light.InitInternal(config.ConnConfig as SerialConnConfig)` + +### 3.3 시리얼 설정 + +`AuroraDevices/AuroraLight/LightConfig.cs` +```csharp +ConnConfig = new SerialConnConfig() { + PortNum = 1, // ← 장치관리자 COM 번호와 맞춰야 함 + BaudRate = 19200, // ← EROOM(EUV 계열)은 38400 사양 확인됨, 불일치 가능 + Parity = Parity.None, StopBits = StopBits.One, DataBits = 8, + ReadTimeout = 500 +}; +``` + +### 3.4 ERI 드라이버 프로토콜 (현재 연결되는 명령어) + +`AuroraDevices/AuroraLight/ERI/ERILight.cs` — **ASCII 텍스트 + `\r` 종결** +| 동작 | 명령 포맷 | +|---|---| +| 밝기 설정 | `setbrightness {channel} {value}\r` (value 0~255) | +| On/Off | `seton {channel} {0|1}\r` | +| On 상태 조회 | `geton {channel}\r` | + +> EROOM 컨트롤러 매뉴얼의 명령어가 위와 다르면, ERI 드라이버로는 통신이 안 된다(=현재 "연결 안 됨" 상태의 핵심 원인 후보). + +### 3.5 시퀀스에서의 사용 (자동운전) + +`Machines/ILSOS_PrismaticRD_PTOP/Sequences/SeqAutoRunPrismaticScanner.cs` +- 변수: `private LightDevice _Light;` (L138) +- 획득: `_Light = context.GetLight();` (L1132) — `SequenceContextEx.GetLight()` → `context.Devices[CommonDeviceIds.Light]` +- 점등: 자동운전 시 `_Light.SetLightValue(lightValues)` 로 4채널 ON (L356~359, L535~537) +- 기본 밝기값: `lightValues = {255,255,255,255}` (L193), 레시피값은 `SequenceSetting.LightValue` / `CompensationLightValue` + +--- + +## 4. EROOM 조명 컨트롤러 (웹 조사 결과) + +- **제조사:** 이룸인더스트리 (eroomi.com) — 머신비전 조명/컨트롤러 전문. (사이트 TLS 인증서 문제로 직접 페이지 파싱은 실패, 검색 요약 기준) +- **대표 컨트롤러 사양(EUV-FX7 등 기준):** + - 통신: **RS232, 38,400 bps**, MANUAL/REMOTE 지원 + - 출력: **0~100%** 파워 제어 + - 채널: **최대 4채널** 점등 start/stop 입력 + - 조사 시간(irradiation time) 설정, 전면 엔코더로 파라미터 조정 및 채널값 메모리 저장 +- **주의:** 정확한 RS232 **명령어 포맷/체크섬/종결문자**는 제품 매뉴얼로 확정 필요. (밝기 스케일이 ERI의 0~255가 아니라 0~100%일 가능성 높음 → 변환 필요) + +--- + +## 5. 별도로 존재하는 CCS 조명 코드 (참고 — 본 건과 무관) + +- `AuroraVision/VisionLight/Light_CCS.cs` + `LightControlCCS_Library.dll`(C++) 가 별도로 존재. +- 단, 이건 **Socket(TCP/IP) 기반**(`managementMain.Connect(ip, port)`)이라 **RS232 EROOM 건과는 연결 방식이 다름**. +- PTOP 빌드 산출물 폴더에 `LightControlCCS_Library.dll` 이 복사돼 있으나, PTOP 소스에서 `Light_CCS` 를 직접 참조하지는 않음. → **이번 건과 혼동 주의.** + +--- + +## 6. 나중에 수정할 때 체크리스트 (코드 수정은 보류, 방안만 정리) + +### Step 1 — 하드웨어/포트 확정 (코드 수정 전) +- [ ] 컨트롤러 전면 스위치 **REMOTE** 전환 +- [ ] 장치관리자에서 **COM 번호** 확인, **Baud(38400 추정)/패리티/스톱비트** 확인 +- [ ] 시리얼 터미널로 EROOM 매뉴얼 명령 직접 전송 → 조명 반응 확인 (배선/포트 문제 격리) + +### Step 2 — 설정만으로 해결되는 경우 (EROOM ↔ ERI 프로토콜이 호환될 때) +- [ ] `LightConfig` 의 `PortNum` 을 실제 COM 번호로 +- [ ] `BaudRate` 19200 → 38400 등 사양에 맞게 +- [ ] (설정값은 가능하면 장비 Setting/UI에서 변경하도록, 하드코딩 지양) + +### Step 3 — 프로토콜이 다른 경우 (EROOM 전용 드라이버 신규 추가) +- [ ] `AuroraDevices/AuroraLight/eLight` enum 에 `EROOM` 추가 +- [ ] `AuroraDevices/AuroraLight/EROOM/EROOMLight.cs` 신규 (`SerialConn, IEROOMLight` 구현, ERI 클래스 패턴 참고) + - EROOM 매뉴얼대로 명령 포맷/밝기 스케일(0~100%) 구현 +- [ ] `LightDevice.DoActivate()` 의 `switch(_lightType)` 에 `case eLight.EROOM` 분기 추가 +- [ ] `CommonEquipmentSetup.cs:226` 의 `eLight.ERI` → `eLight.EROOM` 으로 변경 +- [ ] `ILight.cs` 에 필요 시 `IEROOMLight` 인터페이스 추가 + +### Step 4 — 검증 +- [ ] 시뮬레이션 해제 후 자동운전에서 `_Light.SetLightValue` 로 점등 확인 +- [ ] 수동/UI(`UCLight`)에서 채널별 밝기 제어 확인 + +--- + +## 7. 핵심 파일 위치 정리 + +| 역할 | 경로 | +|---|---| +| 조명 디바이스 등록 | `WeldingComponents/WeldingCommon/CommonEquipmentSetup.cs:226` | +| 조명 디바이스 본체/드라이버 선택 | `AuroraDevices/AuroraLight/LightDevice.cs` | +| 조명 드라이버 인터페이스 | `AuroraDevices/AuroraLight/ILight.cs` | +| 시리얼/포트 설정 | `AuroraDevices/AuroraLight/LightConfig.cs` | +| 현재 사용 중인 ERI 드라이버 | `AuroraDevices/AuroraLight/ERI/ERILight.cs` | +| 시퀀스 사용처 | `Machines/ILSOS_PrismaticRD_PTOP/Sequences/SeqAutoRunPrismaticScanner.cs` (L138, 1132, 356~, 535~) | +| 디바이스 접근 헬퍼 | `WeldingComponents/WeldingCommon/Sequences/SequenceContextEx.cs:54` (`GetLight`) | +| (참고) CCS Socket 조명 | `AuroraVision/VisionLight/Light_CCS.cs` | + +--- + +--- + +## 8. eLight.ERI 제어 소스 — 참고 코드/위치 모음 + +> 현재 `Light` 디바이스가 **`eLight.ERI` 드라이버로 등록**되어 동작하므로, EROOM 제어 구현 시 **그대로 베껴 쓸 수 있는 레퍼런스**가 ERI 계열이다. 아래는 제어 전체 흐름(등록 → 디바이스 → 드라이버 → UI → 시퀀스)의 실제 코드/위치. + +### 8.1 전체 제어 흐름 한눈에 + +``` +CommonEquipmentSetup.RegisterDevices() ← 디바이스 등록 (eLight.ERI 지정) + │ + ▼ +LightDevice (AuroraLight) ← 추상 디바이스, DoActivate에서 드라이버 선택 + │ _light = new ERILight(...) + ▼ +ERILight : SerialConn, IERILight ← RS232 ASCII 프로토콜 실제 송수신 + ▲ ▲ + │ │ +UCLightViewModel (수동 UI) SeqAutoRun...(자동운전) ← 두 곳에서 제어 호출 +``` + +### 8.2 ① 디바이스 등록 — `WeldingComponents/WeldingCommon/CommonEquipmentSetup.cs` + +- L48: 디바이스 활성상태 표시 등록 `.AddDeviceActiveStatus(CommonDeviceIds.Light)` +- L226~227: 디바이스 + UI 등록 +```csharp +registry.Register(CommonDeviceIds.Light, + () => new LightDevice(CommonDeviceIds.Light, eLight.ERI, isSimul), // 드라이버 타입 + new LightConfig(), // 시리얼 설정 + device => new UCLight() { // 제어 UI + DataContext = new UCLightViewModel(AuroraEnv, CommonDeviceIds.Light, _SequenceSetting.LightValue) + }); +``` + +### 8.3 ② 디바이스 본체 — `AuroraDevices/AuroraLight/LightDevice.cs` + +- `eLight` enum (L16~22): `LvsPn, ALT_4CH, ALT_8CH, ERI` → **EROOM 추가 지점** +- `DoActivate()` (L40~64): `switch(_lightType)` 에서 드라이버 생성 + `InitInternal(SerialConnConfig)` → **EROOM case 추가 지점** +- 외부 공개 제어 API: + - `SetLightValue(List values)` (L88) / `SetLightValue(int ch, int val)` (L93) + - `CheckLightOnAndValue(int val)` (L100) — ERI 전용으로 채널별 On+밝기 검증 +- `DoDeactivate()` (L66~79): ERI는 전 채널 0으로 끄고 시리얼 Close + +### 8.4 ③ ERI 시리얼 드라이버 — `AuroraDevices/AuroraLight/ERI/ERILight.cs` (RS232 프로토콜 본체) + +| 기능 | 메서드 | 명령/응답 포맷 | 라인 | +|---|---|---|---| +| 연결 | `InitInternal(SerialConnConfig)` | `Open(connConfig)` | L30 | +| 밝기 설정 | `SetLightBrightness(ch, val)` | TX `setbrightness {ch} {val}\r` (val 0~255) | L134 | +| On/Off | `SetLightPowerStatus(ch, on)` | TX `seton {ch} {0\|1}\r` | L122 | +| On 조회 | `GetLightOn(ch, out isOn)` | TX `geton {ch}\r` / RX `geton {idx} {ch} {0\|1}` | L186 | +| 밝기 조회 | `GetLightValue(ch, out val)` | TX `getbrightness {ch}\r` / RX `getbrightness {idx} {ch} {val}` | L219 | +| 채널 검증 | `ChannelValidCheck` (0~ch-1) / `ValueValidCheck` (0~255) | — | L162 / L172 | + +- 부모 `SerialConn` 의 `Open / Write / WriteAndReadLine / IsConnected / Close` 사용. +- **EROOM 적용 시 주의:** ASCII 명령어/종결문자(`\r`)/밝기 스케일(0~255)이 EROOM 매뉴얼과 다르면 이 클래스를 그대로 못 쓰고, 동일 구조로 `EROOMLight.cs` 신규 작성. + +### 8.5 ④ 수동 제어 UI — `AuroraDevices/AuroraLight/View/UCLightViewModel.cs` (+ `UCLight.xaml`) + +- 생성자(L67): `aurora.DeviceManager[deviceId] as LightDevice` 로 디바이스 획득 +- ERI일 때 채널/슬라이더 세팅(L76~84): `nChannel=4, nSliderMax=255` +- 슬라이더 → 밝기: `nSliderValue` setter(L21~24) → `_light.SetLightValueInternal(cbChannel, value)` +- 버튼 커맨드: + - `CMD_LightOnOff` → `LightOnOff()` (L111): 채널 토글 On/Off + - `CMD_ALLLightOn` → `ALLLightOn()` (L130): 전 채널 ON + - `CMD_ALLLightOff` → `ALLLightOff()` (L140): 전 채널 OFF +- **참고:** 채널수/스케일이 코드에 하드코딩(`nChannel=4`, `nSliderMax=255`)되어 있음 → EROOM(0~100%) 적용 시 여기 함께 손봐야 함. + +### 8.6 ⑤ 자동운전 사용처 — `Machines/ILSOS_PrismaticRD_PTOP/Sequences/SeqAutoRunPrismaticScanner.cs` + +- L138: `private LightDevice _Light;` +- L193: `private List lightValues = new List { 255, 255, 255, 255 };` +- L1132: `_Light = context.GetLight();` +- L353~359 / L532~537: 자동운전 진입 시 점등 +```csharp +if (_SequenceSetting.UseCellOffset) UpdateAutoLightValue(_SequenceSetting.CompensationLightValue); +else UpdateAutoLightValue(_SequenceSetting.LightValue); +if (!_Light.CheckLightOnAndValue(lightValues[0])) { // 현재 밝기와 다르면 + _Light.SetLightValue(lightValues); // 4채널 점등 + Log.I($"Light All On !"); +} +``` +- L1357: `UpdateAutoLightValue(int value)` — `lightValues` 갱신 헬퍼 + +### 8.7 ⑥ 디바이스 접근 헬퍼 — `WeldingComponents/WeldingCommon/Sequences/SequenceContextEx.cs` + +```csharp +public static LightDevice GetLight(this SequenceContext context) // L54 + => context.Devices[CommonDeviceIds.Light] as LightDevice; // L55 +``` + +### 8.8 인터페이스 / 설정 정의 + +- `AuroraDevices/AuroraLight/ILight.cs` — `ILight` 공통 인터페이스 + `IERILight`(L30, `IsOn`/`GetLightOn`/`GetLightValue` 추가). → EROOM용 `IEROOMLight` 추가 지점. +- `AuroraDevices/AuroraLight/LightConfig.cs` — `SerialConnConfig`(PortNum/BaudRate/Parity/StopBits/DataBits/ReadTimeout) 정의. + +--- + +> ⚠️ 본 문서는 **분석/가이드 전용**이며 코드는 수정하지 않았다. 실제 수정 전 EROOM 컨트롤러 매뉴얼(RS232 명령어/Baud/밝기 스케일)로 사양을 확정할 것. diff --git a/docs/작업일지/2026_06_17.md b/docs/작업일지/2026_06_17.md new file mode 100644 index 0000000..85fa169 --- /dev/null +++ b/docs/작업일지/2026_06_17.md @@ -0,0 +1,55 @@ +# 작업일지 — 2026-06-17 + +## 현장 RTC5보드 갈보모터 피드백 관련 +- 현장 RTC5보드는 갈보모터 X, Y의 피드백값을 가져오는 게 없어서 DEVICE 상에서 COMMAND 값을 불러오도록 체크 해제 후 진행. +- 정확한 피드백값은 확인 불가한 상태. + +## Laser 동작 방식 +- Laser 동작은 I/O로 하며, 프로그램 내에서 사용할 때 Simulation으로 동작한다. + +## Recipe View Laser Ready (DI 9) 미점등 이슈 +- Recipe View에서 Laser Ready 부분이 DI(9)를 보고 있는데, 해당 BIT가 Laser를 IO On 해도 번들을 켜도 안 켜져서 전선 다시 확인 중인 상태. +- _simulation이어서 true가 되긴 해야 하지만, 한번 확인해볼 것._ +- 이전 MOTF 작업하던 프로젝트랑 비교하기. + +### [분석 요청] IsLaserReady 조건 — 현재 프로젝트 vs docs/기존파일비교 +- 대상: `WeldingComponents/WeldingCommon/Views/Recipes/UCScannerObjectTeachingControlBoxViewModel.cs` +- 요청: 현재 파일의 `IsLaserReady` 조건과 `docs/기존파일비교/`의 같은 파일명 버전(이전 MOTF) 비교 + +### [분석 완료] 결론: IsLaserReady 판정 소스가 바뀜 → 미점등의 직접 원인 +- **OnMonitoring()의 IsLaserReady 판정 (핵심 차이)** + - 기존(MOTF): `if (_Laser.GetLaserReady())` → Laser 디바이스 상태로 판정. Simulation이면 true가 됨. + - 현재: `if (_IODevice.ReadDI(9))` (try/catch 포함) → **실제 IO 하드웨어 DI 9번 비트**를 직접 읽음. + - → 현재는 Laser 디바이스(시뮬레이션)가 아니라 물리 DI(9)를 보므로, 배선으로 DI(9)가 실제로 들어오지 않으면 simulation이어도 true가 안 됨. 작업일지에 적은 "simulation이면 true여야 한다"는 기존 코드 기준이고, 현재 코드는 그렇게 동작하지 않음. +- **PerformCmdLaserReadyCheck() (Laser Ready Check 버튼)** + - 기존: `_Laser.GetLaserReady()` 토글 → `_Laser.LaserOn()` / `LaserOff()` (실질적으로 Laser Ready On). + - 현재: `toggleIO` 플래그로 `_IODevice._iOUnit.WriteDoBit(9, true)` + `WriteDoBit(13, true)` 직접 출력 (주석: "추후 Write bit 파라미터화 해야함"). + - → 버튼이 켜는 것은 **DO 9/13번 출력**인데, 모니터링이 읽는 것은 **DI 9번 입력**. DO9 출력이 DI9 입력으로 되돌아오려면 물리 배선(루프백/외부 결선)이 필요함. 이 결선이 없으면 버튼을 눌러도 IsLaserReady가 안 켜짐 → 지금 전선 확인 중인 이슈와 일치. +- **부수 차이 (동작엔 직접 영향 적음)** + - 필드 선언: 기존 `private readonly IODevice _IODevice;` → 현재 `private IODevice _IODevice;` (readonly 제거). 추가 필드 `public bool toggleIO = true;`. + - using 추가: `WeldingCommon`, `WeldingCommon.Devices.Laser`, `using static ...PAIX_PrismaticScanner`. + - 기존 OnMonitoring의 LaserOnLamp WriteDoBit 주석 블록은 현재 제거됨. + - PerformCmdRunTeachingRun: 기존은 `PopupSequenceDiagram(...)` 호출, 현재는 주석 처리. OnCompleteTeachingRun도 기존은 성공 처리/ShowWarningToast 활성, 현재는 주석 처리. +- **확인/해결 방향** + - DI(9)로 판정을 유지하려면 DO9/13 → DI9 물리 결선(또는 레이저 Ready 출력 → DI9 결선)을 확인할 것. + - 시뮬레이션에서 동작 확인이 목적이면, 기존처럼 `_Laser.GetLaserReady()` 기반으로 임시 전환하거나, IO 시뮬레이션에서 DI(9)를 강제 On 할 수 있는지 확인. + +## Axis Position Interlock 파라미터 추가 +- 수평, 수직 + 허용오차 (도) 파라미터 세팅값 입력하는 것 추가함. (현장 PC 업데이트 전) + +### [TODO / 추후 작업 예정] +- 추후 시퀀스 작업할 때 해당 파라미터(수평·수직 ± 허용오차)를 체크해서, 안전을 위해서라도 인터락을 걸어둘 예정. +- 즉, 시퀀스 동작 중 전축 위치(섹터 각도)가 `기준값 ± 허용오차` 범위를 벗어나면 인터락. +- 지금은 파라미터 입력 UI만 추가한 상태이고, 인터락 판정 로직은 나중에 작업. +- 참고: 설계/위치 분석은 `docs/분석/2026/06/17/144442_sequencesetting_axis_interlock_properties.md` 에 정리해둠. + +## PLC 맵 작업 (260617 Laser Interface 반영) +- PLC에서 새 맵 받아서, `D:\ILSOSConfig\ILSOS_PrismaticRD_PTOP\External\PLC` 의 기존 `PLC_DeviceMap_*` bit/word 파일들을 `260617_backup` 으로 백업해둠. +- `유니셀 PtoP 단동기 Laser Interface_260617.xlsx` 참고해서 4개 맵 CSV(Bit/Word × Input/Output) 내부 매핑 갱신. 파일명·주소행 범위는 유지하고 코드 스타일(PC_/PLC_) 네이밍, 음/양극 둘 다 채움. +- Input/Output 방향 검증함 → 안 바뀜(정상). Word_Input 70585~70599 `M` 오기 → `D` 정정. +- 소스 enum(base + machine) 정합성은 **아직 안 건드림(보류)**. PLC측 확정 필요한 것들 있음. + +### [TODO / 추후 확인] +- StepNo(D70502 → RecipeNo로 바뀜), RMS_USED(M70509), 빠진 ALARM_RESET/RMS_READ_REQ/WELD_NG_JUDGE 처리, read count(배수)가 신규 최대 offset 포함하는지 확인. +- 체크리스트: `docs/분석/2026/06/17/171642_plc_map_update_checklist.md` +- 상세 분석: `docs/분석/2026/06/17/165341_plc_devicemap_update_260617.md`, `171312_plc_map_io_direction_and_enum_alignment.md` diff --git a/docs/작업일지/2026_06_18.md b/docs/작업일지/2026_06_18.md new file mode 100644 index 0000000..264568e --- /dev/null +++ b/docs/작업일지/2026_06_18.md @@ -0,0 +1,37 @@ +# 작업일지 — 2026-06-18 + +## 09:22 + +### 조명 컨트롤러 연결 완료 (아침) + +- 조명 컨트롤러 연결을 완료했다. + +**중요 사항 정리:** + +- 조명 컨트롤러는 모델의 사양(스펙) 파라미터를 알고 있어야 한다. +- 사용 중인 모델은 EROOM E-MINI CONTROLLER 인데, AURORA에서 해당 모델 타입을 제공 중이라 그대로 활용했다. +- DEVICE에서 LIGHT1 과 Light 에 대한 파라미터 세팅을 동일하게 맞춰주고, 채널도 맞춰줘야 한다. +- 통신 파라미터: 정지 비트(stop bit), 패리티 비트(parity bit), 시작 비트(start bit), Baud 값, 그리고 어떤 COM 포트에 꽂혔는지만 정확히 알면 된다. +- **막혔던 부분 (해결):** 장치 관리자에서 COM1 포트 설정은 맞췄지만, 고급 설정에 들어가면 버퍼(buffer) 체크가 되어 있는 항목이 있다. 이걸 꼭 **해제**해줘야 리모트 모드로 조명 컨트롤러에 커맨드를 날릴 수 있다. + +## 09:32 + +### 현장 update 파일 정리 + +- 어제(2026-06-17) 수정한 소스 파일들을 현장 소스에 옮기기 위해 `docs/현장update파일/` 폴더를 만들어 복사해두었다. +- 포함 파일: + - `SequenceSetting.cs` — Axis Position Interlock 파라미터 4개 추가 (수직/수평 기준값·허용오차, 커밋 `9543238`) + - `UCScannerObjectTeachingControlBoxViewModel.cs` — Laser Ready 판정을 `DI(9)` → `DI(12) && DI(24)` 동시 조건으로 변경 (커밋 `9aa1b2a`) + - `README.md` — 각 파일의 원본 위치 / 변경 부분(diff) / 적용 체크리스트 정리 + +### 현장 PC 원격 데스크톱 접속 가능하게 변경 + +- 금일 현장 PC가 방화벽 때문에 원격 데스크톱이 안 됐는데, **김남오 대리님**께서 세팅하신 뒤 PC를 재부팅해서 원격 접속이 가능하도록 변경해주셨다. + +## 09:36 + +### 현장 소스 반영(옮기기) 완료 + +- `docs/현장update파일/`에 정리한 변경 내용을 현장 소스에 옮기기 완료했다. + - `SequenceSetting.cs` — Axis Position Interlock 파라미터 4개 추가 반영 + - `UCScannerObjectTeachingControlBoxViewModel.cs` — Laser Ready 판정 `DI(12) && DI(24)` 변경 반영 diff --git a/docs/작업일지/README.md b/docs/작업일지/README.md new file mode 100644 index 0000000..7f1d3a3 --- /dev/null +++ b/docs/작업일지/README.md @@ -0,0 +1,6 @@ +# 작업일지 + +`/doyeon_note` 스킬로 작성되는 일자별 작업일지를 보관하는 폴더입니다. + +- 파일명 형식: `yyyy_mm_dd.md` (예: `2026_06_17.md`) +- 같은 날 여러 번 기록하면 해당 날짜 파일에 항목이 누적됩니다. diff --git a/docs/현장update파일/README.md b/docs/현장update파일/README.md new file mode 100644 index 0000000..e3cb492 --- /dev/null +++ b/docs/현장update파일/README.md @@ -0,0 +1,74 @@ +# 현장 Update 파일 — 변경 내역 + +> 어제(2026-06-17) 수정한 소스 파일들입니다. 현장 소스에 반영(머지)할 때 아래 **변경 부분만** 적용하면 됩니다. +> 이 폴더의 `.cs` 파일은 변경이 모두 반영된 **최신 전체 파일**이며, 참고/덮어쓰기용으로 함께 넣어두었습니다. + +--- + +## 1. SequenceSetting.cs + +- **원본 위치:** `WeldingComponents/WeldingCommon/Settings/SequenceSetting.cs` +- **네임스페이스/클래스:** `WeldingCommon.Settings.SequenceSetting` +- **커밋:** `9543238` — feat: SequenceSetting에 Axis Position Interlock 파라미터 추가 +- **변경 요약:** 전축 위치 안전 인터락용 파라미터 4개 추가. 수직/수평 **기준값 + 허용오차(deg)** 단일 값 입력 속성. + 추후 시퀀스에서 전축 위치 체크 후 안전 인터락 적용 예정. + +### 변경 부분 (추가) + +`GroupRamingUse` 속성 바로 아래에 추가: + +```csharp + [DisplayName("Group Raming Use")] + public bool GroupRamingUse { get; set; } = false; ++ ++ //260617 - 수직, 수평 + 허용오차 단일 값 입력하는 곳(시퀸스 돌 때 같이 입력해야할 것.) ++ [Category("Axis Position Interlock")] ++ [DisplayName("수직 기준값 [deg]")] ++ public double VerticalSector { get; set; } = 0.0; ++ ++ [DisplayName("수직 허용오차 [deg]")] ++ public double VerticalTolerance { get; set; } = 0.0; ++ ++ [DisplayName("수평 기준값 [deg]")] ++ public double HorizontalSector { get; set; } = 0.0; ++ ++ [DisplayName("수평 허용오차 [deg]")] ++ public double HorizontalTolerance { get; set; } = 0.0; +``` + +--- + +## 2. UCScannerObjectTeachingControlBoxViewModel.cs + +- **원본 위치:** `WeldingComponents/WeldingCommon/Views/Recipes/UCScannerObjectTeachingControlBoxViewModel.cs` +- **네임스페이스:** `WeldingCommon.Views` +- **커밋:** `9aa1b2a` — fix: Laser Ready 판정을 Core·Ring Control Status(DI12 & DI24) 동시 조건으로 변경 +- **변경 요약:** `OnMonitoring`의 `IsLaserReady` 판정 조건 변경. + 기존 `DI(9)` (CoreLaser_Laser_Ready 단독) → `DI(12) && DI(24)` (CoreLaser_Control_Status **AND** RingLaser_Control_Status) 동시 조건. + 버튼이 켜는 Control(DO9/13)의 피드백을 Core·Ring 둘 다 확인하도록 변경. + +### 변경 부분 (`OnMonitoring` 메서드 내부) + +```csharp + try + { +- if (_IODevice.ReadDI(9)) ++ // 변경 후 ++ if (_IODevice.ReadDI(12) && _IODevice.ReadDI(24)) + { + IsLaserReady = true; + } + else + { + IsLaserReady = false; + } + } +``` + +--- + +## 적용 체크리스트 + +- [ ] `SequenceSetting.cs` — Axis Position Interlock 파라미터 4개 추가 반영 +- [ ] `UCScannerObjectTeachingControlBoxViewModel.cs` — Laser Ready 판정 `DI(12) && DI(24)`로 변경 반영 +- [ ] 빌드 확인 diff --git a/docs/현장update파일/SequenceSetting.cs b/docs/현장update파일/SequenceSetting.cs new file mode 100644 index 0000000..7249e81 --- /dev/null +++ b/docs/현장update파일/SequenceSetting.cs @@ -0,0 +1,85 @@ +using Aurora.Api; +using PropertyTools.DataAnnotations; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace WeldingCommon.Settings +{ + // SequenceSetting 이 변경되면, 재부팅 필요함. + public class SequenceSetting : ISettingItem + { + [Category("Monitoring Sequence")] + public bool PlcMonitor { get; set; } = true; // PLC_REMOVE + public bool AlarmMonitor { get; set; } = true; + //public bool DeviceMonitor { get; set; } = true; + //public bool MESMonitor { get; set; } = true; + + [Category("Alarm Monitoring Device")] + public bool PlcActivated { get; set; } = true; // Check 되어야 아래 항목 동작 + public bool PlcActivatedLive { get; set; } = true; + + public bool ScannerActivated { get; set; } = true; // Check 되어야 아래 항목 동작 + public bool ScannerActivatedWithIO { get; set; } = false; + + public bool LaserActivated { get; set; } = true; // Check 되어야 아래 항목 동작 + public bool LaserActivatedWithIO { get; set; } = false; + + public bool LbadActivated { get; set; } = true; // Check 되어야 아래 항목 동작 + public bool LbadActivatedWithIO { get; set; } = false; + + public bool OctActivated { get; set; } = true; // Check 되어야 아래 항목 동작 + public bool OctActivatedWithIO { get; set; } = false; + + public bool ChillerActivated { get; set; } = true; // Check 되어야 아래 항목 동작 + public bool ChillerActivatedWithIO { get; set; } = false; + + public bool LeakSensorActivatedWithIO { get; set; } = false; + + [Category("WeldingSequence")] + public bool UseCellReport { get; set; } = false; + public bool UseImageAcquire { get; set; } = false; + public int ImageAcquireDelayTime { get; set; } = 1000; + public bool UseWeldResultImage { get; set; } = false; + public bool UseCellOffset { get; set; } = false; + public bool UseSubPatternOnFailure { get; set; } = false; + public bool UseExistOffset { get; set; } = false; + public double CellOffsetLimit { get; set; } = 0.3; + public double FirstLimitScore { get; set; } = 85.0; + public double SecondLimitScore { get; set; } = 85.0; + public bool UsePageChangeMode { get; set; } = false; + public int LastPageNo { get; set; } = 1; + + [DisplayName("NG(Vision 검출 or Limit Over)인 경우 미용접")] + public bool UseNgCellSkip { get; set; } = false; + + [DisplayName("비전 보상 시 사용할 조명값")] + public int CompensationLightValue { get; set; } = 255; + [DisplayName("기본 조명값")] + public int LightValue { get; set; } = 255; + public string VisionFolderName { get; set; } = @"D:\ILSOSConfig\_vision\ModelFinder"; + public string WeldResultFolderName { get; set; } = @"D:\ILSOSConfig\_vision\WeldResult"; + + [DisplayName("B-Line Hook Marking Use")] + public bool BLineHookMarkingUse { get; set; } = false; + + [DisplayName("Group Raming Use")] + public bool GroupRamingUse { get; set; } = false; + + //260617 - 수직, 수평 + 허용오차 단일 값 입력하는 곳(시퀸스 돌 때 같이 입력해야할 것.) + [Category("Axis Position Interlock")] + [DisplayName("수직 기준값 [deg]")] + public double VerticalSector { get; set; } = 0.0; + + [DisplayName("수직 허용오차 [deg]")] + public double VerticalTolerance { get; set; } = 0.0; + + [DisplayName("수평 기준값 [deg]")] + public double HorizontalSector { get; set; } = 0.0; + + [DisplayName("수평 허용오차 [deg]")] + public double HorizontalTolerance { get; set; } = 0.0; + } +} diff --git a/docs/현장update파일/UCScannerObjectTeachingControlBoxViewModel.cs b/docs/현장update파일/UCScannerObjectTeachingControlBoxViewModel.cs new file mode 100644 index 0000000..4d69a02 --- /dev/null +++ b/docs/현장update파일/UCScannerObjectTeachingControlBoxViewModel.cs @@ -0,0 +1,489 @@ +using Aurora; +using Aurora.Api; +using Aurora.View; +using AuroraIO; +using AuroraLaser; +using AuroraLaserScanner; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Reactive.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Input; +using System.Windows.Threading; +using VisionCommon; +using WeldingCommon; +using WeldingCommon.Devices.Laser; +using WeldingCommon.Models; +using WeldingCommon.Sequences; +using WeldingCommon.Settings; +using WeldingCommon.Utils; +using static AuroraLaser.Ekspla.NL202; +using static WeldingCommon.Devices.Laser.PAIX_PrismaticScanner; + +namespace WeldingCommon.Views +{ + public class UCScannerObjectTeachingControlBoxViewModel : NotifyPropertyChangedBase + { + private readonly Logger Log = new Logger("UCScannerObjectTeachingControlBoxViewModel"); + + public ICommand CMD_VisibleChanged { get; } + + public ICommand CMD_RunScoutNormal { get; } + public ICommand CMD_StopScoutNormal { get; } + + public ICommand CMD_RunScoutFlyingVision { get; } + public ICommand CMD_StopScoutFlyingVision { get; } + + public ICommand CMD_RunTeachingRun { get; } + public ICommand CMD_StopTeachingRun { get; } + + public ICommand CMD_LaserGuideBeam { get; } + public ICommand CMD_LaserReadyCheck { get; } + public ICommand CMD_ImportObjectFromDXF { get; } + + private readonly IAuroraEnvironment _Aurora; + private SystemParameter _SystemParameter; + //private readonly EquipmentInfo _EquipmentInfo; + + private readonly Scanner _Scanner; + private readonly Laser _Laser; + private readonly LaserConfig _LaserConfig; + private IODevice _IODevice; + + + private ISequence _SeqScoutNormal; + private bool _IsSequenceRunning_ScoutNormal; + public bool IsSequenceRunning_ScoutNormal { get => _IsSequenceRunning_ScoutNormal; set => SetProperty(ref _IsSequenceRunning_ScoutNormal, value); } + + private ISequence _SeqScoutFlyingVision; + private bool _IsSequenceRunning_ScoutFlyingVision; + public bool IsSequenceRunning_ScoutFlyingVision { get => _IsSequenceRunning_ScoutFlyingVision; set => SetProperty(ref _IsSequenceRunning_ScoutFlyingVision, value); } + + private ISequence _SeqTeachingRun; + + private bool _IsSequenceRunning_TeachingRun; + public bool IsSequenceRunning_TeachingRun { get => _IsSequenceRunning_TeachingRun; set => SetProperty(ref _IsSequenceRunning_TeachingRun, value); } + + private bool _IsSelectedScoutNormal; + public bool IsSelectedScoutNormal { get => _IsSelectedScoutNormal; set => SetProperty(ref _IsSelectedScoutNormal, value); } + + private bool _IsGuidBeam; + public bool IsGuidBeam { get => _IsGuidBeam; set => SetProperty(ref _IsGuidBeam, value); } + private bool _IsLaserReady; + public bool IsLaserReady { get => _IsLaserReady; set => SetProperty(ref _IsLaserReady, value); } + + // Object Select Type + private bool _IsObjectSelectAll; + public bool IsObjectSelectAll { get => _IsObjectSelectAll; set => SetProperty(ref _IsObjectSelectAll, value); } + + private readonly DispatcherTimer _MonitorTimer; + + private SequenceDialog _SequenceDialog; + + private WinSequenceDiagram _WinSequenceDiagramScoutNormal; + private WinSequenceDiagram _WinSequenceDiagramScoutFlyingVision; + private WinSequenceDiagram _WinSequenceDiagramTeachingRun; + private WinSequenceDiagram _WinSequenceDiagramComponentTeachingRun; + public bool toggleIO = true; + public UCScannerObjectTeachingControlBoxViewModel(IAuroraEnvironment aurora) + { + _Aurora = aurora; + + _SystemParameter = aurora.SettingEntry[CommonSettingIds.SystemParameter] as SystemParameter; + + _SeqScoutNormal = _Aurora.SequenceEntry[CommonSequenceIds.SeqScoutNormal]; + _SeqScoutFlyingVision = _Aurora.SequenceEntry[CommonSequenceIds.SeqScoutFlyingVision]; + + _Scanner = _Aurora.DeviceManager[CommonDeviceIds.Scanner] as Scanner; + _Laser = _Aurora.DeviceManager[CommonDeviceIds.Laser] as Laser; + _IODevice = _Aurora.DeviceManager[CommonDeviceIds.IODevice] as IODevice; + // Trumpf 레이저인 경우에는 ComponentTeachingRun 실행 + _LaserConfig = _Aurora.DeviceManager.GetDeviceConfig(CommonDeviceIds.Laser) as LaserConfig; + _SeqTeachingRun = _Aurora.SequenceEntry[CommonSequenceIds.SeqComponentTeachingRun]; + + InitVariable(); + + CMD_VisibleChanged = new DelegateCommand(VisibleChanged); + + CMD_RunScoutNormal = new DelegateCommand(PerformCmdRunScoutNormal); + CMD_StopScoutNormal = new DelegateCommand(PerformCmdStopScoutNormal); + CMD_RunScoutFlyingVision = new DelegateCommand(PerformCmdRunScoutFlyingVision); + CMD_StopScoutFlyingVision = new DelegateCommand(PerformCmdStopScoutFlyingVision); + CMD_RunTeachingRun = new DelegateCommand(PerformCmdRunTeachingRun); + CMD_StopTeachingRun = new DelegateCommand(PerformCmdStopTeachingRun); + + CMD_LaserGuideBeam = new DelegateCommand(PerformCmdLaserGuideBeam); + CMD_LaserReadyCheck = new DelegateCommand(PerformCmdLaserReadyCheck); + CMD_ImportObjectFromDXF = new DelegateCommand(PerformCmdImportObjectFromDXF); + + _Aurora.EventObservable.AsObservable(AuroraSysEvent.SYS_SETTING_CHANGED) + .ObserveOnDispatcher() + .Subscribe(OnSettingReloaded); + + _MonitorTimer = new DispatcherTimer(); + _MonitorTimer.Tick += OnMonitoring; + _MonitorTimer.Interval = TimeSpan.FromMilliseconds(100); + + } + + private void InitVariable() + { + if (_SystemParameter.ScoutType == eScoutType.Normal) { + IsSelectedScoutNormal = true; + } else { + IsSelectedScoutNormal = false; + } + + IsSequenceRunning_ScoutNormal = false; + IsSequenceRunning_ScoutFlyingVision = false; + IsSequenceRunning_TeachingRun = false; + + IsGuidBeam = false; + IsLaserReady = false; + + IsObjectSelectAll = true; + } + + private void VisibleChanged(object obj) + { + bool visible = (bool)obj; + if (visible) { + _MonitorTimer.Start(); + } else { + _MonitorTimer.Stop(); + } + } + + private void OnSettingReloaded(object obj) + { + _SystemParameter = _Aurora.SettingEntry[CommonSettingIds.SystemParameter] as SystemParameter; + if (_SystemParameter.ScoutType == eScoutType.Normal) { + IsSelectedScoutNormal = true; + } else { + IsSelectedScoutNormal = false; + } + } + + private void OnMonitoring(object sender, EventArgs e) + { + + // TODO: Laser Device 에 구현된 내용에 따르면, 일부 Laser Device 는 구현이 되어 있지 않은 상태. 항상 false + if (_Laser.GetPilotLaserStatus()) { + IsGuidBeam = true; + } else { + IsGuidBeam = false; + } + try + { + // 변경 후 + if (_IODevice.ReadDI(12) && _IODevice.ReadDI(24)) + { + IsLaserReady = true; + } + else + { + IsLaserReady = false; + } + + } + catch (Exception ex) + { + Log.E($"[OnMonitoring] DI 읽기 실패: {ex.Message}"); + IsLaserReady = false; + } + + // TODO: PRIBAT_L1, PRIBAT_L2 는 Blow 의 상태 check 가 들어 있음. 장비 한정이므로 이를 check 하는 것을 modelItem 이나 setting 으로 빼서 진행되도록 함. + } + + private void PerformCmdRunScoutNormal(object obj) + { + var result = MessageUtil.ShowYesNoMessageDialog("Scanner Scout", "Start Scanner Scout Normal Sequence?"); + if (result == false) { + return; + } + + if (VisionRecipeManager.GetFovInfos().Count == 0) { + MessageUtil.ShowWarningToast(string.Format("Please check the Vision Recipe of the currently active Recipe.")); + return; + } + + if (_SeqScoutNormal.RunState == SequenceRunState.Running) { + MessageUtil.ShowWarningToast(string.Format("이미 시퀀스가 Start 상태입니다.", "SeqScoutNormal"), Log); + return; + } + + var scoutSeqParams = new ScoutSeqParams(VisionRecipeManager.GetFovInfos()[0], eDisplayType.Machine_B); + var param = new SeqScoutNormal_InitParam() { + IsRealTimeScout = false, + ScoutSeqParams = scoutSeqParams, + }; + + PopupSequenceDiagram(ref _WinSequenceDiagramScoutNormal, CommonSequenceIds.SeqScoutNormal, param); + + IsSequenceRunning_ScoutNormal = true; + _SeqScoutNormal.Start(param, OnCompleteScoutNormal); + + } + + private void OnCompleteScoutNormal(SequenceActionResult res) + { + if (res != null) { + if (res.Param is SeqScoutNormal_OutParam result && result.Success == true) { + IsSequenceRunning_ScoutNormal = false; + MessageUtil.ShowSuccessToast(string.Format("Success SeqScoutNormal")); + + CloseSequenceDiagram(_WinSequenceDiagramScoutNormal); + return; + } + } + + if (_Laser.GetLaserReady()) { + _Laser.LaserOff(); + } + + IsSequenceRunning_ScoutNormal = false; + MessageUtil.ShowWarningToast(string.Format("Fail SeqScoutNormal")); + } + + private void PerformCmdStopScoutNormal(object obj) + { + if (_SeqScoutNormal.RunState == SequenceRunState.Idle) { + return; + } + + IsSequenceRunning_ScoutNormal = false; + _SeqScoutNormal.Stop(); + } + + private void PerformCmdRunScoutFlyingVision(object obj) + { + var result = MessageUtil.ShowYesNoMessageDialog("Flying Vision", "Start Scanner Scout Flying Vision Sequence?"); + if (result == false) { + return; + } + + if (VisionRecipeManager.GetFovInfos().Count == 0) { + MessageUtil.ShowWarningToast(string.Format("Please check the Vision Recipe of the currently active Recipe.")); + return; + } + + if (_SeqScoutFlyingVision.RunState == SequenceRunState.Running) { + MessageUtil.ShowWarningToast(string.Format("이미 시퀀스가 Start 상태입니다.", "SeqScoutFlyingVision"), Log); + return; + } + + var scoutSeqParams = new ScoutSeqParams(VisionRecipeManager.GetFovInfos()[0], eDisplayType.Machine_B); + var param = new SeqScoutFlyingVision_InitParam() { + IsRealTimeScout = false, + ScoutSeqParams = scoutSeqParams, + SequenceAutoRunCount = 0, + WeldingScannerListNo = eListNo.LIST2, + }; + + PopupSequenceDiagram(ref _WinSequenceDiagramScoutFlyingVision, CommonSequenceIds.SeqScoutFlyingVision, param); + + IsSequenceRunning_ScoutFlyingVision = true; + _SeqScoutFlyingVision.Start(param, OnCompleteScoutFlyingVision); + + } + + private void OnCompleteScoutFlyingVision(SequenceActionResult res) + { + if (res != null) { + if (res.Param is SeqScoutFlyingVision_OutParam result && result.Success == true) { + IsSequenceRunning_ScoutFlyingVision = false; + MessageUtil.ShowSuccessToast(string.Format("Success SeqScoutFlyingVision")); + + CloseSequenceDiagram(_WinSequenceDiagramScoutFlyingVision); + return; + } + } + + if (_Laser.GetLaserReady()) { + _Laser.LaserOff(); + } + + IsSequenceRunning_ScoutFlyingVision = false; + MessageUtil.ShowWarningToast(string.Format("Fail SeqScoutFlyingVision")); + } + + private void PerformCmdStopScoutFlyingVision(object obj) + { + if (_SeqScoutFlyingVision.RunState == SequenceRunState.Idle) { + return; + } + + IsSequenceRunning_ScoutFlyingVision = false; + _SeqScoutFlyingVision.Stop(); + } + + private void PerformCmdRunTeachingRun(object obj) + { + var result = MessageUtil.ShowYesNoMessageDialog("Teaching Run", "Start TeachingRun Sequence?"); + if (result == false) { + return; + } + + if (IsLaserReady == false) { + MessageUtil.ShowWarningToast(string.Format("Please check Laser Ready.")); + return; + } + + // TODO: PRIBAT_L1, PRIBAT_L2 에서 Suction 과 CLAMP 상태 등에 따라 Laser 가 나가지 않도록 해야 함. + // TODO: 이 부분은, Delegate 를 사용해서, Interlock 을 각 장비별로 설정할 수 있도록 해주는게 좋겠음. + + if (_SeqTeachingRun.RunState == SequenceRunState.Running) { + MessageUtil.ShowWarningToast(string.Format("이미 시퀀스가 Start 상태입니다.", "SeqTeachingRun"), Log); + return; + } + + // TODO: NeedSetLaserTiming 는 트럼프 레이저 를 사용할 때, UploadWeldingList Sequence 에서 LaserTiming Set 을 해야 할때 사용함. + // TODO: SMW 와 LHS 는 트럼프 레이저를 사용하지 않으므로 false, PRIBAT_L1 과 PRIBAT_L2 할때 LaserType 을 읽어서 설정하는 것으로 추가하자 + + var currentModelInfo = EquipmentSpecificModelUtil.GetCurrentRecipeEquipmentSpecificModel(); + if (currentModelInfo.equipmentSpecificModel == null) { + MessageUtil.ShowWarningToast(string.Format("선택된 model 이 없습니다.", "SeqTeachingRun")); + return; + } + + var intervalTimeBlowOn = 0; + var intervalTimeBlowOff = 0; + + if (currentModelInfo.equipmentSpecificModel.UseBlow) { + intervalTimeBlowOn = currentModelInfo.equipmentSpecificModel.IntervalTimeBlowOn; + intervalTimeBlowOff = currentModelInfo.equipmentSpecificModel.IntervalTimeBlowOff; + } + + var param = new SeqTeachingRun_InitParam() { + IsObjectSelectAll = IsObjectSelectAll, + + //UseStream = _EquipmentInfo.UseStream, + UseStream = currentModelInfo.equipmentSpecificModel.UseStream, + + UseBlow = currentModelInfo.equipmentSpecificModel.UseBlow, + IntervalTimeBlowOn = intervalTimeBlowOn, + IntervalTimeBlowOff = intervalTimeBlowOff, + + NeedSetLaserTiming = false, + }; + + //PopupSequenceDiagram(ref _WinSequenceDiagramTeachingRun, CommonSequenceIds.SeqComponentTeachingRun, param); + + IsSequenceRunning_TeachingRun = true; + _SeqTeachingRun.Start(param, OnCompleteTeachingRun); + + } + + private void OnCompleteTeachingRun(SequenceActionResult res) + { + + //if (res != null) { + // if (res.Param is SeqTeachingRun_OutParam result && result.Success == true) { + // IsSequenceRunning_TeachingRun = false; + // MessageUtil.ShowSuccessToast(string.Format("Success SeqTeachingRun")); + + // CloseSequenceDiagram(_WinSequenceDiagramTeachingRun); + // return; + // } + //} + + if (_Laser.GetLaserReady()) { + _Laser.LaserOff(); + } + + IsSequenceRunning_TeachingRun = false; + //MessageUtil.ShowWarningToast(string.Format("Fail SeqTeachingRun")); + } + + private void PerformCmdStopTeachingRun(object obj) + { + IsSequenceRunning_TeachingRun = false; + if (_SeqTeachingRun.RunState == SequenceRunState.Idle) { + return; + } + + _SeqTeachingRun.Stop(); + } + + + private void PopupSequenceDiagram(ref WinSequenceDiagram winSequenceDiagram, string seqId, object param) + { + if (winSequenceDiagram == null) { + winSequenceDiagram = new WinSequenceDiagram(seqId); + winSequenceDiagram.Closing += ClosingEvent; + winSequenceDiagram.SetParam(new SeqDrawerParam() { + SequenceId = seqId, + DiagramType = eDiagramType.Tree, + LineType = eLineType.Straight, + IsShowSubSequence = false, + VisibleKeys = new List() + }); + + winSequenceDiagram.Owner = Application.Current.MainWindow; + winSequenceDiagram.Width = 600; + winSequenceDiagram.SequenceParam = param; + winSequenceDiagram.Show(); + } else { + winSequenceDiagram.SequenceParam = param; + winSequenceDiagram.WindowState = WindowState.Normal; + winSequenceDiagram.Show(); + } + } + private void CloseSequenceDiagram(WinSequenceDiagram winSequenceDiagram) + { + Application.Current.Dispatcher.Invoke(() => { + if (winSequenceDiagram != null) { + winSequenceDiagram.Close(); + } + }); + } + private void ClosingEvent(object sender, CancelEventArgs e) + { + e.Cancel = true; + (sender as Window).Hide(); + } + + private void PerformCmdLaserGuideBeam(object obj) + { + if (!_Laser.GetPilotLaserSetBitStatus()) { + _Laser.PilotLaserOn(); + } else { + _Laser.PilotLaserOff(); + } + } + + private void PerformCmdLaserReadyCheck(object obj) + { + // 추후 Write bit 파라미터화 해야함 + if(toggleIO) + { + _IODevice._iOUnit.WriteDoBit(9, true); + _IODevice._iOUnit.WriteDoBit(13, true); + toggleIO = false; + } + else + { + _IODevice._iOUnit.WriteDoBit(9, false); + _IODevice._iOUnit.WriteDoBit(13, false); + toggleIO = true; + } + } + + private void PerformCmdImportObjectFromDXF(object obj) + { + System.Windows.Forms.FileDialog dlg = new System.Windows.Forms.OpenFileDialog(); + dlg.InitialDirectory = @"D:\share\jungwoo.jin\모바일글라스도면 외 (3) 파일_20241028"; + dlg.DefaultExt = "dxf"; + dlg.Filter = "DXF 도면 파일 (*.dxf)|*.dxf"; + dlg.Title = "DXF 도면 열기"; + + if (dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK) { + CommonHelper.GetMainPage().DxfParser(dlg.FileName); + } + } + } +}