docs: 2026-07 분석 문서 4건 및 07/23 작업일지 추가
- 07/03 케이블베어 트러블 분석
This commit is contained in:
parent
b3cb159491
commit
5e0d083166
85
docs/분석/2026/07/03/185609_레시피별_인터락_점검_분석.md
Normal file
85
docs/분석/2026/07/03/185609_레시피별_인터락_점검_분석.md
Normal file
@ -0,0 +1,85 @@
|
||||
# 레시피별 인터락 점검 분석 (2026-07-03)
|
||||
|
||||
> 소스 미수정. 2개 독립 subagent(교차검증) + 직접 코드 확인(Read/Grep)으로 사실만 정리.
|
||||
> 범위: `Machines/ILSOS_PrismaticRD_PTOP/Sequences/`, `Alarm/`, `Device/PLC/`, `Recipes/`
|
||||
|
||||
## 0. 결론 요약 (심각도순)
|
||||
|
||||
1. **메인 AutoRun 시퀀스(SeqAutoRunPTOP)의 인터락 체크 진입점이 통째로 죽은 코드(dead code)** — 현재 실제 운용 중인 자동 사이클에서 `CheckCondition_Interlock()` 호출 자체가 발생하지 않음.
|
||||
2. 다른 시퀀스(ComponentWeld/Prismatic/TestRun/WaitWeldOrChangePage)는 인터락 함수가 **호출은 되지만 항상 `true`를 반환하는 빈 스텁**.
|
||||
3. 수동 티칭 시퀀스(SeqComponentTeachingRun)는 H알람(Heavy) 체크만 살아있고 **Door/EMO 인터락은 주석 처리**됨.
|
||||
4. 실질적으로 살아있는 안전 인터락은 **레이저 Head Theta 각도 인터락 1개뿐**(`ExecuteWelding`, fail-closed).
|
||||
5. **Axis Position Interlock(전축 위치 인터락)은 코드상 전혀 미구현** — 기존 메모 기록과 일치, 여전히 미착수.
|
||||
6. 가접(Tack) 레시피 번호(20/21/22/23 vs 21/22/23/24)는 **코드에 고정 상수로 존재하지 않음** — 런타임에 PLC가 보내는 번호 그대로 매칭하는 구조라 코드로는 확정 불가. 레시피별로 분기되는 인터락 로직 자체도 없음(인터락이 레시피 번호와 무관하게 전역적으로 스텁).
|
||||
|
||||
---
|
||||
|
||||
## 1. SeqAutoRunPTOP.cs — 인터락 진입점 자체가 미호출 (Dead Code)
|
||||
|
||||
- `CheckInitCondition`(322-337행)에서 `CheckCondition(context)` 호출이 **통째로 주석 처리**(327-333행). 대신 `Thread.Sleep(5)` 후 바로 `SEQ_CHECK_PAGENO`로 분기(336행).
|
||||
- `CheckCondition`(1100-1114행)은 `CheckCondition_Interlock()`(1104행) + `InitializeDeviceStatus()`(1106행)를 감싸는 함수지만, 위 주석 처리로 인해 **어디서도 호출되지 않음**.
|
||||
- `CheckCondition_Interlock()`(1116-1121행) 자체도 `Log.I("...현재 항상 통과")` 후 `return true` 뿐인 스텁(1119-1120행).
|
||||
- **의미**: 메인 자동운전 사이클 진입 시 인터락 판정뿐 아니라 `InitializeDeviceStatus`(디바이스 초기상태 확인)까지 같이 스킵됨. 스텁이 항상 true를 반환하는 것과, 애초에 호출 자체가 안 되는 것은 결과는 같아도 원인이 다름 — 후자는 "누군가 임시로 주석 처리한 뒤 복구를 잊었을 가능성"을 시사.
|
||||
|
||||
## 2. 다른 시퀀스 — 인터락 함수는 호출되나 항상 true (스텁)
|
||||
|
||||
| 파일 | CheckCondition 호출 | CheckCondition_Interlock 내용 |
|
||||
|---|---|---|
|
||||
| `SeqAutoRunComponentWeld.cs` | 251행 호출(주석 없음, 실사용) | 590-595행, 주석만 있고 `return true` |
|
||||
| `SeqAutoRunPrismatic.cs` | 342행 호출(실사용) | 1158-1163행, `return true` |
|
||||
| `SeqTestRun.cs` | 489행 호출(실사용) | 507행~, `return true` |
|
||||
| `SeqWaitWeldOrChangePage.cs` | 397-399행 호출(실사용) | 413행~, `return true` |
|
||||
|
||||
이 4개 시퀀스는 SeqAutoRunPTOP과 달리 함수 호출 자체는 살아있으나, 함수 내부가 애초에 아무 조건도 검사하지 않는 빈 스텁이라 결과적으로 동일하게 "인터락 없음" 상태.
|
||||
|
||||
## 3. SeqComponentTeachingRun.cs — 부분적으로만 살아있음
|
||||
|
||||
`CheckCondition_Interlock()`(271-296행):
|
||||
- `AlarmManager.HasAlarmLevelHigh()`(274행) — **살아있음**, High 알람 시 차단.
|
||||
- `IsPlcDoorOpen()`(282-287행), `IsPlcEmoAlarm()`(289-294행) — **전부 주석 처리**. 281행에 `#warning to KSJ > 지워도 무방해보임` 메모가 남아있어, 의도적으로 비활성화한 것으로 보이나 최종 확정 여부는 불명.
|
||||
- 최종 `return true`(295행) — Door/EMO 체크가 없으므로 사실상 Door 열림/EMO 눌림 상태에서도 티칭 시퀀스 조건 통과 가능.
|
||||
|
||||
## 4. 실질적으로 동작하는 유일한 인터락 — Head Theta(레이저 각도)
|
||||
|
||||
`SeqAutoRunPTOP.cs` `ExecuteWelding`(744-792행 부근):
|
||||
- `LaserInterlockSetting.HeadThetaInterlockEnabled`(753행) 체크박스가 켜져 있을 때만 동작.
|
||||
- PLC 미연결/미활성 시 **fail-closed**(756-760행, `blocked = true`).
|
||||
- `HEAD_THETA_POSITION`(D70510) 워드를 읽어 수평/수직 기준각 ± 허용오차 범위 밖이면 `ALARM_LASER_HEAD_THETA_INTERLOCK` 발생 + 발진 차단(786-791행).
|
||||
- 레시피 번호와 무관하게 전역적으로 매 용접마다 적용됨(레시피별 분기 없음).
|
||||
|
||||
## 5. Axis Position Interlock(전축 위치) — 미구현 재확인
|
||||
|
||||
`AxisPosition`/`전축`/`축위치`/`SectorAngle` 키워드로 `Machines/ILSOS_PrismaticRD_PTOP` 전체 grep → 실 소스 매치 0건(라이브러리 XML 문서 파일만 우연 매치). 기존 메모([[axis-position-interlock-todo]]) 그대로, 판정 로직 여전히 미착수.
|
||||
|
||||
## 6. 가접(Tack) 레시피 번호 & 레시피별 인터락 분기
|
||||
|
||||
- 코드 내 Tack/U-Frame 전용 레시피 번호 상수 **없음**. `CheckRecipeNoComp`(SeqAutoRunPTOP.cs 346-433행)는 PLC가 보낸 `PLC_RECIPE_NO`를 `_RecipeManager.Recipes()`에서 `.Id` 매칭으로만 처리 — 번호 자체는 런타임/설정 데이터에 있고 코드에 하드코딩되어 있지 않음.
|
||||
- 과거 기록상 20/21/22/23([[recipe-page-handshake-redesign]])과 21/22/23/24, 20 폐지([[bit15-resume-feature]])가 서로 다르게 남아있는데, **코드로는 어느 쪽도 검증 불가** — 레시피 데이터(엑셀/현장 등록값) 확인 필요.
|
||||
- 레시피 번호별로 다르게 적용되는 인터락 로직도 코드상 확인되지 않음 — 애초에 인터락 판정 자체가 4장 항목 외엔 전역 스텁이라, "레시피 A는 인터락 있고 B는 없다"는 분기 자체가 존재하지 않음(있으면 안 되는데 지금은 다 스텁이라 무의미).
|
||||
|
||||
## 7. bit15(Weld_01 Ready) Resume — 인터락과의 상호작용
|
||||
|
||||
- `PC_WELD_READY_ACK`를 false로 되돌리는 곳은 `CheckCycleStop`(872-878행) 한 곳, `ResumeMode`가 true일 때만.
|
||||
- Resume 트리거·팝업·재개 로직에 레시피 번호(21/22 등) 조건 결합 없음 — 전 레시피 공통 동작.
|
||||
- Resume 경로가 인터락 체크를 "스킵"하는지 여부는 애초에 1장에서 확인했듯 SeqAutoRunPTOP의 인터락 진입점 자체가 미호출(dead code)이므로, "스킵" 여부를 논하는 것 자체가 무의미 — 인터락이 있었다면 스킵됐을지 확인이 필요하지만 지금은 그 이전 단계에서 이미 빠져있음.
|
||||
|
||||
## 8. PLC 인터락 관련 신호 매핑 (참고, 확실치 않음)
|
||||
|
||||
Agent 조사에 따르면 `PLCController.Address.cs`에 `PLC_DOOR_INTERLOCK`, `PLC_EMO_STATUS`, `PLC_LIGHT_ALARM_STATUS`, `PLC_HEAVY_ALARM_STATUS`, `HEAD_THETA_POSITION` 주소가 매핑되어 있음(직접 재검증은 안 함, 파일:라인 인용은 subagent 보고 기준이라 **확실하지 않음**으로 표시). 즉 PLC 신호 자체는 준비돼 있는데, 이를 실제로 읽어 판정하는 애플리케이션 로직(1~3장)이 대부분 비활성화된 상태로 보임.
|
||||
|
||||
---
|
||||
|
||||
## 교차검증 메모
|
||||
|
||||
두 subagent가 독립적으로 조사한 결과가 대부분 일치했으나, SeqAutoRunPTOP의 인터락 상태에 대해 표현이 갈렸음:
|
||||
- Agent A: "인터락 함수가 항상 true를 반환하는 스텁"
|
||||
- Agent B: "인터락 함수 자체가 호출되지 않는 dead code"
|
||||
|
||||
직접 `Read`로 322-337행, 1100-1121행을 확인한 결과 **Agent B가 정확**함 — `CheckCondition(context)` 호출이 주석 처리되어 있어 `CheckCondition_Interlock()`은 SeqAutoRunPTOP에서 실행 경로상 도달하지 않음. (다른 4개 시퀀스는 Agent A 설명대로 호출은 되고 내부가 스텁.)
|
||||
|
||||
## 후속 확인 필요 (제안, 미실행)
|
||||
|
||||
- SeqAutoRunPTOP의 327-333행 주석 처리가 의도적 임시 조치인지, 복구를 잊은 것인지 — 커밋 이력/작업지시서 대조 필요.
|
||||
- SeqComponentTeachingRun의 Door/EMO 인터락 주석 처리(`#warning to KSJ`)가 최종 승인된 사양인지 확인 필요.
|
||||
- 가접 레시피 실제 번호(20~23 vs 21~24)는 코드가 아닌 현장 레시피 데이터/엑셀 기준으로 재확인 필요.
|
||||
- 위 모든 항목은 **분석 전용이며 수정 요청 없이는 손대지 않음**(협업 규칙 #2).
|
||||
557
docs/분석/2026/07/23/Laser_Shot_IO_LaserReady_GuideBeam_분석.md
Normal file
557
docs/분석/2026/07/23/Laser_Shot_IO_LaserReady_GuideBeam_분석.md
Normal file
@ -0,0 +1,557 @@
|
||||
# Recipe Laser Shot — Laser Ready / Guide Beam IO 판정 위치 및 상시 ON 배선 대응 분석
|
||||
|
||||
**분석 일시:** 2026-07-23
|
||||
**대상:** `WeldingComponents/WeldingCommon/Views/Recipes/UCScannerObjectTeachingControlBoxViewModel.cs`
|
||||
**IO enum:** `WeldingComponents/WeldingCommon/Devices/Laser/LaserIOControl.cs` (`PAIX_PrismaticScanner`)
|
||||
**상태:** 분석/수정안만 기록. **소스 미수정**(사용자 요청).
|
||||
**관련:** [[150031_laser_ready_di12_di24_change]] · [[143131_laser_ready_di9_analysis]] · [[124752_guidebeam_trupulse_analysis]] · [[162010_io_interface_map_path_source]]
|
||||
|
||||
---
|
||||
|
||||
## 0. 요청 (사용자)
|
||||
|
||||
- 현재 프로그램 SOURCE는 수정하지 않음.
|
||||
- Recipe의 Laser Shot 구간에서 **Laser Ready 버튼 / Guide Beam 버튼**이 물린 IO 배선의 TRUE/FALSE를 읽어 동작 → **IO 몇 번인지** 확인 요청.
|
||||
- 현장 레이저가 기존과 달라 **배선을 다른 곳에 물렸고, 레이저 컨트롤러를 상시 ON** 상태로 두고 그 값을 읽어 **TRUE로 유지돼야 레시피 출사 시퀀스가 동작**하는 구조로 가야 함.
|
||||
- 해당 소스 위치 + DIFF(적용 전 검토용) + 작업내용 기록 요청.
|
||||
|
||||
---
|
||||
|
||||
## 1. 결론 — 현재 사용 중인 IO 번호
|
||||
|
||||
| 항목 | 방향/번호 | enum 의미 (`PAIX_PrismaticScanner`) | 소스 위치 |
|
||||
|---|---|---|---|
|
||||
| **Laser Ready 판정(모니터링)** | **DI 11 && DI 15** | 11 = `CoreLaser_Laser_Output_State`, 15 = `RingLaser_Laser_Output_State` | `UCScannerObjectTeachingControlBoxViewModel.cs:271` |
|
||||
| **Laser Ready 버튼(토글 출력)** | **DO 9, DO 13** | 9 = `CoreLaser_Control`, 13 = `RingLaser_Control` | 같은 파일 `:552~567` (`PerformCmdLaserReadyCheck`) |
|
||||
| **Guide Beam 상태 표시** | **DO 17 (읽기)** | 17 = `EX_Red_Control` | 같은 파일 `:270` |
|
||||
| **Guide Beam 버튼(토글)** | **DO 17 (쓰기)** | 17 = `EX_Red_Control` | 같은 파일 `:534~542` (`PerformCmdLaserGuideBeam`) |
|
||||
| **출사(TeachingRun) 게이트** | `IsLaserReady == false` → 실행 차단 | — | 같은 파일 `:415~418` (`PerformCmdRunTeachingRun`) |
|
||||
|
||||
> Guide Beam은 **DI가 없어 DO 17 명령 비트를 되읽어 표시**하는 구조(실제 빔 출력 피드백 아님).
|
||||
> 물리 가이드빔 제어의 정식 경로는 Laser(TruPulse) `ExtPilotOnOff`이며 **현재 미구현** — [[124752_guidebeam_trupulse_analysis]] 참고.
|
||||
|
||||
### 1.1 IO 번호 변천 이력 (Laser Ready 판정)
|
||||
|
||||
```
|
||||
DI 9 (CoreLaser_Laser_Ready 단독) — 초기
|
||||
→ DI 12 && DI 24 (Core/Ring Control_Status) — 260617 결선 기준
|
||||
→ DI 11 && DI 15 (Core/Ring Laser_Output_State) — 260618 결선 변경, 현재 소스 상태
|
||||
```
|
||||
|
||||
### 1.2 enum 정의 (`LaserIOControl.cs:26~44`)
|
||||
|
||||
```csharp
|
||||
public enum ePrismaticDIAddress {
|
||||
CoreLaser_Laser_Ready = 9,
|
||||
CoreLaser_Error_State, // 10
|
||||
CoreLaser_Laser_Output_State, // 11 ← 현재 판정에 사용
|
||||
CoreLaser_Control_Status, // 12
|
||||
RingLaser_Laser_Ready, // 13
|
||||
RingLaser_Error_State, // 14
|
||||
RingLaser_Laser_Output_State, // 15 ← 현재 판정에 사용
|
||||
RingLaser_Control_Status = 23, // 현장 실제 24 (enum outdated, 미정정)
|
||||
}
|
||||
public enum ePrismaticDOAddress {
|
||||
CoreLaser_Control = 9,
|
||||
CoreLaser_ErrorClearing, // 10
|
||||
RingLaser_Control = 13,
|
||||
RingLaser_ErrorClearing, // 14
|
||||
EX_Red_Control = 17, // Guide Beam 토글에 사용
|
||||
}
|
||||
```
|
||||
|
||||
> **주의:** DI/DO 인덱스의 실제 의미는 enum이 아니라 **런타임 로드되는 `DI.csv`/`DO.csv` 맵**이 결정한다.
|
||||
> 맵 경로는 Setting → Device(IODevice) Config의 `IOInterfaceMapDI/DO`. 소스에 하드코딩 없음 → [[162010_io_interface_map_path_source]].
|
||||
> 기본 `DI.csv`에서는 12=START SWITCH, 24=FAN 8 ALARM, 25=LASER Ready 로 되어 있어 **enum과 불일치 가능**. 새 배선 번호 확정 시 CSV와 반드시 대조할 것.
|
||||
|
||||
---
|
||||
|
||||
## 2. 현재 구조가 "상시 ON 레이저"와 안 맞는 이유
|
||||
|
||||
```
|
||||
[Laser Ready 버튼] --WriteDoBit(9,13)--> DO9/DO13 (Core/Ring Control 출력)
|
||||
| ← 이전 레이저: 이 출력으로 컨트롤러를 Enable
|
||||
[모니터링 100ms] --ReadDI(11) && ReadDI(15)--> DI11/DI15 (Output State 피드백)
|
||||
[출사 버튼] --IsLaserReady == false → 차단
|
||||
```
|
||||
|
||||
- 새 레이저는 **컨트롤러가 상시 ON**이고 배선이 다른 DI로 옮겨졌으므로,
|
||||
- DO9/DO13 토글은 **더 이상 Ready 성립 조건이 아님**(누르지 않아도 Ready여야 함, 반대로 눌러도 무의미).
|
||||
- 판정은 **새로 물린 DI 비트 하나(또는 AND 조합)를 상시 읽어 TRUE 유지**하는 형태여야 함.
|
||||
- 즉 코드 변경 포인트는 **271행 판정식**(필수) + **버튼 의미 재정의**(선택) 두 곳뿐이다.
|
||||
|
||||
---
|
||||
|
||||
## 3. 수정안 (DIFF) — 미적용
|
||||
|
||||
### 3.1 전제 / 확정 필요 사항
|
||||
|
||||
- **신규 Ready DI 번호가 아직 미확정**이므로, 아래 DIFF는 번호를 **파일 상단 상수 1곳**으로 뽑아 놓았다.
|
||||
현장에서 번호만 확인되면 `DI_LASER_READY_MAIN`(및 필요 시 `DI_LASER_READY_SUB`) 값만 바꾸면 된다.
|
||||
- Core/Ring 2개를 계속 AND로 볼지, 상시 ON 신호 **1개만** 볼지도 현장 확정 필요.
|
||||
→ DIFF는 `DI_LASER_READY_SUB < 0` 이면 **단일 DI 판정**으로 자동 동작하도록 작성.
|
||||
|
||||
### 3.2 판정부 + 상수 (핵심)
|
||||
|
||||
```diff
|
||||
--- a/WeldingComponents/WeldingCommon/Views/Recipes/UCScannerObjectTeachingControlBoxViewModel.cs
|
||||
+++ b/WeldingComponents/WeldingCommon/Views/Recipes/UCScannerObjectTeachingControlBoxViewModel.cs
|
||||
@@ (필드 선언부, 예: L78 부근)
|
||||
private bool _IsLaserReady;
|
||||
public bool IsLaserReady { get => _IsLaserReady; set => SetProperty(ref _IsLaserReady, value); }
|
||||
+
|
||||
+ // ── Laser Ready 판정 DI (260723: 신규 레이저 = 컨트롤러 상시 ON, 배선 변경) ──
|
||||
+ // 이력: DI9 → DI12&&DI24(260617) → DI11&&DI15(260618) → 아래 상수(260723~)
|
||||
+ // * 현장 배선 확정 후 아래 두 상수만 수정할 것. SUB < 0 이면 MAIN 단독 판정.
|
||||
+ private const int DI_LASER_READY_MAIN = 11; // TODO: 현장 신규 배선 DI 번호로 교체
|
||||
+ private const int DI_LASER_READY_SUB = 15; // TODO: 단일 신호면 -1 로 설정
|
||||
+
|
||||
+ // 상시 ON 배선 미완/점검 시 강제 통과용 (기본 false, 절대 true 커밋 금지)
|
||||
+ private const bool FORCE_LASER_READY = false;
|
||||
|
||||
@@ OnMonitoring() L262~271
|
||||
if (_IODevice == null || !_IODevice.IsConnected())
|
||||
{
|
||||
IsGuidBeam = false;
|
||||
IsLaserReady = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
IsGuidBeam = _IODevice.ReadDO(17);
|
||||
- IsLaserReady = _IODevice.ReadDI(11) && _IODevice.ReadDI(15); // 변경 후 (260618: IO 결선 변경 12&24 → 11&15)
|
||||
+ // 260723: 신규 레이저 컨트롤러 상시 ON → Control 출력(DO9/13)과 무관하게
|
||||
+ // 상시 입력되는 Ready DI 를 그대로 읽어 판정한다.
|
||||
+ IsLaserReady = FORCE_LASER_READY
|
||||
+ || (_IODevice.ReadDI(DI_LASER_READY_MAIN)
|
||||
+ && (DI_LASER_READY_SUB < 0 || _IODevice.ReadDI(DI_LASER_READY_SUB)));
|
||||
```
|
||||
|
||||
### 3.3 Laser Ready 버튼 — 의미 재정의 (선택, 권장)
|
||||
|
||||
상시 ON이면 DO9/13 토글은 불필요하며, 잘못 누르면 오히려 Control 출력을 흔들 수 있다.
|
||||
|
||||
```diff
|
||||
@@ PerformCmdLaserReadyCheck() L552~567
|
||||
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;
|
||||
- }
|
||||
+ // 260723: 신규 레이저는 컨트롤러 상시 ON — Control 출력(DO9/13) 토글은 Ready 성립 조건이 아님.
|
||||
+ // 오조작으로 Control 출력을 흔들지 않도록 쓰기를 막고 현재 DI 상태만 알린다.
|
||||
+ // (구 레이저 복귀 시 아래 블록 주석 해제)
|
||||
+ //if (toggleIO) {
|
||||
+ // _IODevice._iOUnit.WriteDoBit((int)ePrismaticDOAddress.CoreLaser_Control, true);
|
||||
+ // _IODevice._iOUnit.WriteDoBit((int)ePrismaticDOAddress.RingLaser_Control, true);
|
||||
+ // toggleIO = false;
|
||||
+ //} else {
|
||||
+ // _IODevice._iOUnit.WriteDoBit((int)ePrismaticDOAddress.CoreLaser_Control, false);
|
||||
+ // _IODevice._iOUnit.WriteDoBit((int)ePrismaticDOAddress.RingLaser_Control, false);
|
||||
+ // toggleIO = true;
|
||||
+ //}
|
||||
+ MessageUtil.ShowWarningToast(
|
||||
+ $"Laser 상시 ON 구성: Ready = DI{DI_LASER_READY_MAIN}"
|
||||
+ + (DI_LASER_READY_SUB < 0 ? "" : $" && DI{DI_LASER_READY_SUB}")
|
||||
+ + $" → 현재 {(IsLaserReady ? "TRUE" : "FALSE")}");
|
||||
}
|
||||
```
|
||||
|
||||
### 3.4 출사 게이트 — 변경 없음
|
||||
|
||||
`PerformCmdRunTeachingRun` L415의 `if (IsLaserReady == false) { ... return; }` 는 그대로 둔다.
|
||||
3.2에서 `IsLaserReady`가 상시 TRUE로 유지되면 출사 시퀀스는 자동으로 통과한다.
|
||||
|
||||
### 3.5 (별건, 권장) enum 정정
|
||||
|
||||
```diff
|
||||
--- a/WeldingComponents/WeldingCommon/Devices/Laser/LaserIOControl.cs
|
||||
+++ b/WeldingComponents/WeldingCommon/Devices/Laser/LaserIOControl.cs
|
||||
@@
|
||||
- RingLaser_Control_Status = 23,
|
||||
+ RingLaser_Control_Status = 24, // 현장 실배선 기준(enum 23은 outdated)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 적용 체크리스트
|
||||
|
||||
- [ ] 현장 신규 배선의 **Ready DI 번호 확정** (단일인지 Core/Ring 2개 AND인지 포함)
|
||||
- [ ] 로드 중인 `DI.csv`(Setting → IODevice Config `IOInterfaceMapDI`)에서 해당 번호의 라벨 확인 — enum과 불일치 여부
|
||||
- [ ] 3.2 판정부 적용 + 상수값 교체
|
||||
- [ ] 3.3 버튼 재정의 적용 여부 결정(구 레이저 복귀 가능성 있으면 주석 보존 형태 유지)
|
||||
- [ ] Recipe 화면에서 컨트롤러 ON 상태만으로 Laser Ready Status = TRUE 표시 확인
|
||||
- [ ] TeachingRun(출사) 시퀀스가 버튼 조작 없이 진입되는지 확인
|
||||
- [ ] `FORCE_LASER_READY` 는 반드시 false 로 되돌린 뒤 커밋
|
||||
|
||||
## 5. 잔여 리스크
|
||||
|
||||
- **Guide Beam(DO 17)** 은 여전히 명령 비트 되읽기 표시 — 신규 레이저에서도 물리 빔이 안 나올 수 있음(정식 경로 `ExtPilotOnOff` 미구현). 신규 레이저 배선에 가이드빔 제어선이 별도로 있으면 DO 번호 재확인 필요.
|
||||
- DO9/DO13 쓰기를 막으면, 이 출력을 다른 인터락에서 참조 중일 경우 영향 가능 → 현장 배선도 대조 필요(현 소스 내 다른 참조처는 없음).
|
||||
- `ePrismaticDIAddress` 11/15 는 이름상 `Laser_Output_State`(출사 중 상태)라서, 원래 의미대로면 "출사 중"에만 TRUE. 신규 구성에서는 **Ready 의미의 비트**를 잡는 것이 맞다.
|
||||
|
||||
---
|
||||
|
||||
# 2차 분석 (동일일자) — 시퀀스 측 레이저(IO) 인터락
|
||||
|
||||
**요청:** "시퀀스에서도 레이저(IO) 인터락 확인하는 부분이 있을 것이다. Manual 동작은 `SeqComponentTeachingRun` 으로 시킬 것이므로 그쪽도 같이 봐야 한다. `IsLaserReady` 도 수정 대상."
|
||||
**상태:** 분석만. **소스 미수정.**
|
||||
|
||||
## 6. Manual 출사 경로에서 "레이저 Ready"를 보는 곳 — 3계층
|
||||
|
||||
| # | 위치 | 판정식 | 실제 효력 |
|
||||
|---|---|---|---|
|
||||
| ① 화면 게이트 | `UCScannerObjectTeachingControlBoxViewModel.cs:415` (`IsLaserReady == false` → 차단) | `:271` `ReadDI(11) && ReadDI(15)` | **유일하게 실동작하는 IO 인터락** |
|
||||
| ② 시퀀스 진입부 | `SeqComponentTeachingRun.cs:473` `RetryUntilSuccess(_Laser.GetLaserReady, 10s, 1)` | `_Laser = context.GetLaser()` → **Aurora Laser(트럼프) 디바이스**, IO 아님 | IO 결선과 무관. 실 레이저 미연결 시 타임아웃/오판 가능 |
|
||||
| ③ 출사 직전 | `SeqComponentTeachingRun.cs:561` → `LaserReadyResponse()` (`:658~669`) | `return true;` (본문 전부 주석 처리) | **스텁 — 인터락 없음** |
|
||||
|
||||
**결론:** 현재 시퀀스 내부에는 **IO(DI) 기반 Ready 확인이 존재하지 않는다.**
|
||||
②는 IO가 아닌 레이저 디바이스 호출이고 ③은 항상 true. 실제로 막는 것은 ①(버튼 클릭 시점 1회)뿐이며,
|
||||
시퀀스 진행 중 Ready 가 떨어져도 검출되지 않는다.
|
||||
`DoComponentWelding` 에서 실동작하는 인터락은 **Head Theta**(`:572~613`)가 유일하다.
|
||||
|
||||
## 7. "Laser Ready" 정의가 소스에 3벌, 서로 다름
|
||||
|
||||
| 정의처 | 판정 DI | enum 의미 |
|
||||
|---|---|---|
|
||||
| `UCScannerObjectTeachingControlBoxViewModel.cs:271` | DI 11 && DI 15 | `Core/RingLaser_Laser_Output_State` |
|
||||
| `LaserIOControl.cs:77~85` `GetLaserReady()` | DI 9 && DI 13 | `Core/RingLaser_Laser_Ready` |
|
||||
| `SeqComponentTeachingRun.cs:473` | (IO 미사용) | Aurora `Laser.GetLaserReady()` |
|
||||
|
||||
상시 ON 레이저로 전환 시 이 셋이 따로 놀아 **화면은 Ready 인데 시퀀스가 막히거나 그 반대** 상황이 생긴다.
|
||||
|
||||
## 8. 부수 발견
|
||||
|
||||
- **`LaserIOControl` 은 PTOP 에서 dead 코드**
|
||||
`EquipmentSetup.cs:153` 이 `CommonDeviceIds.IODevice` 를 `PAIX_PTOP` 으로 등록 →
|
||||
`SeqTestRun.cs:469` 의 `... as LaserIOControl` 는 **항상 null** → `:247 _IOLaser.GetLaserReady()` 는 NRE 경로.
|
||||
**단, `SeqTestRun` 은 현재 사용하지 않는 파일임(2026-07-23 사용자 확인).** 실동작 리스크 없음 — 정리 대상으로만 기록.
|
||||
- **Auto 경로에는 레이저 Ready 인터락 자체가 없음**
|
||||
`SeqAutoRunPTOP.cs:589 LaserReadyResponse()` 는 no-op 이며 **호출처도 없다.**
|
||||
(`SeqAutoRunPrismatic.cs:363/369` 에만 `_Laser.GetLaserReady` 가 남아 있으나 PTOP 활성 경로 아님)
|
||||
- **enum 2벌 중복** — `PAIX_PTOP.cs:19~37` 과 `LaserIOControl.cs:26~44` 에 동일 `ePrismaticDIAddress/DOAddress` 존재.
|
||||
번호 변경 시 한쪽만 고치면 어긋난다.
|
||||
- `IsLaserReady` 는 100ms 모니터링 폴링값이라 **버튼 클릭 시점 스냅샷**으로만 쓰인다(구간 감시 아님).
|
||||
|
||||
## 9. 수정 방향 (미적용) — 판정 1곳 + 호출 3곳
|
||||
|
||||
1. **공용 게이트 신설** — 예: `WeldingComponents/WeldingCommon/Devices/Laser/LaserReadyGate.cs`
|
||||
- 상수: `DI_LASER_READY_MAIN` / `DI_LASER_READY_SUB`(`< 0` 이면 MAIN 단독) / `FORCE_LASER_READY`(기본 false)
|
||||
- `public static bool IsReady(IODevice io)` — `io.IsSimul` → true, `io == null`·미연결·예외 → **fail-closed(false)**
|
||||
- WeldingCommon 에 두면 ViewModel 과 PTOP 시퀀스가 함께 참조 가능
|
||||
- ⚠ 구식 csproj → `WeldingCommon.csproj` 에 `<Compile Include="Devices\Laser\LaserReadyGate.cs" />` **수동 추가 필요**
|
||||
2. `UCScannerObjectTeachingControlBoxViewModel.cs:271` → `IsLaserReady = LaserReadyGate.IsReady(_IODevice);`
|
||||
3. `SeqComponentTeachingRun.cs:473` → `_Laser.GetLaserReady` 대신 `() => LaserReadyGate.IsReady(_IO)`
|
||||
(기존 줄은 구 레이저 복귀 대비 주석 보존)
|
||||
4. `SeqComponentTeachingRun.cs:658 LaserReadyResponse()` → `return LaserReadyGate.IsReady(_IO);`
|
||||
→ 출사 직전 재확인 부활
|
||||
5. `LaserIOControl.GetLaserReady()` 도 동일 게이트로 위임 → 정의 통일 (미사용이지만 정의 분기 제거 목적)
|
||||
6. `PerformCmdLaserReadyCheck`(DO 9/13 토글)은 상시 ON 에서 무의미 → 쓰기 차단 + 현재 DI 상태 토스트(§3.3 안)
|
||||
|
||||
### 9.1 적용 순서 / 리스크
|
||||
|
||||
- **4번은 마지막에.** 지금 "항상 통과"를 "DI 미충족 시 타임아웃 차단"으로 바꾸는 변경이라
|
||||
**Ready DI 번호 확정 전에 넣으면 현장 출사가 막힌다.**
|
||||
- 권장 순서: `DI.csv` 라벨 대조 → 번호 확정 → 상수 교체 → **1·2·3 적용** → 화면 Ready TRUE 유지 확인 → **4 적용** → 5·6.
|
||||
- **Auto 경로(SeqAutoRunPTOP)에 게이트 추가는 별도 판단.** 현재 인터락이 없으므로 넣는 순간
|
||||
양산 정지 리스크가 새로 생긴다(현장 합의 후 결정).
|
||||
|
||||
---
|
||||
|
||||
# 3차 — 아키텍처 검토 (학습용 정리)
|
||||
|
||||
**주제:** "IO 보드를 직접 읽어 레이저를 제어" vs "Aurora `Laser` 추상화 계층을 통해 제어"
|
||||
**상태:** **소스 수정 없음.** 개념 정리 + 다음 프로젝트 방향성 기록.
|
||||
**결론 한 줄:** 기능적으로는 지금 방식도 문제없다. 차이는 **유지보수성·추적성**이며, "매번 소스 수정" 문제는 계층과 **별개**로 핀 번호 외부화로 풀어야 한다.
|
||||
|
||||
## 10. 논의 배경 (2026-07-23)
|
||||
|
||||
- **차장님 의견:** 추상화 레벨이 `Laser` 클래스이므로, **디바이스 등록 시점에 IO 보드를 Laser에 함께 물리고(주입) IO 주소/비트 맵도 같이 등록해서 한 곳에서 관리**하는 게 맞았다.
|
||||
- **현재 PTOP 실상:** IO 보드는 `IODevice` 로 따로 등록하고, 화면·시퀀스가 그 **address 값을 직접 읽어 개별 제어**한다.
|
||||
(PTOP 는 처음부터 "IO 보드로 레이저 출사 제어" 구조로 출발 → DI 핀 확인해서 그때그때 소스 수정해 옴)
|
||||
- **받은 답변(정리):** "IO Device 는 IO Device 대로 쓰고, Laser Device 가 IO Device 에 **의존적으로** 동작하는 구조가 정석이다.
|
||||
다만 **이력을 모르면 따라가기 어렵다는 점**이 문제일 뿐, **사용하는 데는 지장 없다.**"
|
||||
- **방향성:** 다음부터는 등록 시점에 IO 를 Laser 에 주입하는 형태로 작업. **이번 건은 소스 수정하지 않고 나중에 집중해서 진행.**
|
||||
|
||||
## 11. Aurora 가 이미 제공하는 구조
|
||||
|
||||
### 11.1 계층
|
||||
|
||||
```
|
||||
Aurora.Api.Device
|
||||
└─ AuroraLaser.Laser ← 장비 코드가 쓰는 유일한 창구(추상화 레벨)
|
||||
└─ ILaser (인터페이스) ← Laser.cs:555
|
||||
├─ LaserTruFiber (OPC UA, 통신형)
|
||||
├─ TruPulse (통신형)
|
||||
├─ LaserIPG_IO (IO 보드형) ← IODevice 주입
|
||||
├─ LaserArm_IO (IO 보드형) ← IODevice 주입
|
||||
└─ LaserTrumpf_IO (IO 보드형) ← IODevice 주입
|
||||
```
|
||||
|
||||
### 11.2 IO 주입 지점 — `AuroraDevices/AuroraLaser/Laser.cs:60~103`
|
||||
|
||||
```csharp
|
||||
switch (Config.Type) {
|
||||
...
|
||||
case eLaserType.IPG_YLS: // :90~95
|
||||
var obj = ...DeviceManager["IO_Board"] as IODevice;
|
||||
_Laser = new LaserIPG_IO(DeviceId, IsSimul, obj);
|
||||
break;
|
||||
case eLaserType.Coherent_Arm_IO: // :96~99
|
||||
obj = ...DeviceManager["IO_Board"] as IODevice;
|
||||
_Laser = new LaserArm_IO(DeviceId, IsSimul, obj);
|
||||
break;
|
||||
case eLaserType.Trumpf_IO: // :100~103
|
||||
obj = ...DeviceManager["IO_Board"] as IODevice;
|
||||
_Laser = new LaserTrumpf_IO(DeviceId, IsSimul, obj); // ★ IO 보드를 Laser 에 주입
|
||||
break;
|
||||
}
|
||||
```
|
||||
|
||||
즉 **"IO 를 Laser 에 물려서 하나로 가져간다"는 패턴은 이미 프레임워크에 구현되어 있다.**
|
||||
`Config.Type`(= `LaserConfig` 세팅값) 하나로 통신형/IO형이 갈린다.
|
||||
|
||||
### 11.3 IO형 구현체가 하는 일 — `Trumpf/LaserTrumpf_IO.cs`
|
||||
|
||||
`ILaser` 의 모든 동작을 **DI/DO 읽기·쓰기로** 구현한다.
|
||||
|
||||
| ILaser 메서드 | 내부 동작 | 위치 |
|
||||
|---|---|---|
|
||||
| `LaserOn()` | `LASER_READY_ENABLE` DO ON → `LASER_READY` DI 대기 → `PSTART_STATICAL` DO ON → `PROG_ACTIVE` DI 대기 | `:77~135` |
|
||||
| `LaserOff()` | `PSTART_STATICAL` OFF → Ready/Complete DI 확인 → `LASER_READY_ENABLE` OFF | `:137~158` |
|
||||
| `GetLaserReady()` | `PROG_ACTIVE` DI 읽기 | `:271~285` |
|
||||
| `PilotLaserOn/Off()` | `PILOT_LASER_ON` DO 토글 (Ready/Active 중이면 거부) | `:193~229` |
|
||||
| 상태 폴링 | 300ms 스레드 `UpdateLaserStatus()` → Ready 면 `LaserOnLampOn` DO 자동 갱신 | `:248~255`, `:416~434` |
|
||||
|
||||
→ 지금 ViewModel 이 손으로 하고 있는 일(DI 폴링, DO 토글, 램프 표시)이 **이미 여기 다 들어있다.**
|
||||
|
||||
## 12. 현재 PTOP 구조 vs 정석 구조
|
||||
|
||||
### 12.1 현재 (PTOP)
|
||||
|
||||
```
|
||||
EquipmentSetup.RegisterDevices()
|
||||
registry.Register(IODevice, new PAIX_PTOP(...)) :153 ← IO 따로
|
||||
registry.Register(Laser, new Laser(...)) :155 ← Laser 따로 (Config.Type 이 IO형 아님)
|
||||
|
||||
[화면] UCScannerObjectTeachingControlBoxViewModel
|
||||
└ _IODevice.ReadDI(11) && ReadDI(15) :271 ← 직접 읽기
|
||||
└ _IODevice._iOUnit.WriteDoBit(9/13/17, ...) :541,557 ← 직접 쓰기
|
||||
[시퀀스] SeqComponentTeachingRun
|
||||
└ _Laser.GetLaserReady() :473 ← IO 와 무관한 통신형 경로
|
||||
└ LaserReadyResponse() → return true :658 ← 스텁
|
||||
[미사용] LaserIOControl.GetLaserReady() = DI9 && DI13 ← 또 다른 정의
|
||||
```
|
||||
|
||||
**증상:** Ready 정의 3벌, 핀 번호 산재, 이력(DI9 → DI12&24 → DI11&15)이 코드 주석에만 남음.
|
||||
|
||||
### 12.2 정석 (Aurora 패턴)
|
||||
|
||||
```
|
||||
registry.Register(IODevice, new PAIX_PTOP(...)) ← IO 는 IO 대로 등록(유지)
|
||||
registry.Register(Laser, new Laser(...), LaserConfig{
|
||||
Type = eLaserType.XXX_IO, ← IO형 지정
|
||||
(핀 매핑도 Config 로) ← 등록 시점에 함께 관리
|
||||
})
|
||||
└ Laser.DoActivate() 가 IODevice 를 찾아 ILaser 구현체에 주입
|
||||
|
||||
[화면/시퀀스] 모두 _Laser.GetLaserReady() / LaserOn() / PilotLaserOn() 만 호출
|
||||
```
|
||||
|
||||
**핵심:** IO Device 를 없애는 게 아니라, **Laser Device 가 IO Device 에 의존(주입)** 하게 만들고
|
||||
**바깥에서는 Laser 만 보이게** 하는 것. (= 받은 답변의 요지)
|
||||
|
||||
### 12.3 두 방식 비교
|
||||
|
||||
| 항목 | 현재(IO 직접 제어) | 정석(Laser 추상화) |
|
||||
|---|---|---|
|
||||
| 동작 결과 | **동일** — 실제로 잘 돌아감 | 동일 |
|
||||
| 코드량 | 당장은 적음(바로 ReadDI) | 초기 셋업 필요 |
|
||||
| Ready 정의 위치 | **3곳에 분산** | 1곳(`ILaser` 구현체) |
|
||||
| 핀 변경 영향 범위 | 화면·시퀀스 여러 곳 | 구현체/Config 1곳 |
|
||||
| 레이저 교체 시 | 코드 수정 | `Config.Type` 교체 |
|
||||
| 상태 폴링/램프 | 화면에서 수동 구현 | 구현체 스레드가 담당 |
|
||||
| **이력 추적** | **어려움**(주석 의존) | 구현체 1곳에 응집 |
|
||||
| 시뮬레이션 | 개별 가드 필요 | `IsSimul` 일괄 처리 |
|
||||
|
||||
> **"사용하는 덴 아무 상관없다"의 정확한 의미:** 기능적으로는 두 방식이 동치다.
|
||||
> 차이가 나는 건 **6개월 뒤·다른 사람이 볼 때**다. 지금처럼 배선이 3번 바뀐 이력이 주석에만 남으면
|
||||
> "왜 DI 11 인가"를 코드에서 재구성할 수 없다. 그게 이번 건에서 실제로 겪은 비용.
|
||||
|
||||
## 13. 주의 — 계층을 옮겨도 "매번 소스 수정"은 안 없어진다
|
||||
|
||||
`LaserTrumpf_IO` **역시 핀 번호가 enum 하드코딩**이다(`ePTDIAddress` / `ePTDOAddress`, `:101 :126 :279` 등).
|
||||
Trumpf_IO 로 갈아타도 **배선이 바뀌면 똑같이 소스 수정 → 빌드 → 배포**가 필요하다.
|
||||
|
||||
정리하면 **문제는 2개, 해법도 2개**다.
|
||||
|
||||
| 문제 | 증상 | 해법 |
|
||||
|---|---|---|
|
||||
| **(A) 제어 경로 분산** | Ready 정의 3벌, 추적 불가 | `Laser`(ILaser) 로 일원화 — §12.2 |
|
||||
| **(B) 핀 번호가 코드에 박힘** | 배선 변경 시 재빌드/재배포 | **핀 번호 외부화** — §14 |
|
||||
|
||||
**(A) 만 해결하면 구조는 깨끗해지지만 여전히 소스를 고쳐야 하고,
|
||||
(B) 만 해결하면 소스는 안 고쳐도 되지만 정의는 계속 흩어져 있다. 둘은 독립이다.**
|
||||
|
||||
## 14. (B) 핀 번호 외부화 — 이미 쓸 수 있는 수단
|
||||
|
||||
### 옵션 1 (추천, 재빌드 0) — `DI.csv` 라벨 조회
|
||||
|
||||
`AuroraDevices/AuroraIO/IOUnit.cs:367` 에 **이미 존재**:
|
||||
|
||||
```csharp
|
||||
public int GetDiIndex(string id) // ListDI 에서 ID 로 인덱스 검색, 없으면 -1
|
||||
public int GetDoIndex(string id) // :380
|
||||
```
|
||||
|
||||
`ListDI/ListDO` 는 런타임에 `IOUnitConfig.IOInterfaceMapDI/DO` (CSV)를 읽어 채워진다(`IODevice.cs:252`).
|
||||
→ 코드에는 번호 대신 **문자열 키**("LASER Ready" 등)만 두고, 배선 변경 시 **CSV 한 줄만 수정**.
|
||||
**소스 수정·빌드·배포 전부 불필요.**
|
||||
|
||||
- 전제: 현장 `DI.csv` 에 해당 신호가 이름으로 등재돼 있어야 한다.
|
||||
- 현 상태 주의: 기본 CSV 는 `12=START SWITCH / 24=FAN 8 ALARM / 25=LASER Ready` 로 **enum 과 이미 어긋나 있다**(§1 주의 참고).
|
||||
- 반환 `-1` 처리(미등재) 시 fail-closed 필요.
|
||||
|
||||
### 옵션 2 (현장 접근성 최고) — 세팅 화면 노출
|
||||
|
||||
PTOP 에 이미 `Settings/LaserInterlockSetting.cs` 가 있고 **PropertyGrid 로 세팅 메뉴에 자동 노출**된다
|
||||
(Head Theta 인터락이 이 방식). 여기에 `Laser Ready DI (Main/Sub)` 필드를 추가하면
|
||||
**현장에서 숫자만 바꿔 저장 → 재빌드 없음.** CSV 편집보다 접근이 쉽다.
|
||||
|
||||
### 옵션 3 (정공법, 중장기) — `LaserConfig` 확장 + IO형 일원화
|
||||
|
||||
`LaserConfig` 에 핀 매핑을 넣고 `eLaserType` 에 PTOP 용 IO 타입 추가 → (A)+(B) 동시 해결.
|
||||
**리스크:** `AuroraLaser` 는 **공용 프로젝트**라 타 장비 회귀 영향. 충분한 시간 확보 후 진행.
|
||||
|
||||
### 권고 순서
|
||||
|
||||
```
|
||||
[즉효] 옵션 1 or 2 로 핀 외부화 + 판정을 게이트 함수 1곳으로 수렴
|
||||
↓ (게이트를 두면 호출부는 그대로 두고 내부만 교체 가능)
|
||||
[중장기] 옵션 3 — 게이트 내부를 _Laser.GetLaserReady() 위임으로 바꿔 Laser 계층에 흡수
|
||||
```
|
||||
|
||||
## 15. 다음 프로젝트 적용 가이드 (체크리스트)
|
||||
|
||||
- [ ] IO 보드로 제어하는 레이저면 **처음부터 `eLaserType.*_IO` 계열로 등록**하고 `Laser` 만 호출한다.
|
||||
- [ ] 등록 시점에 **IO 디바이스 + 핀/비트 맵을 함께 지정**한다(Config 또는 CSV 키).
|
||||
- [ ] 화면·시퀀스에서 `IODevice.ReadDI/WriteDoBit` 직접 호출 **금지**(레이저 관련 한정).
|
||||
- [ ] 핀 번호는 코드 상수로 두지 않는다 → CSV ID 또는 Setting.
|
||||
- [ ] 배선 변경 이력은 주석이 아니라 **Config/CSV 이력**으로 남긴다.
|
||||
- [ ] 시뮬/미연결 정책(fail-closed)은 구현체 한 곳에서 정한다.
|
||||
|
||||
## 16. 확인 필요 (숙제)
|
||||
|
||||
- [ ] `CommonDeviceIds.IODevice` 의 **실제 문자열 값**이 `"IO_Board"` 인지.
|
||||
`Laser.cs:93/97/101` 이 `DeviceManager["IO_Board"]` 로 **하드코딩 조회**하므로, 값이 다르면 IO형 전환 시 `obj == null` → NRE.
|
||||
(Aurora.dll 내부 상수라 소스로 확인 불가 → 런타임 로그/디컴파일 필요)
|
||||
- [ ] PTOP 의 현재 `LaserConfig.Type` 실제 설정값(세팅 파일)
|
||||
- [ ] `Trumpf_IO` 의 `ePTDIAddress/ePTDOAddress` 핀 배치가 PTOP 실배선과 맞는지 (안 맞으면 신규 IO형 구현체 필요)
|
||||
- [ ] 현장 `DI.csv` 에 신규 Ready 신호가 **이름으로 등재**돼 있는지 → 옵션 1 가능 여부 판단
|
||||
- [ ] `LaserTrumpf_IO.GetLaserReady()` 는 `IsSimul` 일 때 **false** 를 반환(`:273~274`) — 시뮬 개발 PC 에서 출사 막힘. 전환 시 정책 확인.
|
||||
|
||||
## 17. 한 줄 요약
|
||||
|
||||
> **IO Device 는 IO Device 대로 두고, Laser Device 가 IO Device 에 의존(주입)하게 만들어
|
||||
> 바깥에서는 `Laser` 만 호출하는 것이 Aurora 의 정석.
|
||||
> 지금 PTOP 방식도 동작에는 문제없지만, 이력이 코드에 남지 않아 추적이 어렵다.
|
||||
> 그리고 "매번 소스 수정" 은 계층이 아니라 **핀 번호 외부화**로 풀어야 한다.**
|
||||
|
||||
---
|
||||
|
||||
# 4차 — 이설·다현장 대응 관점 (§10~§17 의 짝)
|
||||
|
||||
**질문(사용자):** "A공장에 맞춰 동작시켜도 장비를 B공장으로 옮겨 그 시설에 맞추면 바뀌는 부분이 분명 있을 텐데, 이렇게(소스 수정으로) 대응하는 게 맞나?"
|
||||
**답:** **소스 수정으로 대응하는 것은 맞지 않다.** 다만 "전부 설정화" 도 정답이 아니므로 **분류 기준**이 먼저다.
|
||||
§10~§17 이 *"왜 추상화인가"* 라면, §18 은 *"왜 설정화인가"* 다.
|
||||
|
||||
## 18. 값의 종속성에 따른 3계층 분리
|
||||
|
||||
| 계층 | 무엇이 바뀌나 | 있어야 할 곳 | 이설(A→B) 시 |
|
||||
|---|---|---|---|
|
||||
| **① 설비 고유·불변** | 용접 시퀀스 흐름, 안전 판정 **알고리즘**, PLC 핸드셰이크 규약 | **코드** | 안 바뀜 |
|
||||
| **② 호기·현장 가변** | IO 핀 매핑, PLC IP/국번, 축 원점·오프셋, 스케일, 파일 경로, 타임아웃 | **설정**(CSV/json/DeviceConfig) | **여기만 교체** |
|
||||
| **③ 생산 가변** | 파워·속도·좌표·페이지 | **레시피/모델** | 품종 따라 |
|
||||
|
||||
**현 문제:** ②에 속하는 값(Laser Ready DI 11/15)이 ①(코드)에 들어가 있다.
|
||||
그 결과 같은 공장에서 **레이저만 교체해도 재빌드**가 필요해졌고, 실제로 이번 한 건에서만
|
||||
`DI9 → DI12&&DI24 → DI11&&DI15` 로 **소스를 세 번 고쳤다.**
|
||||
|
||||
## 19. Aurora 는 이미 ②의 자리를 마련해 두었다
|
||||
|
||||
| 프레임워크 자산 | 위치 | 용도 |
|
||||
|---|---|---|
|
||||
| `IOUnitConfig.IOInterfaceMapDI/DO` | `AuroraIO/IOUnitConfig.cs:9` | DI/DO CSV 경로 지정 |
|
||||
| CSV 런타임 로드 | `AuroraIO/IODevice.cs:252` | 핀 라벨↔인덱스 테이블 구성 |
|
||||
| `IOUnit.GetDiIndex(string)` / `GetDoIndex(string)` | `AuroraIO/IOUnit.cs:367 / :380` | **이름으로 인덱스 조회**(미등재 시 `-1`) |
|
||||
| `DeviceManager.GetDeviceConfig()` / `SettingEntry` | Aurora | 장비별 설정 저장·로드, PropertyGrid 자동 노출 |
|
||||
|
||||
> **방증:** `DI.csv` / `DO.csv` 는 **이 저장소에 존재하지 않는다.**
|
||||
> 원래부터 소스가 아니라 **현장 데이터**로 관리되도록 설계된 파일이다.
|
||||
> 프레임워크는 "핀 번호는 데이터" 라고 이미 선언해 뒀는데, PTOP 가 그 자리를 안 쓰고 코드에 박은 상태.
|
||||
|
||||
## 20. 이설 시 실제로 바뀌는 항목 (대부분 ②)
|
||||
|
||||
- PLC IP/국번, 통신 포트, MES/호스트 주소
|
||||
- 전장 재배선에 따른 **DI/DO 번호**(도어, EMO, 레이저 Ready 등)
|
||||
- 축 원점·좌표 오프셋, 스캐너 보정 파일
|
||||
- 로그/레시피 저장 경로
|
||||
- 타임아웃(설비 속도·유틸리티 차이)
|
||||
|
||||
이것들이 코드에 있으면 **이설 = 재빌드 + 재배포 + 재검증** 이 되고,
|
||||
현장에서 급히 고친 값이 소스에 남아 **다음 호기로 전파**된다.
|
||||
(`FORCE_LASER_READY` 같은 점검용 플래그가 위험한 이유도 동일 — §3.2/§4 체크리스트 참고)
|
||||
|
||||
## 21. 권장 형태 — "현장 프로파일" 분리
|
||||
|
||||
```
|
||||
소스(불변) ─┐
|
||||
├─ 실행
|
||||
현장 프로파일 ─┘ ← 이설 시 이 폴더만 교체
|
||||
├ DI.csv / DO.csv (핀 매핑)
|
||||
├ DeviceConfig (PLC IP, 포트, 시뮬 여부)
|
||||
├ *Setting.json (인터락 각도·허용오차·Ready DI 번호)
|
||||
└ 보정/오프셋 파일
|
||||
```
|
||||
|
||||
**반드시 함께 넣어야 하는 2가지** (없으면 설정화가 오히려 위험해진다)
|
||||
|
||||
1. **기동 시 매핑 검증 + 로그**
|
||||
부팅 시 `"Laser Ready = DI11 && DI15 (from DI.csv)"` 를 **로그·화면에 표시**.
|
||||
`GetDiIndex()` 가 `-1`(미등재) 이면 **fail-closed + 알람**.
|
||||
→ 설정화의 최대 위험은 "값이 조용히 틀린 채 도는 것" 이며, 이 검증이 그것을 막는다.
|
||||
2. **프로파일 형상관리**
|
||||
현장 프로파일을 공장/호기별로 Git 또는 백업에 남긴다.
|
||||
현재는 CSV 이력이 어디에도 없어 **"A공장은 왜 11이었나"를 재구성할 수 없다.**
|
||||
|
||||
## 22. 주의 — 무조건 설정화는 답이 아니다
|
||||
|
||||
- **안전 인터락의 판정 로직 자체**(무엇을 AND 할지, fail-closed 정책)는 **코드에 둔다.**
|
||||
현장에서 바꿀 수 있게 하면 사고 시 책임 소재가 사라진다.
|
||||
현장이 바꿀 수 있는 범위는 **"어느 핀이 그 신호인가"까지**.
|
||||
- 설정 항목이 늘수록 **조합 검증 난이도**가 올라간다. ② 로 뺄 대상은 *현장마다 실제로 달라지는 것*만.
|
||||
- 설정 변경은 **엔지니어 권한**으로 제한하고 **변경 이력을 로그**로 남긴다.
|
||||
|
||||
## 23. 이번 건 결론 — 진행 순서
|
||||
|
||||
Ready DI 번호가 **아직 미확정**이므로, 지금 소스에 박으면 확정 후 **또 고치게 된다.**
|
||||
|
||||
```
|
||||
1) 판정을 게이트 함수 1곳으로 수렴 ← 이후 호출부는 건드리지 않아도 됨
|
||||
2) 게이트 내부 = DI.csv 라벨 조회(옵션 1) ← 현장은 CSV 만 수정, 재빌드 0
|
||||
3) CSV 등재가 어려우면 Setting 노출(옵션 2)로 대체
|
||||
4) 기동 시 매핑 검증·로그 추가 ← §21-1
|
||||
5) 안정화 후 게이트 내부를 Laser 계층 위임으로 교체 ← §14 옵션 3
|
||||
```
|
||||
|
||||
**소스 수정은 하지 않음.** 위 순서는 DI 번호 확정 이후 집중 작업으로 진행.
|
||||
276
docs/분석/2026/07/23/레이저제어_구조_학습노트.md
Normal file
276
docs/분석/2026/07/23/레이저제어_구조_학습노트.md
Normal file
@ -0,0 +1,276 @@
|
||||
# 학습노트 — 레이저 제어 구조 (IO 직접 제어 vs Laser 추상화)
|
||||
|
||||
**작성:** 2026-07-23
|
||||
**용도:** 혼자 읽으며 공부하기 위한 정리본. 소스를 안 열어도 흐름이 잡히도록 **핵심 코드를 여기 옮겨 놓았다.**
|
||||
**원본 분석서:** `Laser_Shot_IO_LaserReady_GuideBeam_분석.md` (§1~§23) — 근거·표·체크리스트는 그쪽에 있음
|
||||
|
||||
---
|
||||
|
||||
## 0. 30초 요약
|
||||
|
||||
1. Aurora 에는 **`Laser` 라는 추상화 계층**이 있고, 그 아래 `ILaser` 구현체가 통신형/IO형으로 갈린다.
|
||||
2. **IO 보드로 레이저를 제어하는 구현체도 이미 있다**(`LaserTrumpf_IO` 등). IO 보드를 Laser 에 **주입**하는 구조.
|
||||
3. PTOP 는 그 계층을 안 쓰고, **화면·시퀀스가 IO 보드를 직접 읽어** 레이저를 제어한다.
|
||||
4. 동작에는 문제없다. 문제는 **Ready 정의가 3벌로 흩어지고, 배선 변경 이력이 코드에 안 남는 것**.
|
||||
5. 그리고 **"배선 바뀔 때마다 소스 수정" 은 계층 문제가 아니라 핀 번호가 코드에 박혀 있어서**다. 둘은 별개 문제.
|
||||
|
||||
---
|
||||
|
||||
## 1. 먼저 알아야 할 그림 — Aurora 의 레이저 계층
|
||||
|
||||
```
|
||||
Aurora.Api.Device
|
||||
└─ AuroraLaser.Laser ← 장비 코드가 쓰는 유일한 창구
|
||||
└─ ILaser (인터페이스)
|
||||
├─ LaserTruFiber 통신형 (OPC UA)
|
||||
├─ TruPulse 통신형
|
||||
├─ LaserIPG_IO IO 보드형 ← IODevice 주입
|
||||
├─ LaserArm_IO IO 보드형 ← IODevice 주입
|
||||
└─ LaserTrumpf_IO IO 보드형 ← IODevice 주입
|
||||
```
|
||||
|
||||
**포인트:** 통신으로 제어하든 IO 릴레이로 제어하든, **바깥에서 보이는 얼굴은 `Laser` 하나**다.
|
||||
`LaserOn()`, `GetLaserReady()` 를 부르면 되고, 그게 시리얼인지 DO 비트인지는 구현체가 알아서 한다.
|
||||
|
||||
---
|
||||
|
||||
## 2. 코드 읽는 순서 (이 순서대로 보면 이해가 빠름)
|
||||
|
||||
| 순서 | 파일 | 볼 것 |
|
||||
|---|---|---|
|
||||
| ① | `AuroraDevices/AuroraLaser/Trumpf/LaserTrumpf_IO.cs` | IO형 구현체가 **DI/DO로 뭘 하는지** (§3) |
|
||||
| ② | `AuroraDevices/AuroraLaser/Laser.cs:60~103` | **타입 하나로 구현체가 갈리는 지점**, IO 주입 (§4) |
|
||||
| ③ | `Machines/.../EquipmentSetup.cs:153~156` | PTOP 가 **어떻게 등록했는지** (§5) |
|
||||
| ④ | `WeldingCommon/Views/Recipes/UCScannerObjectTeachingControlBoxViewModel.cs:262~281` | 화면이 **직접 DI 읽는 코드** (§5) |
|
||||
| ⑤ | `Machines/.../Sequences/Manual/SeqComponentTeachingRun.cs:473, 658` | 시퀀스의 Ready 확인부 (§6) |
|
||||
| ⑥ | `AuroraDevices/AuroraIO/IOUnit.cs:367` | **이름으로 핀 찾기** — 설정화의 열쇠 (§8) |
|
||||
|
||||
①을 먼저 보는 이유: "지금 화면에서 손으로 하는 일이 저기 이미 다 구현돼 있네" 를 먼저 느끼면 나머지가 쉽다.
|
||||
|
||||
---
|
||||
|
||||
## 3. ① IO형 구현체는 무슨 일을 하나 — `LaserTrumpf_IO.cs`
|
||||
|
||||
`ILaser` 의 모든 동작을 **DI 읽기 / DO 쓰기**로 구현해 놓았다.
|
||||
|
||||
```csharp
|
||||
public int LaserOn() // :77
|
||||
{
|
||||
SetLaserDOCommand(ePTDOAddress.LASER_READY_ENABLE, true); // DO ON
|
||||
Thread.Sleep(500);
|
||||
SetProgBit(""); // 프로그램 번호를 3비트로 출력
|
||||
while (!bResult) { // 타임아웃 10s
|
||||
GetLaserDIStatus(ePTDIAddress.LASER_READY, out bResult); // DI 로 Ready 대기
|
||||
}
|
||||
SetLaserDOCommand(ePTDOAddress.PSTART_STATICAL, true); // 출사 시작 DO
|
||||
while (!bResult) {
|
||||
GetLaserDIStatus(ePTDIAddress.PROG_ACTIVE, out bResult); // DI 로 Active 확인
|
||||
}
|
||||
}
|
||||
|
||||
public bool GetLaserReady() // :271
|
||||
{
|
||||
if (_IsSimul) return false; // ★ 시뮬이면 false (주의, §9)
|
||||
GetLaserDIStatus(ePTDIAddress.PROG_ACTIVE, out bActiveResult);
|
||||
return bActiveResult;
|
||||
}
|
||||
|
||||
private void UpdateLaserStatus() // :248
|
||||
{
|
||||
while (GetConnected()) {
|
||||
Thread.Sleep(300); // 300ms 폴링 스레드
|
||||
GetLaserAllStatus(); // Ready 면 LaserOnLampOn DO 자동 갱신
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**여기서 얻을 것:**
|
||||
- 지금 ViewModel 이 `OnMonitoring` 에서 손으로 하는 **DI 폴링 + 램프 표시**가 이미 여기 있다.
|
||||
- Ready 대기·타임아웃·시뮬 처리 같은 **정형 패턴이 한 곳에 응집**돼 있다.
|
||||
- 다만 핀 번호는 `ePTDIAddress` **enum 하드코딩** — 이게 §7 의 함정.
|
||||
|
||||
---
|
||||
|
||||
## 4. ② 구현체는 어디서 갈리나 — `Laser.cs:60~103`
|
||||
|
||||
```csharp
|
||||
protected override int DoActivate(IDeviceConfig config)
|
||||
{
|
||||
Config = config as LaserConfig;
|
||||
switch (Config.Type) { // ← 세팅값 하나로 결정된다
|
||||
case eLaserType.TruFiber:
|
||||
_Laser = new LaserTruFiber(DeviceId, IsSimul, Config.Opcua_Slot_No, SocketProtocol.TCP);
|
||||
break;
|
||||
case eLaserType.Trumpf_IO: // :100
|
||||
obj = Application.Current.AuroraEnv().DeviceManager["IO_Board"] as IODevice;
|
||||
_Laser = new LaserTrumpf_IO(DeviceId, IsSimul, obj); // ★ IO 보드 주입
|
||||
break;
|
||||
}
|
||||
return _Laser.Open(Config);
|
||||
}
|
||||
```
|
||||
|
||||
**여기서 얻을 것:**
|
||||
- "IO 를 Laser 에 물려서 하나로 관리" = **이미 프레임워크에 있는 패턴**. 새로 만들 게 아니다.
|
||||
- 레이저를 교체하면 이상적으로는 **`Config.Type` 만 바꾸면 된다.**
|
||||
- ⚠ `DeviceManager["IO_Board"]` — **디바이스 ID가 문자열로 하드코딩.** PTOP 등록 ID 와 다르면 `obj == null` → NRE.
|
||||
(`CommonDeviceIds.IODevice` 실제 값 확인이 숙제)
|
||||
|
||||
---
|
||||
|
||||
## 5. ③④ PTOP 는 실제로 어떻게 하고 있나
|
||||
|
||||
**등록** — `EquipmentSetup.cs`
|
||||
```csharp
|
||||
registry.Register(CommonDeviceIds.IODevice, () => new PAIX_PTOP(...), new IOUnitConfig()); // :153 IO 따로
|
||||
registry.Register(CommonDeviceIds.Laser, () => new Laser(...), new LaserConfig(), ...); // :155 Laser 따로
|
||||
```
|
||||
→ **둘이 서로 모른다.** `Config.Type` 이 IO형이 아니라서 `_Laser` 는 통신형 구현체가 붙는다.
|
||||
|
||||
**화면** — `UCScannerObjectTeachingControlBoxViewModel.cs`
|
||||
```csharp
|
||||
IsGuidBeam = _IODevice.ReadDO(17); // :270 DO 되읽기(피드백 아님)
|
||||
IsLaserReady = _IODevice.ReadDI(11) && _IODevice.ReadDI(15); // :271 ★ 직접 판정
|
||||
...
|
||||
_IODevice._iOUnit.WriteDoBit(9, true); // :557 Laser Ready 버튼
|
||||
_IODevice._iOUnit.WriteDoBit(13, true);
|
||||
```
|
||||
|
||||
**출사 게이트** — 같은 파일 `:415`
|
||||
```csharp
|
||||
if (IsLaserReady == false) { MessageUtil.ShowWarningToast("Please check Laser Ready."); return; }
|
||||
```
|
||||
|
||||
**여기서 얻을 것:** 레이저 제어가 `Laser` 를 안 거치고 **화면이 IO 보드를 직접 때린다.**
|
||||
그래서 "Laser Ready 가 무엇인가" 의 정의가 화면 안에 들어가 버렸다.
|
||||
|
||||
---
|
||||
|
||||
## 6. ⑤ 시퀀스 쪽은 더 애매하다 — `SeqComponentTeachingRun.cs`
|
||||
|
||||
```csharp
|
||||
// :473 진입부 — IO 가 아니라 통신형 Laser 디바이스에 물어본다
|
||||
var res = context.RetryUntilSuccess(_Laser.GetLaserReady, timeoutSeconds * 1000, 1);
|
||||
|
||||
// :561 출사 직전
|
||||
var res = context.RetryUntilSuccess(LaserReadyResponse, timeoutSeconds * 1000, 10);
|
||||
|
||||
// :658 그런데 그 함수는…
|
||||
private bool LaserReadyResponse()
|
||||
{
|
||||
//if (ApplicationExt.IsSimulation)
|
||||
return true; // ★ 항상 통과 (본문 전부 주석)
|
||||
}
|
||||
```
|
||||
|
||||
**정리하면 Manual 출사 경로의 Ready 확인은 3계층인데:**
|
||||
|
||||
| 계층 | 판정 | 효력 |
|
||||
|---|---|---|
|
||||
| 화면 게이트 `:415` | `DI11 && DI15` | **유일하게 실동작** |
|
||||
| 시퀀스 진입 `:473` | 통신형 Laser 호출 | IO 결선과 무관 |
|
||||
| 출사 직전 `:658` | `return true` | **인터락 없음** |
|
||||
|
||||
→ **시퀀스 안에는 IO 기반 Ready 확인이 없다.** 실동작 인터락은 Head Theta(`:572~613`) 뿐.
|
||||
|
||||
그리고 "Laser Ready" 정의가 소스에 **3벌**로 갈려 있다:
|
||||
|
||||
| 정의처 | 핀 | enum 이름 |
|
||||
|---|---|---|
|
||||
| ViewModel `:271` | DI 11 && DI 15 | `Laser_Output_State` (= 출사 *중* 상태) |
|
||||
| `LaserIOControl.cs:77` | DI 9 && DI 13 | `Laser_Ready` |
|
||||
| 시퀀스 `:473` | (IO 미사용) | 통신형 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 여기서 헷갈리기 쉬운 지점 — 문제는 **두 개**다
|
||||
|
||||
```
|
||||
(A) 제어 경로가 분산됨 → 정의 3벌, 이력 추적 불가 → 해법: Laser 계층으로 일원화
|
||||
(B) 핀 번호가 코드에 박힘 → 배선 바뀌면 재빌드 → 해법: 핀 번호 외부화
|
||||
```
|
||||
|
||||
**둘은 독립이다.**
|
||||
- (A) 만 고치면 → 구조는 깨끗해지지만 `LaserTrumpf_IO` 도 enum 하드코딩이라 **여전히 소스를 고쳐야 한다.**
|
||||
- (B) 만 고치면 → 소스는 안 고쳐도 되지만 **정의는 계속 흩어져 있다.**
|
||||
|
||||
이번 건에서 실제로 겪은 비용: `DI9` → `DI12 && DI24`(260617) → `DI11 && DI15`(260618) — **소스 3회 수정.**
|
||||
|
||||
---
|
||||
|
||||
## 8. ⑥ (B)의 열쇠 — 이름으로 핀 찾기
|
||||
|
||||
```csharp
|
||||
// AuroraDevices/AuroraIO/IOUnit.cs:367
|
||||
public int GetDiIndex(string id)
|
||||
{
|
||||
foreach (InfoIO io in ListDI)
|
||||
if (io.ID == id) return io.Index;
|
||||
return -1; // 미등재
|
||||
}
|
||||
```
|
||||
|
||||
`ListDI` 는 런타임에 **`DI.csv` 를 읽어** 채워진다(`IOUnitConfig.IOInterfaceMapDI` → `IODevice.cs:252`).
|
||||
|
||||
→ 코드에 **번호 대신 이름**("LASER Ready")만 두면, 배선이 바뀌어도 **CSV 한 줄 수정으로 끝.** 재빌드 0.
|
||||
|
||||
> **방증:** `DI.csv` / `DO.csv` 는 **이 저장소에 없다.** 원래부터 소스가 아니라 **현장 데이터**로 관리되도록 설계된 파일이다.
|
||||
> 프레임워크는 "핀 번호는 데이터" 라고 이미 선언해 뒀는데, 우리가 그 자리를 안 쓰고 코드에 박은 것.
|
||||
|
||||
**주의:** 기본 `DI.csv` 는 `12=START SWITCH / 24=FAN 8 ALARM / 25=LASER Ready` 로 **enum 과 이미 어긋나 있다.**
|
||||
번호 확정 시 CSV 대조 필수.
|
||||
|
||||
---
|
||||
|
||||
## 9. 이설(A공장→B공장) 관점 — 값의 3계층
|
||||
|
||||
| 계층 | 예시 | 있어야 할 곳 | 이설 시 |
|
||||
|---|---|---|---|
|
||||
| ① 설비 고유·불변 | 시퀀스 흐름, 안전 판정 **알고리즘**, PLC 규약 | **코드** | 안 바뀜 |
|
||||
| ② 호기·현장 가변 | **IO 핀**, PLC IP/국번, 원점·오프셋, 경로, 타임아웃 | **설정(CSV/json)** | **여기만 교체** |
|
||||
| ③ 생산 가변 | 파워·속도·좌표·페이지 | **레시피/모델** | 품종별 |
|
||||
|
||||
**우리 문제:** ② 인 Laser Ready DI 가 ① 에 들어가 있다.
|
||||
|
||||
**권장 형태**
|
||||
```
|
||||
소스(불변) ─┐
|
||||
├─ 실행
|
||||
현장 프로파일 ─┘ ← 이설 시 이 폴더만 교체
|
||||
├ DI.csv / DO.csv, DeviceConfig, *Setting.json, 보정파일
|
||||
```
|
||||
+ 반드시 함께: **① 기동 시 매핑 검증·로그**(미등재 `-1` 이면 fail-closed·알람), **② 프로파일 형상관리**(공장/호기별 백업).
|
||||
|
||||
**한계선:** 인터락 **판정 로직 자체**는 코드에 유지한다.
|
||||
현장이 바꿀 수 있는 범위는 **"어느 핀이 그 신호인가" 까지**. 그 이상은 사고 시 책임 소재가 사라진다.
|
||||
|
||||
**시뮬 주의:** `LaserTrumpf_IO.GetLaserReady()` 는 `_IsSimul` 이면 **false** 를 반환한다(`:273`).
|
||||
IO형으로 전환하면 개발 PC 에서 출사가 막힐 수 있다 — 전환 시 정책을 정해야 한다.
|
||||
|
||||
---
|
||||
|
||||
## 10. 오늘의 결론 3줄
|
||||
|
||||
1. **IO Device 는 IO Device 대로 두고, Laser Device 가 IO 에 의존(주입)** 하게 만들어 바깥에서는 `Laser` 만 호출 — 이게 Aurora 정석.
|
||||
2. 지금 PTOP 방식도 **동작에는 지장 없다.** 비용은 6개월 뒤·다른 사람이 볼 때 발생한다(이력이 주석에만 남음).
|
||||
3. **"매번 소스 수정" 은 계층이 아니라 핀 번호 외부화로 푼다.** 순서는 게이트 1곳 수렴 → CSV 라벨 조회 → 기동 검증 → (안정화 후) Laser 계층 흡수.
|
||||
|
||||
---
|
||||
|
||||
## 11. 셀프 체크 (이해했는지 확인용)
|
||||
|
||||
1. `Config.Type` 을 `Trumpf_IO` 로 바꾸면 무슨 일이 일어나나? 왜 그것만으로는 PTOP 에서 바로 안 되나?
|
||||
2. 화면의 `IsLaserReady` 와 `LaserIOControl.GetLaserReady()` 는 왜 서로 다른 값을 낼 수 있나?
|
||||
3. `LaserReadyResponse()` 를 지금 당장 실제 판정으로 되살리면 왜 위험한가?
|
||||
4. 핀 번호를 CSV 로 뺐는데 CSV 에 이름이 없으면 어떻게 동작해야 하나? 왜?
|
||||
5. 인터락 "허용 각도" 는 설정으로 빼도 되는데, "Front/Down 둘 다 아니면 차단" 규칙은 왜 코드에 둬야 하나?
|
||||
|
||||
---
|
||||
|
||||
## 12. 남은 숙제 (확인 필요)
|
||||
|
||||
- [ ] `CommonDeviceIds.IODevice` 실제 문자열이 `"IO_Board"` 인가 (`Laser.cs:93/97/101` 하드코딩 조회)
|
||||
- [ ] PTOP 현재 `LaserConfig.Type` 설정값
|
||||
- [ ] `Trumpf_IO` 의 `ePTDIAddress/ePTDOAddress` 배치가 PTOP 실배선과 맞는가 (안 맞으면 PTOP 전용 IO형 구현체 필요)
|
||||
- [ ] 현장 `DI.csv` 에 신규 Ready 신호가 **이름으로 등재**돼 있는가 → CSV 라벨 조회 가능 여부
|
||||
- [ ] 현장 신규 **Ready DI 번호 확정** (단일 신호인지 Core/Ring 2개 AND 인지)
|
||||
@ -0,0 +1,141 @@
|
||||
# Scanner Calibration — 그리드 마우스 조작 시 Index 에러 & One Step Vision Offset 미입력 분석
|
||||
|
||||
- 작성일: 2026-07-28
|
||||
- 대상: `UCScannerCalibration` (View / ViewModel / 코드비하인드) + `SeqScannerCalibration`
|
||||
- 상태: **분석만 완료 (소스 미수정)**
|
||||
|
||||
---
|
||||
|
||||
## 대상 파일
|
||||
|
||||
| 역할 | 경로 |
|
||||
|---|---|
|
||||
| View | `WeldingComponents/WeldingCommon/Views/Calibration/UCScannerCalibration.xaml` |
|
||||
| 코드비하인드 | `WeldingComponents/WeldingCommon/Views/Calibration/UCScannerCalibration.xaml.cs` |
|
||||
| ViewModel | `WeldingComponents/WeldingCommon/Views/Calibration/UCScannerCalibrationViewModel.cs` |
|
||||
| 시퀀스 | `WeldingComponents/WeldingCommon/Sequences/Calibration/SeqScannerCalibration.cs` |
|
||||
|
||||
---
|
||||
|
||||
## 이슈 1 — 그리드(row×column) 마우스 조작 중 Index 에러
|
||||
|
||||
### 증상
|
||||
Vision Correction List 그리드(row×column으로 채워진 셀)를 **마우스로 조작하다가 가끔 index 관련 에러**가 난다.
|
||||
|
||||
### 근본 원인 — 방어 없는 `VisionCorrectionList[SelectedIndex]` 접근
|
||||
|
||||
핵심은 `MoveScanner()` (ViewModel `UCScannerCalibrationViewModel.cs:536`):
|
||||
|
||||
```csharp
|
||||
public void MoveScanner()
|
||||
{
|
||||
double scannerPosX = VisionCorrectionList[SelectedIndex].MasterX; // ← 방어 없음
|
||||
double scannerPosY = VisionCorrectionList[SelectedIndex].MasterY;
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
이 메서드는 그리드 **더블클릭** 시 호출된다 (`UCScannerCalibration.xaml.cs:149`):
|
||||
|
||||
```csharp
|
||||
private void GridEditorVisionCorrectionList_MouseDoubleClick(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
_ViewModel.MoveScanner(); // 무조건 호출 — 유효 행 판정 없음
|
||||
}
|
||||
```
|
||||
|
||||
`SelectedIndex`는 `SelectedCellsChanged`에서 `DataGrid.SelectedIndex`를 그대로 대입받는다 (`UCScannerCalibration.xaml.cs:108`):
|
||||
|
||||
```csharp
|
||||
_ViewModel.SelectedIndex = obj.SelectedIndex; // 선택 해제 시 -1 이 들어옴
|
||||
```
|
||||
|
||||
`DataGrid.SelectedIndex`는 **선택이 없으면 -1**을 반환한다. 따라서 다음 상황에서 `VisionCorrectionList[SelectedIndex]`가 `ArgumentOutOfRangeException`으로 터진다.
|
||||
|
||||
#### 터지는 시나리오
|
||||
|
||||
1. **선택되지 않은 영역 더블클릭** (`SelectedIndex == -1`)
|
||||
- 헤더, 마지막 행 아래 빈 공간, 빈 그리드를 더블클릭하면 `SelectedIndex`가 -1인 상태에서 `MoveScanner()` → `VisionCorrectionList[-1]` 예외.
|
||||
- 특히 **Apply 버튼을 누르기 전**에는 리스트가 비어 있다. `InitVariable()`에서 `InitializeDataGridViewItem(...)` 호출이 주석 처리(`UCScannerCalibrationViewModel.cs:188`)돼 있어, 화면 진입 직후 `VisionCorrectionList.Count == 0`. 이때 더블클릭하면 `SelectedIndex`가 0이든 -1이든 무조건 out-of-range.
|
||||
|
||||
2. **Esc → 더블클릭 조합**
|
||||
- `KeyDown`에서 Esc를 누르면 `dg.SelectedItem = null` (`UCScannerCalibration.xaml.cs:140`) → `SelectedCellsChanged` 발생 → `SelectedIndex = -1`. 곧이어 더블클릭하면 예외.
|
||||
|
||||
3. **화면 전환(IsVisibleChanged) 직후 stale 상태**
|
||||
- `GridEditorVisionCorrectionList_IsVisibleChanged`에서 `UnselectAllCells()` (`UCScannerCalibration.xaml.cs:130`) → `SelectedIndex = -1`.
|
||||
|
||||
4. **Apply 재적용으로 인한 stale 인덱스 (셀 개수 축소 시)**
|
||||
- 큰 row×column으로 한 번 채운 뒤 큰 인덱스의 셀을 선택 → row/column을 줄여 다시 **Apply** → `InitializeDataGridViewItem`이 `VisionCorrectionList.Clear()` 후 더 적은 개수로 재생성(`UCScannerCalibrationViewModel.cs:510`). 그런데 `SelectedIndex`는 이전 큰 값 그대로 남아 있음 → 이후 더블클릭/시퀀스에서 `VisionCorrectionList[SelectedIndex]` out-of-range.
|
||||
- Apply 시 `SelectedIndex`를 리셋하는 로직이 없다.
|
||||
|
||||
### 이슈 1 권장 수정 (방향, 미적용)
|
||||
|
||||
- `MoveScanner()` 진입부 가드:
|
||||
```csharp
|
||||
if (SelectedIndex < 0 || SelectedIndex >= VisionCorrectionList.Count) return;
|
||||
```
|
||||
- `MouseDoubleClick` 핸들러에서 실제로 행(row)을 더블클릭했는지 확인 후 호출 (`e.OriginalSource`를 타고 `DataGridRow` 유무 확인, 또는 `dg.SelectedItem is CorrectionUnit` 체크).
|
||||
- `PerformCmdApplyGridItems`(재적용) 시 `SelectedIndex = -1`로 리셋.
|
||||
- `SelectedCellsChanged`에서 `-1` 방어(음수면 SelectedVisionCorrectionList 갱신만 하고 SelectedIndex는 별도 처리).
|
||||
|
||||
---
|
||||
|
||||
## 이슈 2 — Scanner Cal 후 Vision Cal + One Step 이동/Find 했는데 Vision Offset(X,Y)이 안 들어감
|
||||
|
||||
### 증상
|
||||
Scanner Calibration → Vision Calibration 진행 후, 개별 **One Step**으로 선택 좌표 이동 → Find로 패턴을 찾는데, 결국 **Vision Offset 값이 안 들어가는 경우**가 있다.
|
||||
|
||||
### 흐름 요약
|
||||
|
||||
- One Step 실행 시 `oneStepCalibrationIndex = SelectedIndex` 로 넘어감 (`UCScannerCalibrationViewModel.cs:474`).
|
||||
- 시퀀스 `InitSequenceVariable`: `if (_InitParam.Is1StepCalibration) _PositionIndex = _InitParam.oneStepCalibrationIndex;` (`SeqScannerCalibration.cs:151`).
|
||||
- Find 성공 시 `SetVisionPosition`이 이벤트로 `UpdateDataGridItem(_PositionIndex, visionBitX, visionBitY)` 발행 (`SeqScannerCalibration.cs:425`).
|
||||
- ViewModel `OnUpdateCorrectionTable`가 받아서 그리드에 반영 (`UCScannerCalibrationViewModel.cs:223`):
|
||||
```csharp
|
||||
VisionCorrectionList[updateDataGridItem.Upositionindex].VisionX = updateDataGridItem.UVisionX;
|
||||
VisionCorrectionList[updateDataGridItem.Upositionindex].VisionY = updateDataGridItem.UVisionY;
|
||||
```
|
||||
|
||||
### 원인 후보 (값이 0으로 남거나 아예 반영 안 되는 경로)
|
||||
|
||||
#### (A) 패턴 Find 실패 / 오프셋 미수렴 → **의도적으로 0,0 기록**
|
||||
- `DecisionFindResult`에서 `_PatternResult.Status != FoundOK` 면 `SEQ_SET_VISION_POSITION_FAILED` (`SeqScannerCalibration.cs:350`).
|
||||
- `DecisionCheckOffset`에서 10회(`_CheckOffsetCount < 10`) 내에 오프셋이 `_InitParam.CheckOffset`(= UI "Align Distortion", 기본 **0.01mm**) 아래로 못 내려가면 `SetVisionPositionFailed` (`SeqScannerCalibration.cs:405`).
|
||||
- `SetVisionPositionFailed`는 `VisionX=0, VisionY=0`을 기록(`SeqScannerCalibration.cs:449`).
|
||||
- → 사용자 눈에는 **"오프셋이 안 들어감(0으로 남음)"** 으로 보임. Align Distortion 임계값이 과도하게 타이트하면 정상 Find여도 수렴 실패로 0 처리됨. **가장 흔한 실사용 원인.**
|
||||
|
||||
#### (B) `SelectedIndex == -1` 인 채 One Step 실행 → 시퀀스 예외로 미반영
|
||||
- One Step 체크했지만 그리드에서 **행을 선택하지 않았거나 Esc/화면전환으로 선택 해제**된 상태면 `SelectedIndex = -1`.
|
||||
- `_PositionIndex = -1` → `CaptureNextPosition`의 `_MarkingPositionList_MM[-1]` (`SeqScannerCalibration.cs:282`) 또는 `OnUpdateCorrectionTable`의 `VisionCorrectionList[-1]`에서 예외 → 해당 셀 오프셋 미반영.
|
||||
- `PerformCmdRunScannerCalibration`에는 `VisionCorrectionList.Count <= 0` 체크만 있고 **SelectedIndex 유효성 검사가 없음** (`UCScannerCalibrationViewModel.cs:445`).
|
||||
|
||||
#### (C) `OnUpdateCorrectionTable`의 무방어 인덱스 접근 → 예외 시 조용히 누락
|
||||
- `VisionCorrectionList[Upositionindex]`에 범위/유효성 검사 없음 (`UCScannerCalibrationViewModel.cs:223`).
|
||||
- 이벤트 처리 도중 사용자가 **Apply를 다시 눌러 리스트를 Clear/재생성**했거나 인덱스가 어긋나면 dispatcher 핸들러에서 예외 → 오프셋 반영 실패. try/catch도 없어 이후 업데이트에도 영향 가능.
|
||||
|
||||
#### (D) 이벤트 반영 타이밍
|
||||
- 시퀀스는 `Publish` 후 `Thread.Sleep(100)`만 주고 진행 (`SeqScannerCalibration.cs:427`). 핸들러는 `.ObserveOnDispatcher()`로 UI 스레드에 큐잉되는데, One Step은 반영 직후 바로 `SEQ_SAVE_RESULT_DATA`로 종료(`DecisionCheckNextPostionExists`, `SeqScannerCalibration.cs:466`)된다. UI가 바쁘면 100ms 내 dispatcher 처리가 밀릴 수 있음(주 원인은 아니나 (C)와 겹치면 악화).
|
||||
|
||||
### 참고 — 인덱스 매핑 자체는 일치
|
||||
UI `InitializeDataGridViewItem`(`UCScannerCalibrationViewModel.cs:512`)와 시퀀스 `InitSequenceVariable`(`SeqScannerCalibration.cs:163`) 모두 **column 바깥 / row 안쪽** 루프로 `index = column * rowCount + row` 동일. 즉 row/column count가 같으면 인덱스는 어긋나지 않는다. (단, count가 UI/시퀀스 간 달라지는 재적용 타이밍이 있으면 (C)로 이어짐.)
|
||||
|
||||
### 이슈 2 권장 수정 (방향, 미적용)
|
||||
|
||||
1. **One Step 실행 가드**: `PerformCmdRunScannerCalibration`에서 `Is1StepCalibration && (SelectedIndex < 0 || SelectedIndex >= VisionCorrectionList.Count)`면 경고 후 return.
|
||||
2. **`OnUpdateCorrectionTable` 방어**: `Upositionindex` 범위 검사 + try/catch, 범위 밖이면 로그만.
|
||||
3. **Find 실패/미수렴 가시화**: `SetVisionPositionFailed` 진입 시 사용자에게 명확히 알림(현재는 0,0만 조용히 기록). Align Distortion(CheckOffset) 기본값·현장값 재검토(0.01mm가 현실적으로 수렴 가능한지).
|
||||
4. **`_CheckOffsetCount < 10` / CheckOffset 조합**을 현장 스캐너 정밀도에 맞게 튜닝.
|
||||
|
||||
---
|
||||
|
||||
## 우선순위 제안
|
||||
|
||||
| 순위 | 대상 | 조치 |
|
||||
|---|---|---|
|
||||
| 1 | `MoveScanner()` / `OnUpdateCorrectionTable` | 인덱스 범위 가드 추가 (이슈1·2 크래시 즉시 차단) |
|
||||
| 2 | `PerformCmdRunScannerCalibration` | One Step 시 SelectedIndex 유효성 검사 |
|
||||
| 3 | `PerformCmdApplyGridItems` | 재적용 시 SelectedIndex 리셋 |
|
||||
| 4 | `MouseDoubleClick` | 실제 DataGridRow 더블클릭만 MoveScanner 호출 |
|
||||
| 5 | Align Distortion(CheckOffset) | 현장 임계값 재검토 + Find 실패 알림 |
|
||||
|
||||
> 본 문서는 분석 전용이며 소스는 아직 수정하지 않았습니다.
|
||||
162
docs/작업일지/2026_07_23.md
Normal file
162
docs/작업일지/2026_07_23.md
Normal file
@ -0,0 +1,162 @@
|
||||
# 작업일지 — 2026-07-23
|
||||
|
||||
## 1. Recipe Laser Shot — Laser Ready / Guide Beam IO 번호 확인 및 상시 ON 레이저 대응
|
||||
|
||||
### 배경
|
||||
현장 레이저가 기존과 달라 배선을 다른 곳에 물렸고, **레이저 컨트롤러를 상시 ON** 상태로 둔 채 그 값을 읽어
|
||||
**TRUE로 유지돼야 레시피 출사 시퀀스가 동작**하는 구조로 가야 함.
|
||||
Recipe의 Laser Shot 구간에서 Laser Ready 버튼 / Guide Beam 버튼이 물린 **IO 번호 확인** 요청.
|
||||
|
||||
> **현재 프로그램 SOURCE는 수정하지 않음** — 분석 및 수정안(DIFF)만 기록.
|
||||
|
||||
### 확인 결과 — 현재 사용 중인 IO 번호
|
||||
| 항목 | 방향/번호 | enum 의미 | 소스 위치 |
|
||||
|---|---|---|---|
|
||||
| Laser Ready 판정(모니터링) | **DI 11 && DI 15** | `CoreLaser_Laser_Output_State` / `RingLaser_Laser_Output_State` | `UCScannerObjectTeachingControlBoxViewModel.cs:271` |
|
||||
| Laser Ready 버튼(토글 출력) | **DO 9, DO 13** | `CoreLaser_Control` / `RingLaser_Control` | 같은 파일 `:552~567` |
|
||||
| Guide Beam 상태 표시 | **DO 17 (읽기)** | `EX_Red_Control` | 같은 파일 `:270` |
|
||||
| Guide Beam 버튼(토글) | **DO 17 (쓰기)** | `EX_Red_Control` | 같은 파일 `:534~542` |
|
||||
| 출사(TeachingRun) 게이트 | `IsLaserReady == false` → 차단 | — | 같은 파일 `:415~418` |
|
||||
|
||||
- Guide Beam은 DI가 없어 **DO 17 명령 비트를 되읽어 표시**(실제 빔 출력 피드백 아님).
|
||||
- Laser Ready 판정 IO 변천: `DI 9` → `DI 12 && DI 24`(260617) → `DI 11 && DI 15`(260618, 현재).
|
||||
|
||||
### 현재 구조가 상시 ON 레이저와 안 맞는 이유
|
||||
- DO9/DO13 토글은 더 이상 Ready 성립 조건이 아님(안 눌러도 Ready여야 하고, 눌러도 무의미).
|
||||
- 판정은 **새로 물린 DI를 상시 읽어 TRUE 유지**하는 형태여야 함.
|
||||
- 변경 포인트는 **271행 판정식(필수)** + **버튼 의미 재정의(선택)** 두 곳뿐.
|
||||
|
||||
### 작성한 수정안 (미적용)
|
||||
- 판정 DI를 파일 상단 상수(`DI_LASER_READY_MAIN` / `DI_LASER_READY_SUB`)로 추출 → 현장 번호 확정 시 상수만 교체.
|
||||
`SUB < 0` 이면 단일 DI 판정으로 자동 동작.
|
||||
- 점검용 `FORCE_LASER_READY`(기본 false, true 커밋 금지) 추가.
|
||||
- Laser Ready 버튼: DO9/13 쓰기 차단 + 현재 DI 상태 토스트 안내(구 레이저 복귀 대비 주석 보존).
|
||||
- 출사 게이트(`PerformCmdRunTeachingRun`)는 변경 없음 — `IsLaserReady`가 TRUE 유지되면 자동 통과.
|
||||
- (별건) `LaserIOControl.cs` `RingLaser_Control_Status = 23` → 실배선 기준 `24` 정정 권장.
|
||||
|
||||
### 주의 — DI/DO 실제 의미
|
||||
인덱스의 실제 의미는 enum이 아니라 **런타임 로드되는 `DI.csv`/`DO.csv` 맵**이 결정한다
|
||||
(Setting → Device(IODevice) Config의 `IOInterfaceMapDI/DO`, 소스 하드코딩 없음).
|
||||
기본 `DI.csv`는 12=START SWITCH, 24=FAN 8 ALARM, 25=LASER Ready 로 **enum과 불일치 가능** → 번호 확정 시 CSV 대조 필수.
|
||||
|
||||
### 잔여·후속 작업
|
||||
- [ ] 현장 신규 배선 **Ready DI 번호 확정**(단일인지 Core/Ring 2개 AND인지 포함)
|
||||
- [ ] 로드 중인 `DI.csv`에서 해당 번호 라벨 확인
|
||||
- [ ] 판정부 적용 + 상수값 교체 → Recipe 화면에서 컨트롤러 ON만으로 Ready=TRUE 확인
|
||||
- [ ] TeachingRun(출사)이 버튼 조작 없이 진입되는지 확인
|
||||
- [ ] Guide Beam은 여전히 명령 비트 되읽기 — 신규 레이저에 가이드빔 제어선 별도 존재 시 DO 번호 재확인
|
||||
(정식 경로 TruPulse `ExtPilotOnOff` 미구현)
|
||||
|
||||
**상세 분석서:** `docs/분석/2026/07/23/Laser_Shot_IO_LaserReady_GuideBeam_분석.md`
|
||||
|
||||
---
|
||||
|
||||
## 2. 시퀀스 측 레이저(IO) 인터락 추가 분석 — `SeqComponentTeachingRun`
|
||||
|
||||
Manual 출사를 `SeqComponentTeachingRun` 으로 돌릴 예정이므로, 시퀀스 안의 레이저 Ready 확인부까지 확인.
|
||||
**소스는 여전히 미수정(분석만).**
|
||||
|
||||
### 확인 결과 — Ready 를 보는 곳은 3계층, 그중 실효는 1개뿐
|
||||
| 계층 | 위치 | 판정 | 효력 |
|
||||
|---|---|---|---|
|
||||
| ① 화면 게이트 | ViewModel `:415` ← `:271` `DI11 && DI15` | IO 기반 | **유일한 실동작 IO 인터락**(버튼 클릭 시점 1회) |
|
||||
| ② 시퀀스 진입 | `SeqComponentTeachingRun.cs:473` `_Laser.GetLaserReady` | Aurora Laser 디바이스(**IO 아님**) | 결선과 무관 |
|
||||
| ③ 출사 직전 | 같은 파일 `:561` → `LaserReadyResponse()` `:658` | `return true;` 스텁 | **인터락 없음** |
|
||||
|
||||
→ **시퀀스 내부에는 IO(DI) 기반 Ready 확인이 없다.** 실동작 인터락은 Head Theta(`:572~613`)뿐.
|
||||
|
||||
### "Laser Ready" 정의가 3벌로 갈려 있음
|
||||
- ViewModel `:271` = **DI 11 && DI 15**(`Laser_Output_State`)
|
||||
- `LaserIOControl.cs:77` = **DI 9 && DI 13**(`Laser_Ready`)
|
||||
- 시퀀스 `:473` = 레이저 디바이스 호출(IO 미사용)
|
||||
|
||||
### 부수 발견
|
||||
- `LaserIOControl` 은 PTOP 에서 dead — IODevice 는 `PAIX_PTOP` 등록(`EquipmentSetup.cs:153`),
|
||||
`SeqTestRun.cs:469` 의 `as LaserIOControl` 는 항상 null. **단 `SeqTestRun` 은 미사용 파일(확인 완료)** → 실동작 리스크 없음.
|
||||
- **Auto 경로엔 레이저 Ready 인터락 자체 없음** — `SeqAutoRunPTOP.cs:589` 는 no-op 이고 호출처도 없음.
|
||||
- `ePrismaticDIAddress/DOAddress` enum 이 `PAIX_PTOP.cs` / `LaserIOControl.cs` **2벌 중복**.
|
||||
|
||||
### 수정 방향(미적용) — 판정 1곳 + 호출 3곳
|
||||
1. 공용 게이트 `WeldingCommon/Devices/Laser/LaserReadyGate.cs` 신설(상수 MAIN/SUB/FORCE, `IsReady(IODevice)` fail-closed)
|
||||
— 구식 csproj 라 `<Compile Include>` 수동 추가 필요
|
||||
2. ViewModel `:271` → 게이트 호출
|
||||
3. 시퀀스 `:473` → `_Laser.GetLaserReady` 대신 게이트
|
||||
4. 시퀀스 `:658 LaserReadyResponse()` → 게이트 (출사 직전 재확인 부활)
|
||||
5. `LaserIOControl.GetLaserReady()` 도 게이트로 위임
|
||||
6. Laser Ready 버튼(DO9/13) 쓰기 차단 + DI 상태 토스트
|
||||
|
||||
**리스크:** 4번은 "항상 통과 → 미충족 시 차단"으로 바뀌므로 **Ready DI 번호 확정 후 마지막에** 적용.
|
||||
Auto 경로 게이트 추가는 양산 정지 리스크가 새로 생기므로 현장 합의 후 별도 결정.
|
||||
|
||||
**상세:** 같은 분석서 §6~§9
|
||||
|
||||
---
|
||||
|
||||
## 3. 아키텍처 검토 — IO 직접 제어 vs Laser 추상화 계층 (학습용)
|
||||
|
||||
**소스 수정 없음. 개념 정리 + 다음 프로젝트 방향성 기록.**
|
||||
|
||||
### 논의
|
||||
- **차장님 의견:** 추상화 레벨이 `Laser` 클래스이므로, **디바이스 등록 시점에 IO 보드를 Laser 에 물리고 IO 주소/비트 맵도 함께 등록**해 한 곳에서 관리했어야 함.
|
||||
- **PTOP 현실:** IO 는 `IODevice` 로 따로 등록 → 화면·시퀀스가 address 를 직접 읽어 개별 제어.
|
||||
- **결론(받은 답변):** IO Device 는 IO Device 대로 쓰되 **Laser Device 가 IO Device 에 의존적으로 동작**하는 게 정석. **이력을 모르면 추적이 어려울 뿐, 사용에는 지장 없음.**
|
||||
|
||||
### 확인한 사실
|
||||
- Aurora 에 **IO형 레이저 구현이 이미 존재** — `Laser.cs:90~103` 의 `eLaserType.IPG_YLS / Coherent_Arm_IO / Trumpf_IO` 가 `DeviceManager["IO_Board"]` 를 꺼내 `ILaser` 구현체에 **주입**.
|
||||
- `LaserTrumpf_IO` 는 `LaserOn/Off`, `GetLaserReady`, `PilotLaser`, 300ms 상태 폴링·램프 갱신까지 **전부 DI/DO 로 구현**돼 있음 → 지금 ViewModel 이 손으로 하는 일이 이미 들어있음.
|
||||
- **단, 계층을 옮겨도 "매번 소스 수정"은 안 없어짐** — `LaserTrumpf_IO` 도 핀이 `ePTDIAddress` enum 하드코딩.
|
||||
→ 문제는 **(A) 제어 경로 분산** 과 **(B) 핀 번호 하드코딩** 2개이며 서로 독립.
|
||||
|
||||
### (B) 해결 수단 — 이미 사용 가능
|
||||
- **옵션 1(추천, 재빌드 0):** `IOUnit.cs:367 GetDiIndex(string id)` / `:380 GetDoIndex` 로 **`DI.csv` 라벨 조회** → 배선 변경 시 CSV 한 줄만 수정.
|
||||
- **옵션 2:** `LaserInterlockSetting` 에 DI 번호 필드 추가 → 세팅 화면에서 변경(재빌드 없음).
|
||||
- **옵션 3(중장기):** `LaserConfig` 확장 + IO형 일원화 — 공용 `AuroraLaser` 수정이라 타 장비 회귀 리스크.
|
||||
|
||||
### 다음 방향성
|
||||
즉효로 옵션 1/2 + 판정 게이트 1곳 수렴 → 안정화 후 게이트 내부를 `_Laser.GetLaserReady()` 위임으로 교체해 Laser 계층에 흡수.
|
||||
신규 프로젝트는 **처음부터 `eLaserType.*_IO` 등록 + 핀 맵 Config/CSV 관리**.
|
||||
|
||||
### 숙제
|
||||
- `CommonDeviceIds.IODevice` 실제 값이 `"IO_Board"` 인지 (`Laser.cs` 가 문자열 하드코딩 조회 → 다르면 IO형 전환 시 NRE)
|
||||
- PTOP 현재 `LaserConfig.Type` 설정값 / `Trumpf_IO` 핀 배치와 실배선 일치 여부
|
||||
- `DI.csv` 에 신규 Ready 신호가 이름으로 등재됐는지(옵션 1 가능 여부)
|
||||
- `LaserTrumpf_IO.GetLaserReady()` 는 시뮬에서 **false** 반환(`:273`) → 개발 PC 출사 막힘, 전환 시 정책 확인
|
||||
|
||||
**상세:** 같은 분석서 §10~§17
|
||||
|
||||
---
|
||||
|
||||
## 4. 이설·다현장 대응 관점 — "왜 설정화인가"
|
||||
|
||||
**질문:** A공장에 맞춰 놓아도 B공장으로 이설하면 바뀌는 부분이 있는데, 소스 수정으로 대응하는 게 맞나?
|
||||
**답:** 맞지 않다. 단 "전부 설정화"도 아니고 **값의 종속성으로 3계층 분리**가 먼저.
|
||||
|
||||
| 계층 | 내용 | 위치 | 이설 시 |
|
||||
|---|---|---|---|
|
||||
| ① 설비 고유·불변 | 시퀀스 흐름, 안전 판정 **알고리즘**, PLC 규약 | 코드 | 안 바뀜 |
|
||||
| ② 호기·현장 가변 | **IO 핀 매핑**, PLC IP/국번, 원점·오프셋, 경로, 타임아웃 | 설정(CSV/json) | **여기만 교체** |
|
||||
| ③ 생산 가변 | 파워·속도·좌표·페이지 | 레시피/모델 | 품종별 |
|
||||
|
||||
- **현 문제:** ②인 Laser Ready DI 가 ①(코드)에 있음 → 레이저 교체만으로 재빌드. 실제 이번 건에서 `DI9 → DI12&24 → DI11&15` **소스 3회 수정.**
|
||||
- **Aurora 는 ② 자리를 이미 제공** — `IOUnitConfig.IOInterfaceMapDI/DO`, `IODevice.cs:252` CSV 로드, `IOUnit.cs:367 GetDiIndex(string)`.
|
||||
**`DI.csv` 가 저장소에 없다는 것 자체가 "현장 데이터로 관리하라"는 설계 방증.**
|
||||
- **권장:** 소스 / **현장 프로파일**(DI·DO CSV, DeviceConfig, Setting json, 보정파일) 분리 → 이설 시 프로파일만 교체.
|
||||
단 **① 기동 시 매핑 검증+로그**(미등재 `-1` 이면 fail-closed·알람), **② 프로파일 형상관리(공장/호기별 백업)** 를 반드시 같이 넣을 것.
|
||||
- **한계선:** 인터락 **판정 로직 자체**는 코드에 유지(현장이 바꿀 수 있는 건 "어느 핀인가"까지). 설정 변경은 권한 제한 + 이력 로그.
|
||||
- **진행 순서:** 게이트 1곳 수렴 → CSV 라벨 조회 → (대체: Setting 노출) → 기동 검증·로그 → 안정화 후 Laser 계층 위임.
|
||||
|
||||
**상세:** 같은 분석서 §18~§23
|
||||
|
||||
---
|
||||
|
||||
## 5. 학습노트 (읽기용 정리본)
|
||||
|
||||
소스를 열지 않고도 흐름이 잡히도록 **핵심 코드를 옮겨 담은 정리본**을 별도 작성.
|
||||
|
||||
**`docs/분석/2026/07/23/레이저제어_구조_학습노트.md`**
|
||||
|
||||
- §0 30초 요약 → §1 Aurora 레이저 계층도 → **§2 코드 읽는 순서(①~⑥)**
|
||||
- §3 `LaserTrumpf_IO` 가 DI/DO 로 하는 일(코드 발췌) → §4 구현체가 갈리는 지점(`Laser.cs:60~103`)
|
||||
- §5 PTOP 실제 등록/화면 코드 → §6 시퀀스 Ready 3계층 + 정의 3벌 표
|
||||
- §7 문제는 두 개(A 경로 분산 / B 핀 하드코딩, 서로 독립) → §8 `IOUnit.GetDiIndex` 로 이름 조회
|
||||
- §9 이설 관점 3계층 + 현장 프로파일 → §10 결론 3줄 → **§11 셀프 체크 5문항** → §12 숙제
|
||||
Loading…
x
Reference in New Issue
Block a user