Compare commits

...

2 Commits

Author SHA1 Message Date
03bf31e38a docs: 자동운전 전환 사전점검 분석 (정적분석)
수동출사에서 자동운전 전환 시 레시피/페이지 이동·출사 경로 정적분석. 소스 무수정·런타임 검증 미실시.

결론: 출사 경로 자체는 정상. 다만 종결부 3노드 미실행(파형·카운트·비트리셋), 자동 경로 Door 인터락 부재, 중알람 정지 ID 불일치가 확인됨.

PLC 확인 4문항(PAGE_COMPLETE 감시 / Door 차단 보장 / REQ OFF 보장 / Resume REQ 재발행) 포함.
2026-09-16 16:24:06 +09:00
dc012d9313 feat: 자동운전 관측용 로그계측 추가 (동작 무변경)
자동 전환 전 판단 근거 확보를 위한 로그만 추가. 분기/리턴/비트쓰기 무수정.

- [Enter] 활성 레시피 페이지별 객체 수 1회 스캔 (빈 레이어 사전 경고)

- [CheckPageNo] REQ 진입 시점 ON 여부 (레벨/엣지 전환 판단 근거)

- [DoorCond] 출사 직전 Door 판정 기록 전용 - 차단하지 않음

- [사이클요약] 레시피/페이지/객체수/용접ms/GateON ms/PAGE_CMP/Resume 1줄

- [중알람] H알람 정지 대상 ID 불일치 경고 (StopSequence 호출은 그대로)

Door 차단과 정지 ID 수정은 극성/알람목록 확인 후 별건으로 진행한다. 원복: 마커 [로그계측 2026-09-16] 전수 검색 후 삭제 (2파일 10곳).
2026-09-16 16:21:16 +09:00
3 changed files with 447 additions and 1 deletions

View File

