13 KiB
작업지시서 — 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,538string 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) 하단에 신규 그룹박스 배치.
- 내부 그리드
Grid.RowDefinitions에<RowDefinition Height="auto"/>1개 추가(현재 3행 → 4행). - 신규 그룹박스(예:
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
- 프로퍼티 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 });
- 로드 시점:
- 생성자/
InitVariable()에서 현재 레시피값 로드. - 레시피 전환 반영: 기존
SYS_SETTING_CHANGED구독(OnSettingReloaded)과 동일하게, 활성 레시피 변경 이벤트에서 다시Load해 체크 상태 갱신. (레시피 활성 이벤트는RecipeManager활성 알림 사용 — 4-D의 sequence와 동일 소스) - 저장 중 stale 덮어쓰기 방지: 로드로 인한 set 시에는 저장 억제 플래그 사용(기존 패턴과 동일하게 가드).
- 생성자/
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. 적용 시 확인 사항 (반드시 검증)
- D70510 스케일:
HeadThetaPlcScale기본 10(=deg×10) 가정. 현장 실값으로thetaDeg가 맞는지 1회 확인. - 레시피 폴더 일치:
Recipe_Laser_Interlock.json이FieldHelper.json과 같은 폴더에 생성되는지 확인. - 알람 코드:
AlarmCode_PTOP.ALARM_LASER_HEAD_THETA_INTERLOCK가 알람 테이블에 등록되어 있는지(기존 블록도 try/catch로 미등록 대비) 확인. 별도 신규 알람 코드가 필요하면 추가 여부 협의. - 레시피 전환 시 체크 상태 갱신: 레시피를 바꾸면 UI 체크박스가 해당 레시피의 json 값으로 즉시 갱신되는지(stale 방지) 확인.
- Use Interlock OFF 동작: Setting의 Use가 꺼져 있으면 체크 여부와 무관하게 정상 발진되는지 확인.
6. 작업 순서 권장
- 4-A 모델/헬퍼(
RecipeLaserInterlock) 작성 — 로드/세이브 단위 검증. - 4-B/4-C UI + VM — 체크 저장/로드, 레시피 전환 갱신 확인.
- 4-D Auto 시퀀스 교체.
- 4-E Manual 시퀀스 추가.
- 5장 검증 항목 점검 → 작업일지 기록.
7. 미해결/협의 항목
- 신규 알람 코드 분리 필요 여부(현재는 기존
ALARM_LASER_HEAD_THETA_INTERLOCK재사용 전제). Recipe_Laser_Interlock.json의 활성 레시피 경로 소스를 VM/시퀀스에서 동일하게 보장하는 정확한 API(작업 PC에서FieldHelper.json저장에 쓰는 경로와 일치시킬 것).