LGES-OC5-PtoP-Welder/docs/작업지시서/2026-06-24_Laser_Interlock_레시피별_Front_Down.md

13 KiB
Raw Blame History

작업지시서 — 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
      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. 파일 포맷

{
  "Laser Interlock": {
    "Front": 0,
    "Down": 1
  }
}
  • 0 = 미체크(해당 방향 인터락 해제), 1 = 체크(해당 방향에서 발진 차단).
  • 파일이 없으면 기본값 { Front:0, Down:0 } (= 차단 없음)으로 간주하고, 최초 저장 시 생성.

4. 작업 범위 및 변경 파일

4-A. (신규) 레시피 인터락 모델 + 입출력 헬퍼

신규 파일 1개. 위치: WeldingComponents/WeldingCommon/Settings/ 또는 .../Recipes/ (프로젝트 컨벤션에 맞춰).

// 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

<GroupBox Header="Laser Shot"> 내부 그리드에 Row 추가 후, "Run Welding/Stop" 버튼(현재 Grid.Row 2) 하단에 신규 그룹박스 배치.

  1. 내부 그리드 Grid.RowDefinitions 에 <RowDefinition Height="auto"/> 1개 추가(현재 3행 → 4행).
  2. 신규 그룹박스(예: Grid.Row="3" Grid.ColumnSpan="2"):
<GroupBox Header="Laser Interlock" Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2"
          Margin="0 5 0 0" Style="{StaticResource StGroupBox4th}">
    <StackPanel>
        <CheckBox Margin="6 4" FontSize="12" FontWeight="Bold" Foreground="#FFBBBBBB"
                  Content="FRONT INTERLOCK" IsChecked="{Binding IsFrontInterlock}"/>
        <CheckBox Margin="6 4" FontSize="12" FontWeight="Bold" Foreground="#FFBBBBBB"
                  Content="DOWN INTERLOCK"  IsChecked="{Binding IsDownInterlock}"/>
    </StackPanel>
</GroupBox>
  • Content 명/스타일은 기존 그룹박스 스타일(StGroupBox4th)·텍스트 색(#FFBBBBBB) 관례에 맞춤.

4-C. (UI VM) UCScannerObjectTeachingControlBoxViewModel.cs

  1. 프로퍼티 2개 추가 — 변경 시 즉시 레시피 json 저장:
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 });
  1. 로드 시점:
    • 생성자/InitVariable() 에서 현재 레시피값 로드.
    • 레시피 전환 반영: 기존 SYS_SETTING_CHANGED 구독(OnSettingReloaded)과 동일하게, 활성 레시피 변경 이벤트에서 다시 Load 해 체크 상태 갱신. (레시피 활성 이벤트는 RecipeManager 활성 알림 사용 — 4-D의 sequence와 동일 소스)
    • 저장 중 stale 덮어쓰기 방지: 로드로 인한 set 시에는 저장 억제 플래그 사용(기존 패턴과 동일하게 가드).
  2. RecipeFilePath() 는 FieldHelper.json 과 동일 소스(활성 레시피 파일 경로) 반환.

4-D. (Auto 시퀀스) SeqAutoRunPTOP.cs

ExecuteWelding 내 기존 블록 752~782 를 아래로 확장(① 안전 게이트 유지 + ② 레시피 차단 추가):

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<LaserInterlockSetting>(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 저장에 쓰는 경로와 일치시킬 것).