@ -0,0 +1,336 @@
# 자동운전 전환 사전 점검 — 레시피/페이지 이동 및 출사 경로 분석
- 작성일: 2026-09-16
- 대상: `SeqAutoRunPTOP` (Machines/ILSOS_PrismaticRD_PTOP/Sequences/SeqAutoRunPTOP.cs)
- 배경: 현장에서 현재 **수동 출사**로 운영 중. PLC 비트 조건에 따른 **자동 운전**으로 전환했을 때
시퀀스가 레시피/페이지를 스스로 이동해 정상 출사하는지 확인 요청.
- 방법: **정적 분석 전용. 소스 무수정, 런타임 검증 미실시.**
런타임으로 확인한 항목은 하나도 없으며, 모든 판정은 코드 경로 추적 결과다.
---
## 1. 결론 요약
**출사 자체는 간다.** 레시피 전환 → 페이지 전환 → 레이어 선택 → 리스트 업로드 → 발진까지
경로가 끊긴 곳은 없고, 260806~260818 에 들어간 에코 수렴 로직이 레시피/페이지 정합성을
노드 통과 조건으로 잡아주고 있다.
**다만 사이클 종결부가 통째로 실행되지 않는다.** 용접은 되는데 파형 저장·생산 카운트·출력비트
리셋이 영구히 빠진다. 그리고 **자동 경로에는 Door 인터락이 없고, 중알람이 자동운전을 멈추지 못한다.**
| 등급 | 항목 | 영향 |
|---|---|---|
| 치명 | 사이클 종결부 3노드 영구 미실행 | 파형 미저장 · 생산카운트 미증가 · 출력비트 미리셋 |
| 높음 | 자동 경로에 Door 인터락 없음 | 문 열림 상태에서도 PC는 출사를 막지 않음 |
| 높음 | 중알람이 자동운전을 정지시키지 못함 | 정지 대상 시퀀스 ID 불일치 |
| 중 | REQ/TRIGGER 가 레벨 판정(엣지 아님) | 이론상 같은 페이지 재출사 가능 |
| 중 | 빈 페이지 만나면 자동운전 정지 | 객체 0개 레이어에서 시퀀스 중단 |
| 중 | bit15 Resume 핸드셰이크 스킵 미구현 | Resume 후 PLC 새 REQ 없으면 교착 |
| 낮음 | 디버그 토스트가 매 사이클 표시 | 운전 중 화면 노이즈 |
| 낮음 | 진단계측 로그 잔존 | MC 개별읽기 16회/초, 로그 증가 |
---
## 2. 자동 사이클 흐름 (실제 코드 경로)
`Builder()` 의 Flows 정의 기준 (SeqAutoRunPTOP.cs:211-253).
```
Init
→ Sequence_Enter :293 PC_AUTO_STATUS=ON, 레시피 핸들 확보
→ CheckInitCondition :350 ★ 항상 RESYNC 로만 분기 (아래 6-C)
→ ResyncRecipePage :598 재기동 시 PLC 워드로 Recipe/Page 선(先) 확정
→ CheckPageNo :689 PLC_PAGE_CHANGE_REQ(0x08) ON 대기 ◄── 사이클 진입점
→ CheckRecipeNoComp :475 Recipe 수렴: 읽기→ActivateRecipe→Layers reload→에코→되읽기 확인
→ CheckPageNoComp :953 Page 수렴: 읽기→_CurrentPageNo 갱신→에코→되읽기 확인
+ Recipe/Page 동시 최종확인 (어긋나면 Recipe부터 재확정)
→ SetUploadWeldingList_Params :1095 Layers[_CurrentPageNo] 의 객체를 용접 대상으로 선택
→ (SeqUploadWeldingList) :181 RTC 리스트 생성 (서브시퀀스)
→ GetUploadWeldingList_Result :1159
→ WeldingStartReadBit :1181 PLC_WELD_TRIGGER_REQ ON 대기
→ PreScoutCheck :1412 M70509 ON 이면 Scout 실행, OFF 면 스킵
→ WaitWeldingStart :1221 PC_WELD_TRIGGER_ACK=ON, PC_WELD_COMPLETE=OFF
→ ExecuteWelding :1244 HeadTheta 인터락 → Gate ON → ExecuteList → Gate OFF
→ PostScoutCheck :1430
→ WriteWeldComplete :1464 PC_WELD_TRIGGER_ACK=OFF, PC_WELD_COMPLETE=ON
→ CheckCycleStop :1379 ★ 무조건 CheckPageNo 로 회귀
┊
┊ (아래 3노드는 도달하지 않음)
┄ SaveResultData :1478
┄ WriteCompletionPLCBit :1512
┄ Complete :1530
```
---
## 3. 정상 동작이 확인된 부분
### 3-1. 레시피 이동
`CheckRecipeNoComp` (:475-579) 가 **워드만 맞추고 끝내지 않는다.**
`PLC_RECIPE_NO` 읽기 → 등록 레시피 조회 → `ActivateRecipe` → `WaitRecipeActivated` 로 반영 대기
→ `RecipeHelper.OnActiveReloaded()` 를 **UI 스레드에서 동기 호출**해 `Model.Layers` 갱신 보장
→ `PC_RECIPE_NO` 에코 → 되읽기로 수렴 확인(:565-567) 후에만 다음 노드로 간다.
미등록 레시피나 0 이면 **에코하지 않고 재시도**(:516-533)한다. 에코가 안 나가면 PLC 게이트가
열리지 않으므로 잘못된 레시피로 출사가 나갈 수 없다. 설계가 맞다.
### 3-2. 페이지 이동
`CheckPageNoComp` (:953-1086) 도 같은 구조다. 추가로 두 가지가 좋다.
- 페이지 상한을 **활성 레시피의 레이어 수로 가변 검증**(:998). 하드코딩이 없어 레시피별 2~5페이지에 대응한다.
- **Recipe/Page 동시 최종확인**(:1035-1057). Page 수렴 중 PLC가 Recipe를 바꿔버린 260812 케이스를
잡기 위해, Page 확정 후 두 워드를 다시 읽어 둘 다 일치할 때만 통과한다. 어긋나면 Recipe부터 재확정한다.
### 3-3. 페이지 → 실제 용접 레이어 연결
`_CurrentPageNo = p - 1` (:1009) 로 1-base→0-base 변환 후,
`SetUploadWeldingList_SeqParams` 가 `Model.Layers[_CurrentPageNo]` 의 객체를 선택한다(:1110-1111).
Layers 접근을 UI 스레드로 직렬화(:1105)해 레시피 reload 와의 race 를 막아뒀다. 정합한다.
### 3-4. 출사 게이트
`ExecuteWelding` (:1244-1377): HeadTheta 인터락 판정 → `Laser_REMOTE` DO ON → 15ms 대기 →
`ExecuteList` → `WaitForListTaskCompleted` → `Laser_REMOTE` OFF → `JumpAbs(0,0)` 원점 복귀.
게이트 ON/OFF 짝이 맞고, 정지 시에도 `OnSeqActionStop` (:1786) 에서 OFF 를 보장한다.
> **정정**: `WaitForListTaskCompleted(1000)` 의 인자 단위는 **초**다
> (`ScannerUnit.cs:1095` `public void WaitForListTaskCompleted(int timeOut) //단위는 초`).
> 즉 1000초로, 긴 용접이 조기 절단될 위험은 없다. 이전에 "1초 하드코딩"으로 기록해 둔 메모가 있다면 잘못이다.
> 다만 초과 시 `throw`(:1104) 하며 `ExecuteWelding` 에 try/catch 가 없다.
---
## 4. 치명 — 사이클 종결부 3노드 영구 미실행
### 근거
```csharp
// SeqAutoRunPTOP.cs:1379-1394
private string CheckCycleStop(...)
{
if (ResumeMode) { ... }
Log.I($"[CheckCycleStop] 사이클 계속 진행 → CheckPageNo 이동");
return SEQ_CHECK_PAGENO; // ← 분기 조건 없음. 무조건 이 한 줄.
}
```
`SEQ_SAVE_RESULT_DATA` / `SEQ_WRITE_COMPLETION_PLC_BIT` / `SEQ_COMPLETE` 로 가는 경로는
Flows 상 존재하지만(:242-244) 실행 시 선택되지 않는다.
### 자동 전환 시 실제로 빠지는 것
| 미실행 | 위치 | 결과 |
|---|---|---|
| `SaveScannerRecordFileForamt()` | :1484 → :1497 | **스캐너 파형(`SaveRecord("AutoRunTDC")`) 이 한 번도 저장되지 않음** |
| 생산 카운트 증가 | :1541-1542 | Total/Today 생산수량이 자동운전에서 절대 늘지 않음 |
| `RestoreDeviceStatus()` | :1518 → :1564 | PC 출력비트 일괄 리셋 미수행 |
| `KEY_SEQUENCE_RESULT` 기록 | :1487 | 사이클 성공 여부가 남지 않음 |
### 가장 주의할 부수효과 — `PC_PAGE_CHANGE_COMPLETE` 가 계속 ON
`CheckPageNoComp` 가 매 사이클 이 비트를 true 로 쓴다(:1076). 이 비트를 false 로 내리는 곳은
`RestoreDeviceStatus`(:1568) 와 `OnSeqActionStop`(:1723) 둘뿐인데, 전자는 도달하지 않는다.
→ **자동운전 중에는 첫 페이지 전환 이후 계속 ON 상태로 latch 된다.**
소스 주석(:1069, :1742)은 "PLC 는 이 비트를 보지 않는다(260806 확인)"라고 기록하고 있다.
그 전제가 맞으면 무해하고, 틀리면 2번째 페이지부터 PLC 가 전환 완료를 인지하지 못한다.
**→ PLC 확인 항목 Q1.**
---
## 5. 높음 — 안전/정지 계통
### 5-A. 자동 경로에 Door 인터락이 없다
`AlarmManager.IsDoorOpen()` 호출처를 전수 조사한 결과 **수동 경로 2곳뿐**이다.
- `SeqComponentTeachingRun.cs:285` (시퀀스 진입 전)
- `SeqComponentTeachingRun.cs:624` (출사 직전 최종 게이트)
- `UCScannerLaserControlViewModel.cs:295/387/516` (메뉴얼 직접 출사)
`SeqAutoRunPTOP.cs` 전체에 `IsDoorOpen` / `Door` 검사가 **0건**이다. `ExecuteWelding`(:1244-1377)
어디에도 없다. 즉 **자동 운전 중 문이 열려도 PC 는 출사를 막지 않는다.**
260902 Door 인터락 작업(ISSUE-260902-01)은 수동 출사 경로만 대상이었으므로 이는 누락이 아니라
범위 밖이었던 것이다. 다만 자동 전환 시에는 차단 책임이 **전적으로 PLC/하드와이어**에 있다.
**→ PLC 확인 항목 Q2.**
### 5-B. 중알람이 자동운전을 멈추지 못한다 (ISSUE-260828-02, 현재 소스에서 재확인)
```csharp
// Alarm/AlarmManager_PTOP.cs:25-27
if (alarm.Level == AlarmLevel.H) {
StopSequence(SequenceIds.SeqAutoRun); // ← 구 Prismatic 시퀀스
}
```
실제 운전 중인 것은 `SeqAutoRunPTOP` 다(`UCMainAutoRunViewModel.cs:101, 365`).
등록도 두 ID 가 따로 되어 있다(`EquipmentSetup.cs:218` vs `:225`).
→ **H 레벨 중알람이 떠도 자동운전은 계속 돈다.** 식별자 1개 교체로 해결되는 사안이지만 미수정 상태다.
추가로 `ExecuteWelding` 에 알람 검사 자체가 없어, 알람 상태에서도 발진 경로가 열려 있다.
### 5-C. 참고 — 수동 정지는 정상
`OnStopSequenceAutoRun`(:449-467) 은 `_SeqAutoRunPTOP.Stop()` 을 정확히 호출하고,
`OnSeqActionStop` 에서 PC 출력비트 OFF + `Laser_REMOTE` OFF 까지 수행한다. 이쪽은 문제없다.
---
## 6. 중 — 동작 정합성
### 6-A. REQ / TRIGGER 가 레벨 판정이다 (엣지 아님)
```csharp
// CheckPageNo :726
if (_PLC.ReadBit(eBitInput.PLC_PAGE_CHANGE_REQ).data) { ... break; }
// WeldingStartReadBit :1195
if (_PLC.ReadBit(eBitInput.PLC_WELD_TRIGGER_REQ).data) { ... break; }
```
두 대기 노드 모두 "ON 이면 통과"이지 상승엣지를 보지 않는다.
`CheckCycleStop` 이 `CheckPageNo` 로 루프백할 때 PLC 가 아직 REQ 를 내리지 않았다면
**같은 페이지를 다시 읽어 에코하고, TRIGGER_REQ 도 ON 상태면 같은 페이지를 한 번 더 용접한다.**
실제로는 260826 진단계측에서 "성공 구간에는 REQ 가 OFF 로 떨어진다"고 관찰된 바 있어
PLC 가 비트를 내려주고 있는 것으로 보인다. 즉 **현재 안전은 PLC 타이밍에 의존**하고 있고,
PC 쪽에는 중복 방지 장치가 없다. **→ PLC 확인 항목 Q3.**
### 6-B. 빈 페이지를 만나면 자동운전이 멈춘다
```csharp
// SetUploadWeldingList_SeqParams :1122-1126
if (_SelectedObjectList == null || _SelectedObjectList.Count() == 0) {
Log.E("[SetUploadWeldingList] No welding objects found! Sequence will fail.");
return null; // ← 시퀀스 중단
}
```
객체가 하나도 없는 레이어를 PLC 가 지정하면 그 자리에서 자동운전이 끝난다(재시작 필요).
전환 전에 **운용 대상 레시피 전 페이지의 객체 수가 0 이 아닌지** 확인해 두는 편이 안전하다.
### 6-C. bit15 Resume 핸드셰이크 스킵이 미구현 (ISSUE-260805-01, 재확인)
```csharp
// CheckInitCondition :350-365 — 분기 조건 없이
return SEQ_RESYNC_RECIPE_PAGE;
```
Builder 에 정의된 Resume 스킵 경로
`sb.Flows(sb[SEQ_CHECK_INIT_CONDITION] > sb[SEQ_SET_PARAMS_UPLOAD_WELDING_LIST_SEQ]); // :252`
는 **선택되지 않는 dead code** 다.
`ResumeMode=true` 여도 `ResyncRecipePage`(:600) 에서 재동기만 건너뛸 뿐, 결국 `CheckPageNo` 의
**PLC 새 REQ 대기로 들어간다.** PLC 가 Resume 후 REQ 를 다시 주지 않으면 교착이다.
자동 전환 후 bit15(가변 용접) 운용을 함께 쓸 계획이면 이 항목이 먼저 정리돼야 한다.
### 6-D. 참고 — HeadTheta 인터락은 체크박스 의존
`ExecuteWelding :1253` 의 `thetaSetting.HeadThetaInterlockEnabled` 가 꺼져 있으면
각도 판정 전체가 스킵된다. 자동 전환 전 설정값 확인 필요(fail-closed 는 활성화된 경우에만 적용).
---
## 7. 낮음 — 운전 품질
### 7-A. 디버그 토스트가 매 사이클 뜬다
- `Sequence_Enter :298` — `MessageUtil.ShowToast("Sequence_Enter")` (기동 시 1회)
- `WeldingStartReadBit :1200` — `MessageUtil.ShowToast("PLC_WELD_TRIGGER_REQ : ON")` **(매 사이클)**
둘 다 `// 디버그 끝내고 삭제` 주석이 달린 채 살아 있다. 연속 자동운전 시 화면에 계속 뜬다.
### 7-B. 진단계측 로그 잔존
`CheckPageNo` 의 `[진단계측 2026-08-26]` region(:701-715, :752-765)이 **1초 주기로
`BuildHandshakeSnapshot()`** 을 호출한다. 이 헬퍼는 **MC 개별읽기 16회**(:788-804)다.
page 대기가 길어지는 구간에서 PLC 통신 부하와 로그량이 함께 늘어난다.
원복 표기가 주석에 있으니 자동 안정화 후 제거 대상.
### 7-C. 정지 시 레시피 롤백
`OnStopSequenceAutoRun :462` 에서 `RecipeHelper.RollbackRecipe()` 를 호출한다.
자동 정지 후 활성 레시피가 직전 선택 레시피로 되돌아가므로, 정지 직후 화면 레시피가
PLC 지시와 달라 보일 수 있다. 동작 자체는 의도된 것으로 보이나 현장 혼선 여지가 있어 기록해 둔다.
---
## 8. PLC 확인 요청 항목
| # | 질문 | 왜 필요한가 |
|---|---|---|
| Q1 | `PC_PAGE_CHANGE_COMPLETE`(M70008)를 PLC 가 보는가? 본다면 엣지인가 레벨인가? | 자동운전 중 이 비트가 계속 ON 으로 latch 된다(4장). 엣지를 본다면 2페이지째부터 전환 인지 실패 |
| Q2 | 자동 운전 중 Door Open 시 레이저 차단은 PLC/하드와이어에서 보장되는가? | PC 측 자동 경로에 Door 검사가 전혀 없다(5-A) |
| Q3 | `PLC_PAGE_CHANGE_REQ` / `PLC_WELD_TRIGGER_REQ` 는 PC 응답 수신 후 반드시 OFF 로 내리는가? | PC 가 레벨 판정이라 OFF 가 보장돼야 중복 출사가 없다(6-A) |
| Q4 | bit15 Resume 후 PLC 가 새 PAGE_CHANGE_REQ 를 재발행하는가? | 재발행이 없으면 Resume 교착(6-C) |
---
## 9. 자동 전환 전 권고 순서
1. **Q1~Q4 를 PLC 측과 먼저 정리.** 특히 Q2(Door)는 안전 항목이라 선행돼야 한다.
2. **운용 레시피 전 페이지의 객체 수 확인** — 빈 레이어가 있으면 그 페이지에서 멈춘다(6-B).
3. **HeadTheta 인터락 체크박스 상태 확인**(6-D).
4. **첫 전환은 1사이클 단위로** — `[CheckCycleStop] 사이클 계속 진행` 로그가 뜨는 것을 확인하고,
그 뒤로 `[SaveResultData]` / `[Complete]` 가 **안 뜨는 것이 현재 정상**임을 인지한 상태로 관찰.
5. 파형 데이터와 생산 카운트가 필요하면 **4장 종결부 문제를 먼저 조치**해야 한다(현재 ISSUE-260828-01 보류 중).
6. bit15 가변 용접을 병행할 계획이면 **6-C 선행**.
---
## 10. 로그로 보는 판정 기준
정상 1사이클이면 이 순서로 찍힌다.
```
[CheckPageNo] PLC_PAGE_CHANGE_REQ 신호 대기 중...
---> [CheckPageNo] Check OK -> PLC_PAGE_CHANGE_REQ : ON
[CheckRecipeNoComp] PLC_RECIPE_NO 수렴 시작
---> [CheckRecipeNoComp] 수렴 완료 (N회차) - PC_RECIPE_NO: [xx]
[CheckPageNoComp] PLC_PAGE_NO 수렴 시작
---> [CheckPageNoComp] 수렴 완료 (N회차) - PC_PAGE_NO: [x], PC_Index: [x-1]
---> [CheckPageNoComp] Recipe/Page 최종 확인 완료 (1회차)
[SetUploadWeldingList] Selected PageNo [x-1] → Layer Name: [...], Object Count: [N]
---> [WeldingStartReadBit] PLC_WELD_TRIGGER_REQ ON 감지
[ExecuteWelding] Laser Gate ON (REMOTE)
[ExecuteWelding] WaitForListTaskCompleted 완료
[ExecuteWelding] Laser Gate OFF (REMOTE)
[WriteWeldComplete] 용접완료 송신 - COMPLETE=true
[CheckCycleStop] 사이클 계속 진행 → CheckPageNo 이동
```
주의해서 볼 로그:
| 로그 | 의미 |
|---|---|
| `[CheckPageNoComp] 최종 확인 불일치` 반복 | PLC 가 확정 후 Recipe/Page 를 계속 바꾸는 중 (260812 패턴) |
| `[CheckRecipeNoComp] 미수렴` 반복 | 에코가 PLC 에 반영되지 않음 — 통신 또는 워드 맵 확인 |
| `Invalid PageNo: N (활성 Recipe 페이지 수: M)` | PLC 지시 페이지가 레시피 레이어 수를 초과 |
| `No welding objects found!` | 빈 페이지 — 여기서 자동운전 종료(6-B) |
| `[진단계측][CheckPageNo] 변화` 가 길게 이어짐 | REQ 대기 교착 — 스냅샷의 PAGE/REQ 값으로 PLC 스텝 확인 |
| `Object Count: [0]` | 빈 레이어 경고 신호 |
---
## 11. 근거 위치 정리
| 항목 | 파일:라인 |
|---|---|
| 노드 등록 / Flows | SeqAutoRunPTOP.cs:172-253 |
| CheckInitCondition 무조건 분기 | SeqAutoRunPTOP.cs:350-365 |
| CheckRecipeNoComp (에코 수렴형) | SeqAutoRunPTOP.cs:475-579 |
| ResyncRecipePage | SeqAutoRunPTOP.cs:598-668 |
| CheckPageNo (REQ 레벨 대기) | SeqAutoRunPTOP.cs:689-777 |
| 진단계측 스냅샷 헬퍼 | SeqAutoRunPTOP.cs:779-825 |
| CheckPageNoComp (수렴 + 동시 최종확인) | SeqAutoRunPTOP.cs:953-1086 |
| 페이지 → 레이어 선택 | SeqAutoRunPTOP.cs:1095-1126 |
| WeldingStartReadBit (TRIGGER 레벨 대기) | SeqAutoRunPTOP.cs:1181-1218 |
| ExecuteWelding (HeadTheta + Gate) | SeqAutoRunPTOP.cs:1244-1377 |
| CheckCycleStop 무조건 루프백 | SeqAutoRunPTOP.cs:1379-1394 |
| 미도달 3노드 | SeqAutoRunPTOP.cs:1478-1562 |
| RestoreDeviceStatus | SeqAutoRunPTOP.cs:1564-1580 |
| OnSeqActionStop | SeqAutoRunPTOP.cs:1713-1800 |
| 중알람 정지 대상 ID | Alarm/AlarmManager_PTOP.cs:20-28 |
| 시퀀스 등록 (SeqAutoRun vs PTOP) | EquipmentSetup.cs:218, 225 |
| AutoRun 시작/정지/Resume | Views/Main/UCMainAutoRunViewModel.cs:242-342, 449-467 |
| PLC_AUTO_REQ 상승엣지 → 시작 이벤트 | Devices/PLC/PLCController.Config.cs:125-131 |
| WaitForListTaskCompleted 단위(초) | LASA/GUI/Scanner/ScannerUnit.cs:1095-1107 |
| Door 인터락 (수동 경로에만 존재) | SeqComponentTeachingRun.cs:285, 624 |
---
## 12. 관련 이슈
| 이슈번호 | 관계 |
|---|---|
| ISSUE-260828-01 | 본 문서 4장과 동일 건 (종결부 3노드). 현재 보류 |
| ISSUE-260828-02 | 본 문서 5-B 와 동일 건 (중알람 정지 ID). 현재 OPEN |
| ISSUE-260805-01 | 본 문서 6-C 와 동일 건 (bit15 Resume). 현재 OPEN |
| ISSUE-260820-01 | 6-A 와 인접 (REQ 선행 / 지연). PLC 회신 대기 |
| ISSUE-260826-01 | 6-A·6-C 와 인접 (page5→6 REQ 미발행). PLC 문의 발송 |
| ISSUE-260902-01 | 5-A 의 배경 (Door 인터락은 수동 경로 한정 적용) |
---
*본 문서는 정적 분석 결과이며 소스를 수정하지 않았다. 런타임 검증은 수행하지 않았다.*

