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곳).
This commit is contained in:
노도연 2026-09-16 16:21:16 +09:00
parent 8eaf99c8be
commit dc012d9313
2 changed files with 111 additions and 1 deletions

View File

@ -12,6 +12,10 @@ namespace ILSOS_PrismaticRD_PTOP.Alarm
{
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)
{
@ -23,6 +27,19 @@ namespace ILSOS_PrismaticRD_PTOP.Alarm
// High Alarm 은 Sequence 정지
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);
}
}

View File

@ -145,6 +145,18 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences
const int RESYNC_TIMEOUT_SEC = 10;
#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 List<IDrawObject> _SelectedObjectList;
@ -324,7 +336,25 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences
InitSequenceVariable(context);
#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);
_PLC.WriteBit(eBitOutput.PC_AUTO_STATUS, true); // 음극 (양극 P 미사용)
@ -691,6 +721,23 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences
Log.I($"[CheckPageNo] PLC_PAGE_CHANGE_REQ 신호 대기 중...");
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...");
//Thread.Sleep(1000);
@ -1301,9 +1348,30 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences
//Log.I($"[ExecuteWelding] Laser Gate ON (Core+Ring)");
#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 자리)
_IO._iOUnit.WriteDoBit(Convert.ToInt32(ePrismaticDOAddress.Laser_REMOTE), true);
Log.I($"[ExecuteWelding] Laser Gate ON (REMOTE)");
_GateOnTime = DateTime.Now; // [로그계측 2026-09-16] Gate 유지시간 계산용 (원복: 이 줄 삭제)
Thread.Sleep(laserEmissionGateDelayMs); // 260518 매뉴얼 기준 ≥10ms, 여유분 포함 =15ms 적용함.
Log.I($"[ExecuteWelding] Gate ON Delay {laserEmissionGateDelayMs}ms 완료 → ExecuteList 시작");
@ -1327,6 +1395,7 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences
// 260824 본 레이저 단일 발진기 — 제어권 해제 1발
_IO._iOUnit.WriteDoBit(Convert.ToInt32(ePrismaticDOAddress.Laser_REMOTE), false);
Log.I($"[ExecuteWelding] Laser Gate OFF (REMOTE)");
_LastGateOnMs = DateTime.Now.Subtract(_GateOnTime).TotalMilliseconds; // [로그계측 2026-09-16] (원복: 이 줄 삭제)
#endregion
// 260626 Busy bit 위치 변경 - Laser On Display bit
@ -1335,6 +1404,7 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences
Thread.Sleep(50);
_WeldingSpanTime = DateTime.Now.Subtract(weldingTime);
_LastWeldSpan = _WeldingSpanTime; // [로그계측 2026-09-16] 이후 _WeldingSpanTime 은 다른 구간 측정에 재사용된다 (원복: 이 줄 삭제)
Status.GetProcessingWeldingData().WeldingEndTime = DateTime.Now;
@ -1381,6 +1451,29 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences
Log.I($"[CheckCycleStop] 사이클 중지 여부 확인 중");
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 해제 (이후 정상 루프 복귀)
if (ResumeMode)
{