# 작업지시서 — Laser Interlock (레시피별 Front/Down 방향 발진 차단) - 작성일: 2026-06-24 - 대상 브랜치: `field/ptop-recipe-scanner-move` - 작업 분류: 기능 추가 (레시피별 발진 차단 인터락) — 기존 전역 안전 인터락은 **유지** - ⚠️ 본 문서는 **수정안(검토용)** 입니다. 아직 소스는 변경하지 않았습니다. 작업 PC에서 본 지시서대로 적용하세요. --- ## 1. 목적 레이저 인터락은 **2단계**로 동작하며, 둘 다 **Setting의 `Use Interlock`(HeadThetaInterlockEnabled)가 ON일 때만** 평가한다. (OFF면 인터락 전체 미적용 = 정상 발진) - **① Use Interlock = 안전 구역 게이트 (기존, 유지)** - 헤드 각도 θ(D70510)가 Front 또는 Down 각도의 **±Tolerance 안에 있을 때만 발진 허용**, 그 외(어중간/위험 위치)면 **차단**. - 모터가 위험한 위치에서 발진하는 것을 막는 안전장치. → **기존 로직 그대로 유지.** - **② Recipe 체크박스 = 레시피별 방향 발진 차단 (신규)** - 이번에 추가. 모터상 발진해도 되는 안전 구역이라도, **이 레시피에서는 이 방향이면 일부러 차단**. - 예) Recipe#1에서 DOWN 체크 → Down 각도(예: 180° ±2.5°) 구간은 안전 구역이지만 이 레시피에서는 발진 차단. ### 발진 가부 진리표 (Use ON 기준) | 상황 | ① Use 안전구역 | ② Recipe 차단 | 결과 | |---|---|---|---| | θ가 Front/Down 어느쪽도 아님 | 차단 | – | **발진 X** | | θ가 Front 구역, Recipe Front 미체크 | 통과 | 통과 | **발진 O** | | θ가 Front 구역, Recipe Front 체크 | 통과 | 차단 | **발진 X** | | θ가 Down 구역, Recipe Down 체크 | 통과 | 차단 | **발진 X** | | (Use OFF) | – | – | **발진 O (인터락 미적용)** | ## 2. 기존 코드 현황 (분석 결과) ### 2-1. Setting (그대로 활용) `Machines/ILSOS_PrismaticRD_PTOP/Settings/LaserInterlockSetting.cs` | 프로퍼티 | DisplayName | 기본값 | 본 작업에서의 역할 | |---|---|---|---| | `HeadThetaInterlockEnabled` | Use Interlock | false | **마스터 ON/OFF 스위치** — OFF면 ①·② 전체 미동작. ON이면 ① 안전 게이트 + ② 레시피 차단 모두 평가 | | `HeadThetaFront` | Front Angle (deg) | 0 | Front 방향 기준 각도 | | `HeadThetaDown` | Down Angle (deg) | 90 | Down 방향 기준 각도 | | `HeadThetaTolerance` | Tolerance (±deg) | 2.5 | 오차허용값 | | `HeadThetaPlcScale` | PLC Scale (deg×?) | 10 | `thetaDeg = D70510 / scale` | → **각도/Tolerance/Scale/마스터 ON은 Setting에서 읽고**, **방향별 체크(Front/Down on·off)는 레시피 json에서 읽는다.** ### 2-2. 기존 전역 안전 인터락 로직 (→ 유지, 위에 ② 추가) `Machines/ILSOS_PrismaticRD_PTOP/Sequences/SeqAutoRunPTOP.cs:752~782` (`ExecuteWelding` 내부) - 현재 로직: `HeadThetaInterlockEnabled`가 켜지면 **"Front 또는 Down 부근일 때만 발진 허용, 둘 다 아니면 차단"** = ① 안전 게이트. - 이 로직은 **그대로 유지**한다. 그 위에 ② 레시피별 방향 차단을 **추가**한다(2-4). - ⚠️ Manual 경로(`SeqComponentTeachingRun`)에는 ① 안전 게이트조차 **현재 없음** → ①+② 모두 신규 추가(4-E). ### 2-3. 각도 offset 선택은 별개 (건드리지 않음) `LaserAngleOffsetSelector.Select(...)` (Auto: `SeqAutoRunPTOP.cs:640`, Manual: `SeqComponentTeachingRun.cs:507`) 는 발진 **위치 offset**(Front/Down offset) 선택 용도로, 본 인터락(발진 가부)과 **무관**. 그대로 둔다. ### 2-4. 판정 규칙 (Auto/Manual 공통) ``` // Use Interlock(HeadThetaInterlockEnabled) 가 false 면 인터락 미적용 (정상 발진) if (!setting.HeadThetaInterlockEnabled) → 통과(발진 O) // PLC 미연결/미활성 → fail-closed (차단) if (PLC == null || !PLC.IsActivated) → 차단(알람) thetaDeg = ReadWord(HEAD_THETA_POSITION) / (scale!=0 ? scale : 1) nearFront = Math.Abs(thetaDeg - HeadThetaFront) <= HeadThetaTolerance nearDown = Math.Abs(thetaDeg - HeadThetaDown ) <= HeadThetaTolerance // ① 안전 구역 게이트 (기존): 안전 구역(Front/Down 부근) 아니면 차단 if (!nearFront && !nearDown) → 차단(알람) // ② 레시피별 방향 차단 (신규): 체크된 방향의 구역이면 차단 // 레시피 체크값 로드 (Recipe_Laser_Interlock.json): frontChecked=(Front==1), downChecked=(Down==1) if ((frontChecked && nearFront) || (downChecked && nearDown)) → 차단(알람) // 위 어디에도 안 걸리면 발진 O ``` - 차단 시: `AlarmManager.Alarm(ALARM_LASER_HEAD_THETA_INTERLOCK)` + `return null`(발진 차단 & 시퀀스 중단). ## 3. 데이터 저장 구조 (신규) ### 3-1. 파일 위치 - 레시피 폴더마다 1개: `_config_recipe/<레시피>/Recipe_Laser_Interlock.json` - 경로 산출은 기존 `FieldHelper.json` 과 **동일 패턴** 사용 — 활성 레시피 파일 경로의 파일명만 치환. - 참조: `WeldingComponents/LASA/GUI/Page/MainPage.cs:431,538` ```csharp string newpath = fullpath.Replace(Path.GetFileName(fullpath), "Recipe_Laser_Interlock.json"); ``` - `fullpath` = 활성 레시피 파일 경로(예: `DB.Dao.SystemRecipe.WorkRecipeFile`). - ⚠️ **적용 시 확인**: `Recipe_Laser_Interlock.json` 이 `FieldHelper.json` 과 **같은 레시피 폴더**에 떨어지는지 1회 검증(같은 `fullpath` 소스를 쓰면 보장됨). ### 3-2. 파일 포맷 ```json { "Laser Interlock": { "Front": 0, "Down": 1 } } ``` - `0` = 미체크(해당 방향 인터락 해제), `1` = 체크(해당 방향에서 발진 차단). - 파일이 없으면 기본값 `{ Front:0, Down:0 }` (= 차단 없음)으로 간주하고, 최초 저장 시 생성. ## 4. 작업 범위 및 변경 파일 ### 4-A. (신규) 레시피 인터락 모델 + 입출력 헬퍼 신규 파일 1개. 위치: `WeldingComponents/WeldingCommon/Settings/` 또는 `.../Recipes/` (프로젝트 컨벤션에 맞춰). ```csharp // RecipeLaserInterlock.cs (가칭) public class RecipeLaserInterlock { public bool Front { get; set; } // json 1=true / 0=false public bool Down { get; set; } // "Laser Interlock" 래퍼 키로 직렬화: { "Laser Interlock": { "Front":0, "Down":1 } } // → Newtonsoft 사용 시 래퍼 DTO 또는 JObject로 Front/Down 정수(0/1) 매핑. public static RecipeLaserInterlock Load(string recipeFilePath); // 파일 없으면 기본(false,false) public static void Save(string recipeFilePath, RecipeLaserInterlock value); } ``` - 직렬화 시 bool ↔ 0/1 정수 변환 주의(요청 포맷이 정수 0/1). - 경로 인자는 **레시피 파일 경로**를 받아 내부에서 파일명 치환(3-1). ### 4-B. (UI) `UCScannerObjectTeachingControlBox.xaml` `` 내부 그리드에 **Row 추가** 후, "Run Welding/Stop" 버튼(현재 Grid.Row 2) **하단**에 신규 그룹박스 배치. 1. 내부 그리드 `Grid.RowDefinitions` 에 `` **1개 추가**(현재 3행 → 4행). 2. 신규 그룹박스(예: `Grid.Row="3" Grid.ColumnSpan="2"`): ```xml ``` - Content 명/스타일은 기존 그룹박스 스타일(`StGroupBox4th`)·텍스트 색(`#FFBBBBBB`) 관례에 맞춤. ### 4-C. (UI VM) `UCScannerObjectTeachingControlBoxViewModel.cs` 1. 프로퍼티 2개 추가 — 변경 시 즉시 레시피 json 저장: ```csharp private bool _IsFrontInterlock; public bool IsFrontInterlock { get => _IsFrontInterlock; set { SetProperty(ref _IsFrontInterlock, value); SaveLaserInterlock(); } } private bool _IsDownInterlock; public bool IsDownInterlock { get => _IsDownInterlock; set { SetProperty(ref _IsDownInterlock, value); SaveLaserInterlock(); } } private void SaveLaserInterlock() => RecipeLaserInterlock.Save(RecipeFilePath(), new RecipeLaserInterlock { Front = _IsFrontInterlock, Down = _IsDownInterlock }); ``` 2. 로드 시점: - 생성자/`InitVariable()` 에서 현재 레시피값 로드. - **레시피 전환 반영**: 기존 `SYS_SETTING_CHANGED` 구독(`OnSettingReloaded`)과 동일하게, 활성 레시피 변경 이벤트에서 다시 `Load` 해 체크 상태 갱신. (레시피 활성 이벤트는 `RecipeManager` 활성 알림 사용 — 4-D의 sequence와 동일 소스) - 저장 중 stale 덮어쓰기 방지: 로드로 인한 set 시에는 저장 억제 플래그 사용(기존 패턴과 동일하게 가드). 3. `RecipeFilePath()` 는 `FieldHelper.json` 과 동일 소스(활성 레시피 파일 경로) 반환. ### 4-D. (Auto 시퀀스) `SeqAutoRunPTOP.cs` `ExecuteWelding` 내 **기존 블록 752~782** 를 아래로 **확장**(① 안전 게이트 유지 + ② 레시피 차단 추가): ```csharp var thetaSetting = Application.Current.AuroraEnv().SettingEntry[SettingIds.LaserInterlock] as LaserInterlockSetting; if (thetaSetting != null && thetaSetting.HeadThetaInterlockEnabled) { bool blocked = false; if (_PLC == null || !_PLC.IsActivated) { Log.W("[ExecuteWelding] LaserInterlock - PLC 미연결/미활성 → 발진 차단(fail-closed)"); blocked = true; } else { short raw = _PLC.ReadWord(eWordInput.HEAD_THETA_POSITION).data; double scale = thetaSetting.HeadThetaPlcScale != 0 ? thetaSetting.HeadThetaPlcScale : 1; double thetaDeg = raw / scale; bool nearFront = Math.Abs(thetaDeg - thetaSetting.HeadThetaFront) <= thetaSetting.HeadThetaTolerance; bool nearDown = Math.Abs(thetaDeg - thetaSetting.HeadThetaDown ) <= thetaSetting.HeadThetaTolerance; // ① 안전 게이트(기존): 안전 구역 아니면 차단 if (!nearFront && !nearDown) { Log.W($"[ExecuteWelding] ① 안전구역 벗어남(deg={thetaDeg:F1}) → 발진 차단"); blocked = true; } else { // ② 레시피별 방향 차단(신규) var rl = RecipeLaserInterlock.Load(_SystemParameter.WorkRecipeFile); // = 활성 레시피 파일 경로 if ((rl.Front && nearFront) || (rl.Down && nearDown)) { Log.W($"[ExecuteWelding] ② 레시피 차단 deg={thetaDeg:F1} Front?{rl.Front}/{nearFront} Down?{rl.Down}/{nearDown} → 발진 차단"); blocked = true; } } } if (blocked) { try { AlarmManager.Alarm(AlarmCode_PTOP.ALARM_LASER_HEAD_THETA_INTERLOCK); } catch (Exception ex) { Log.E($"[ExecuteWelding] 알람 표시 실패: {ex.Message}"); } return null; } } ``` - 위치: `_Scanner.SelectCorrectionTable(2)` / Laser Gate ON **직전**(현재 블록과 동일 지점). ### 4-E. (Manual 시퀀스) `SeqComponentTeachingRun.cs` - 현재 Manual 경로에는 인터락 블록이 **없음**(① 안전 게이트조차 없음). `DoComponentWelding` 의 `_Scanner.ExecuteList(_WeldingScannerListNo);`(`:571`) **직전**에 4-D와 동일하게 **①+② 모두 신규 추가**. - 설정 취득: `context.GetSetting(SettingIds.LaserInterlock)` (기존 `:507` 패턴). - PLC 핸들은 `_plc`, 레시피 경로는 `_SystemParameter.WorkRecipeFile`(또는 활성 레시피 파일 경로) 사용. - 차단 시 `return null;`. ## 5. 적용 시 확인 사항 (반드시 검증) 1. **D70510 스케일**: `HeadThetaPlcScale` 기본 10(=deg×10) 가정. 현장 실값으로 `thetaDeg`가 맞는지 1회 확인. 2. **레시피 폴더 일치**: `Recipe_Laser_Interlock.json` 이 `FieldHelper.json` 과 같은 폴더에 생성되는지 확인. 3. **알람 코드**: `AlarmCode_PTOP.ALARM_LASER_HEAD_THETA_INTERLOCK` 가 알람 테이블에 등록되어 있는지(기존 블록도 try/catch로 미등록 대비) 확인. 별도 신규 알람 코드가 필요하면 추가 여부 협의. 4. **레시피 전환 시 체크 상태 갱신**: 레시피를 바꾸면 UI 체크박스가 해당 레시피의 json 값으로 즉시 갱신되는지(stale 방지) 확인. 5. **Use Interlock OFF 동작**: Setting의 Use가 꺼져 있으면 체크 여부와 무관하게 정상 발진되는지 확인. ## 6. 작업 순서 권장 1. 4-A 모델/헬퍼(`RecipeLaserInterlock`) 작성 — 로드/세이브 단위 검증. 2. 4-B/4-C UI + VM — 체크 저장/로드, 레시피 전환 갱신 확인. 3. 4-D Auto 시퀀스 교체. 4. 4-E Manual 시퀀스 추가. 5. 5장 검증 항목 점검 → 작업일지 기록. ## 7. 미해결/협의 항목 - 신규 알람 코드 분리 필요 여부(현재는 기존 `ALARM_LASER_HEAD_THETA_INTERLOCK` 재사용 전제). - `Recipe_Laser_Interlock.json` 의 활성 레시피 경로 소스를 VM/시퀀스에서 동일하게 보장하는 정확한 API(작업 PC에서 `FieldHelper.json` 저장에 쓰는 경로와 일치시킬 것).