View File

@ -12,6 +12,10 @@ namespace ILSOS_PrismaticRD_PTOP.Alarm
{ {
public class AlarmManager_PTOP : AlarmManager public class AlarmManager_PTOP : AlarmManager
{ {
// [로그계측 2026-09-16] 기반 클래스의 AlarmLog 는 private 이라 파생 클래스에서 쓸 수 없어 별도로 둔다.
// (원복: 아래 region 과 함께 이 필드도 삭제)
private static readonly Logger AlarmLogPTOP = new Logger("ALARM");
public AlarmManager_PTOP(IAuroraEnvironment aurora) : base(aurora) public AlarmManager_PTOP(IAuroraEnvironment aurora) : base(aurora)
{ {
@ -23,6 +27,19 @@ namespace ILSOS_PrismaticRD_PTOP.Alarm
// High Alarm 은 Sequence 정지 // High Alarm 은 Sequence 정지
if (alarm.Level == AlarmLevel.H) { if (alarm.Level == AlarmLevel.H) {
#region [로그계측 2026-09-16] 정지 대상 ID 불일치 경고 (원복: 이 region 삭제)
// 아래 StopSequence 의 대상은 SeqAutoRun(구 Prismatic)인데, 실제 운전 중인 것은
// SeqAutoRunPTOP 이다 (EquipmentSetup.cs:218 vs :225 / UCMainAutoRunViewModel.cs:101, 365).
// → H 알람이 떠도 자동운전은 멈추지 않는다.
// 식별자 1개 교체로 해결되지만, 지금까지 안 멈추던 것이 멈추기 시작하면 오정지로 라인이 설 수
// 있고 IO 알람이 무력화(ISSUE-260828-03)되어 실제 발생 H 알람 목록이 확인되지 않았다.
// 그래서 동작은 그대로 두고, 어떤 H 알람이 실제로 뜨는지 목록만 먼저 확보한다. (ISSUE-260828-02)
AlarmLogPTOP.W($"[로그계측][중알람] H 알람 발생 - Code:{code}, Name:{alarm.Name} → " +
$"StopSequence 대상이 '{SequenceIds.SeqAutoRun}' 이라 " +
$"실제 운전 중인 '{SequenceIds.SeqAutoRunPTOP}' 는 정지하지 않는다 " +
$"(ISSUE-260828-02 / 현재 동작 무변경)");
#endregion
StopSequence(SequenceIds.SeqAutoRun); StopSequence(SequenceIds.SeqAutoRun);
} }
} }

View File

@ -145,6 +145,18 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences
const int RESYNC_TIMEOUT_SEC = 10; const int RESYNC_TIMEOUT_SEC = 10;
#endregion #endregion
#region [로그계측 2026-09-16] 자동운전 관측용 필드
// 원복: 이 파일과 Alarm/AlarmManager_PTOP.cs 에서 "[로그계측 2026-09-16]" 표시가 붙은 것을 전부 삭제하면 된다
// (이 region 포함, 현재 두 파일 합쳐 10곳 — region 5개 + 단일 라인 5개).
// 자동 전환 사전점검(Doc/인수인계/260916_자동운전_전환_사전점검.md)의 관측 항목을 남기기 위한 값들이다.
// 전부 기록 전용이며 분기·리턴·비트쓰기 어디에도 관여하지 않는다.
private static long _CycleCount = 0; // Enter 이후 누적 사이클(=페이지 용접) 수
private TimeSpan _LastWeldSpan; // 직전 ExecuteList 소요(실제 용접 구간)
private double _LastGateOnMs; // 직전 Laser Gate ON→OFF 유지 시간
private DateTime _GateOnTime; // Gate ON 시각(위 계산용)
private bool _ReqAlreadyOnAtEntry; // CheckPageNo 진입 시 REQ 가 이미 ON 이었는가
#endregion
private eListNo _WeldingScannerListNo; private eListNo _WeldingScannerListNo;
private List<IDrawObject> _SelectedObjectList; private List<IDrawObject> _SelectedObjectList;
@ -324,7 +336,25 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences
InitSequenceVariable(context); InitSequenceVariable(context);
#endregion #endregion
// 장비 체크 #region [로그계측 2026-09-16] 활성 레시피 페이지별 객체 수 1회 스캔 (원복: 이 region 삭제)
// 객체가 0개인 레이어를 PLC 가 지정하면 SetUploadWeldingList_SeqParams 가 시퀀스를 중단한다.
// 그 사실을 운전이 멈춘 뒤가 아니라 시작 시점에 알 수 있도록 페이지별 객체 수를 한 번만 남긴다.
// 읽기 전용이며, 조회가 실패해도 계측만 건너뛰고 운전은 그대로 진행한다.
_CycleCount = 0;
try
{
string pageSummary = Application.Current.Dispatcher.Invoke(() =>
string.Join(", ", _ScannerCanvasUserCtrl.Model.Layers
.Select((layer, idx) => $"P{idx + 1}:{layer.Objects.Count()}")));
Log.I($"[로그계측][Enter] 활성 레시피 페이지별 객체 수 — {pageSummary} (0 인 페이지는 그 페이지 지시 시 운전이 멈춘다)");
}
catch (Exception ex)
{
Log.W($"[로그계측][Enter] 페이지별 객체 수 조회 실패({ex.GetType().Name}: {ex.Message}) — 계측만 생략, 운전에는 영향 없음");
}
#endregion
// 장비 체크
InitDev(context); InitDev(context);
_PLC.WriteBit(eBitOutput.PC_AUTO_STATUS, true); // 음극 (양극 P 미사용) _PLC.WriteBit(eBitOutput.PC_AUTO_STATUS, true); // 음극 (양극 P 미사용)
@ -691,6 +721,23 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences
Log.I($"[CheckPageNo] PLC_PAGE_CHANGE_REQ 신호 대기 중..."); Log.I($"[CheckPageNo] PLC_PAGE_CHANGE_REQ 신호 대기 중...");
Status.SetWorkingPosition(eWorkingPosition.LOADING_CELL); Status.SetWorkingPosition(eWorkingPosition.LOADING_CELL);
#region [로그계측 2026-09-16] REQ 진입 시점 상태 (원복: 이 region 삭제)
// 이 노드는 REQ 가 ON 이면 통과하는 레벨 판정이다(아래 :726). 상승엣지를 보지 않으므로,
// CheckCycleStop 루프백 시점에 PLC 가 아직 REQ 를 내리지 않았다면 대기 없이 즉시 통과한다.
// 그 경우 같은 페이지를 다시 확정·용접할 수 있어, 엣지 판정 전환이 필요한지 가리기 위해
// 진입 시점 값을 남긴다. 판정은 하지 않고 기록만 한다.
_ReqAlreadyOnAtEntry = false;
if (_PLC != null)
{
var reqAtEntry = _PLC.ReadBit(eBitInput.PLC_PAGE_CHANGE_REQ);
_ReqAlreadyOnAtEntry = reqAtEntry.err == Hermes.E1.OK && reqAtEntry.data;
if (_ReqAlreadyOnAtEntry)
Log.W($"[로그계측][CheckPageNo] 진입 시점에 REQ 가 이미 ON — 대기 없이 통과 (직전 사이클 #{_CycleCount})");
else
Log.I($"[로그계측][CheckPageNo] 진입 시점 REQ OFF — 정상 대기 진입 (직전 사이클 #{_CycleCount})");
}
#endregion
// 디버그 끝내고 삭제 // 디버그 끝내고 삭제
//MessageUtil.ShowToast("CheckPageNo -> Wait..."); //MessageUtil.ShowToast("CheckPageNo -> Wait...");
//Thread.Sleep(1000); //Thread.Sleep(1000);
@ -1301,9 +1348,30 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences
//Log.I($"[ExecuteWelding] Laser Gate ON (Core+Ring)"); //Log.I($"[ExecuteWelding] Laser Gate ON (Core+Ring)");
#endregion #endregion
#region [로그계측 2026-09-16] 자동 출사 직전 Door 상태 (원복: 이 region 삭제)
// ★ 기록 전용이다. 차단하지 않는다.
// 자동 경로에는 Door 인터락이 없다(수동 경로에만 존재 — SeqComponentTeachingRun:285/624,
// UCScannerLaserControlViewModel:295/387). 그렇다고 여기서 바로 차단을 넣으면,
// Door 비트 극성이 실장비 미검증(ISSUE-260902-01 체크리스트 7번)이라 극성이 반대일 경우
// 자동운전이 전면 차단된다. 먼저 판정값만 쌓아 극성을 확인한 뒤 차단 여부를 결정한다.
// IsDoorOpen 은 80ms TTL 캐시(AlarmManager.cs:304)라 사이클당 1회 호출은 IO 부담이 없고,
// caller 키가 분리돼 있어 수동 경로의 상태 전이 로그와 섞이지 않는다.
try
{
bool doorOpen = AlarmManager.IsDoorOpen("AutoRunExecuteWelding");
Log.I($"[로그계측][DoorCond] 자동 출사 직전 판정 — DoorOpen:{doorOpen} / {AlarmManager.GetDoorStateText()} " +
$"(Recipe:{plcRecipeNo}, Page:{plcPageNo}, 사이클 #{_CycleCount + 1}) ※ 기록 전용 — 차단하지 않음");
}
catch (Exception ex)
{
Log.W($"[로그계측][DoorCond] Door 상태 조회 실패({ex.GetType().Name}: {ex.Message}) — 계측만 생략, 출사에는 영향 없음");
}
#endregion
// 260824 본 레이저 단일 발진기 — 제어권 요청 1발 (구 Core/Ring_Control 자리) // 260824 본 레이저 단일 발진기 — 제어권 요청 1발 (구 Core/Ring_Control 자리)
_IO._iOUnit.WriteDoBit(Convert.ToInt32(ePrismaticDOAddress.Laser_REMOTE), true); _IO._iOUnit.WriteDoBit(Convert.ToInt32(ePrismaticDOAddress.Laser_REMOTE), true);
Log.I($"[ExecuteWelding] Laser Gate ON (REMOTE)"); Log.I($"[ExecuteWelding] Laser Gate ON (REMOTE)");
_GateOnTime = DateTime.Now; // [로그계측 2026-09-16] Gate 유지시간 계산용 (원복: 이 줄 삭제)
Thread.Sleep(laserEmissionGateDelayMs); // 260518 매뉴얼 기준 ≥10ms, 여유분 포함 =15ms 적용함. Thread.Sleep(laserEmissionGateDelayMs); // 260518 매뉴얼 기준 ≥10ms, 여유분 포함 =15ms 적용함.
Log.I($"[ExecuteWelding] Gate ON Delay {laserEmissionGateDelayMs}ms 완료 → ExecuteList 시작"); Log.I($"[ExecuteWelding] Gate ON Delay {laserEmissionGateDelayMs}ms 완료 → ExecuteList 시작");
@ -1327,6 +1395,7 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences
// 260824 본 레이저 단일 발진기 — 제어권 해제 1발 // 260824 본 레이저 단일 발진기 — 제어권 해제 1발
_IO._iOUnit.WriteDoBit(Convert.ToInt32(ePrismaticDOAddress.Laser_REMOTE), false); _IO._iOUnit.WriteDoBit(Convert.ToInt32(ePrismaticDOAddress.Laser_REMOTE), false);
Log.I($"[ExecuteWelding] Laser Gate OFF (REMOTE)"); Log.I($"[ExecuteWelding] Laser Gate OFF (REMOTE)");
_LastGateOnMs = DateTime.Now.Subtract(_GateOnTime).TotalMilliseconds; // [로그계측 2026-09-16] (원복: 이 줄 삭제)
#endregion #endregion
// 260626 Busy bit 위치 변경 - Laser On Display bit // 260626 Busy bit 위치 변경 - Laser On Display bit
@ -1335,6 +1404,7 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences
Thread.Sleep(50); Thread.Sleep(50);
_WeldingSpanTime = DateTime.Now.Subtract(weldingTime); _WeldingSpanTime = DateTime.Now.Subtract(weldingTime);
_LastWeldSpan = _WeldingSpanTime; // [로그계측 2026-09-16] 이후 _WeldingSpanTime 은 다른 구간 측정에 재사용된다 (원복: 이 줄 삭제)
Status.GetProcessingWeldingData().WeldingEndTime = DateTime.Now; Status.GetProcessingWeldingData().WeldingEndTime = DateTime.Now;
@ -1381,6 +1451,29 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences
Log.I($"[CheckCycleStop] 사이클 중지 여부 확인 중"); Log.I($"[CheckCycleStop] 사이클 중지 여부 확인 중");
Status.SetWorkingPosition(eWorkingPosition.CHECKING_CYCLE); Status.SetWorkingPosition(eWorkingPosition.CHECKING_CYCLE);
#region [로그계측 2026-09-16] 사이클 요약 1줄 (원복: 이 region 삭제)
// 노드별로 흩어진 값을 한 줄로 모은다. 현장 로그 대조 시간을 줄이기 위한 것이며 동작에 관여하지 않는다.
// PAGE_CMP: CheckPageNoComp 가 매 사이클 ON 으로 쓰지만(:1076) 이를 끄는 RestoreDeviceStatus 는
// 아래 무조건 루프백 때문에 도달하지 않는다. 실제로 계속 ON 으로 latch 되는지 확인용.
// Resume : 아래 ResumeMode 해제 블록보다 앞에 두어야 해제 전 값이 찍힌다.
_CycleCount++;
try
{
int objCount = _SelectedObjectList == null ? -1 : _SelectedObjectList.Count();
var pageCmp = _PLC.ReadOutBit(eBitOutput.PC_PAGE_CHANGE_COMPLETE);
string pageCmpText = pageCmp.err == Hermes.E1.OK ? (pageCmp.data ? "ON" : "OFF") : "E";
Log.I($"[로그계측][사이클요약] #{_CycleCount} Recipe:{plcRecipeNo} Page:{plcPageNo}(Idx:{_CurrentPageNo}) " +
$"객체:{objCount} 용접:{_LastWeldSpan.TotalMilliseconds:F0}ms GateON:{_LastGateOnMs:F0}ms " +
$"PAGE_CMP:{pageCmpText} REQ진입시ON:{(_ReqAlreadyOnAtEntry ? "Y" : "N")} " +
$"Resume:{(ResumeMode ? "1" : "0")}");
}
catch (Exception ex)
{
Log.W($"[로그계측][사이클요약] 요약 로그 실패({ex.GetType().Name}: {ex.Message}) — 계측만 생략, 운전에는 영향 없음");
}
#endregion
// 260621: Resume 사이클 1회 완료 → 출력 bit15 OFF + ResumeMode 해제 (이후 정상 루프 복귀) // 260621: Resume 사이클 1회 완료 → 출력 bit15 OFF + ResumeMode 해제 (이후 정상 루프 복귀)
if (ResumeMode) if (ResumeMode)
{ {