docs: 작업일지·현장update파일·분석 문서 및 아키텍처 정리

- docs/작업일지: 06-17, 06-18 작업일지 추가 (조명 컨트롤러 연결, 현장 PC 원격 접속, 현장 소스 반영)
- docs/현장update파일: 현장 반영용 변경 파일(SequenceSetting.cs, UCScannerObjectTeachingControlBoxViewModel.cs) 및 README 정리
- docs/분석: 260617 분석 문서(Laser Ready DI 변경, Axis Position Interlock, PLC DeviceMap, EROOM Light RS232 등) 추가
- docs/architecture.md, CLAUDE.md, harness-ptop.md 갱신
- PLC 인터페이스 엑셀 및 기존파일비교 자료 추가

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
노도연 2026-06-18 09:38:33 +09:00
parent 9aa1b2aa13
commit 70739252bb
21 changed files with 2591 additions and 38 deletions

View File

@ -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 분석 하네스(팀 구성, 트리거, 구성 요소, 변경 이력)

230
docs/architecture.md Normal file
View File

@ -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` (분석 결과 저장)

View File

@ -39,4 +39,4 @@ ILSOS_PrismaticRD_PTOP(PtoP)의 3계층 아키텍처, 구현 상세, 빌드 최
| 날짜 | 변경 내용 | 대상 | 사유 |
|------|----------|------|------|
| 2026-06-16 | 초기 구성 | agents/, skills/ | PTOP이 핵심 활성 모듈 |
| 2026-06-16 | 초기 구성: PTOP 분석 팀 구축 | agents/, skills/ | ILSOS_PrismaticRD_PTOP이 핵심 활성 모듈, 종합 분석 필요 |

View File

@ -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<string>()
});
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);
}
}
}
}

View File

@ -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 배선과 맞는지 확인 후, 맞지 않으면 버튼 동작을 의미에 맞게 재정의 필요.

View File

@ -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" 그룹 추가도 가능(확장성은 낮음).
> 상태: 위치/구조 추천만 정리. 실제 코드 추가는 미적용.

View File

@ -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`)에 기준값±허용오차 판정 + 알람/인터록 처리
- [ ] 설정 변경 후 재부팅 안내(설계상 제약)
> 상태: 설계/위치 분석만. 실제 코드 추가는 미적용.

View File

@ -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
> 상태: 분석/수정 방안만 기록. 실제 코드 수정은 미적용.

View File

@ -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).

View File

@ -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절)은 미확인 — 추후 작업.

View File

@ -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) 의 코드 영향 반영.

View File

@ -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 확정 후** 수정 예정. 지금은 보류.

View File

@ -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<int> 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<int> lightValues = new List<int> { 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/밝기 스케일)로 사양을 확정할 것.

View File

@ -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`

View File

@ -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)` 변경 반영

View File

@ -0,0 +1,6 @@
# 작업일지
`/doyeon_note` 스킬로 작성되는 일자별 작업일지를 보관하는 폴더입니다.
- 파일명 형식: `yyyy_mm_dd.md` (예: `2026_06_17.md`)
- 같은 날 여러 번 기록하면 해당 날짜 파일에 항목이 누적됩니다.

View File

@ -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)`로 변경 반영
- [ ] 빌드 확인

View File

@ -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;
}
}

View File

@ -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<string>()
});
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);
}
}
}
}