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 0000000..709c1a7 Binary files /dev/null and b/Doc/LGPRI문서/유니셀 PtoP 단동기 Laser Interface_260617.xlsx differ 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 0000000..709c1a7 Binary files /dev/null and b/docs/PtoP 유니셀 용접기 PLC 인터페이스_260617/유니셀 PtoP 단동기 Laser Interface_260617.xlsx differ 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); + } + } + } +}