LGES-OC5-PtoP-Welder/docs/분석_PLC210002_기동오알람_260810.md
NohDoyeon241104 e0e5416975 fix: PLC 기동 시 210002 오알람 차단 — Livetimer 초기화 및 모니터 타이머 기동 위치 이동
PLCController.DoActivate() 가 PLC 연결을 열기 전에 _monitorPlcInterfaceMap 을
기동해, 연결 확립 전 LiveStatusUpdate() 가 돌면서
ALARM_DEVICE_PLC_DISCONNECT(210002) 오알람이 매 기동마다 발생했다.

원인
- Livetimer 가 초기화되지 않아 DateTime.MinValue 상태 → (Now - MinValue) 가
  항상 10000ms 를 초과해 워치독이 오판. 임계값 조정으로는 해결 불가.
- MicroTimer 첫 콜백이 주기(100ms) 뒤가 아니라 Start() 직후(약 1ms)라
  경합이 아니라 100% 재현되는 결정적 버그.

실측 (2026-08-10 16:12:25.904)
  Livetimer=0001-01-01, elapsed=63921975145904ms,
  isLive=False, IsActivated=False, IsConnected=False

조치
- _monitorPlcInterfaceMap.Start() 를 base.DoActivate() 성공 이후로 이동
- Livetimer = DateTime.Now 로 기준시각 확보 후 타이머 기동
- 두 조치는 각각 단독으로는 무효. 반드시 함께 적용해야 한다.
- 수정 전/후를 #region 2개(검색: 260810 기동 오알람)로 기록해 원복 가능

검증: 2026-08-10 현장 재기동 확인 — 기동 시 210002 미발생

미해결(별건): Livetimer 이중 용도, ReadBit .err 무시, ClearAlarm 부재,
SeqAlarmMonitor 의 무지연 루프 및 Laser/Chiller 즉시 알람

상세: docs/분석_PLC210002_기동오알람_260810.md
작업일지: docs/작업일지/2026_08_10.md
2026-08-10 16:37:27 +09:00

11 KiB
Raw Blame History

기동 시 PLC_DISCONNECT(210002) 오알람 분석 — 2026-08-10

1. 증상

프로그램 첫 시작 시, PLC 연결이 열리기도 전에 ALARM_DEVICE_PLC_DISCONNECT(210002)가 뜬다. 연결은 곧 정상적으로 붙지만 알람은 화면에 남는다. 시뮬 모드에서는 발생하지 않는다.


2. 자동 연결 경로

순서 위치 내용
1 Machines/ILSOS_PrismaticRD_PTOP/EquipmentSetup.cs:91 AutomaticallyConnectDevices 확인
2 EquipmentSetup.cs:92 await ActivateDevices()
3 WeldingComponents/WeldingCommon/CommonEquipmentSetup.cs:135-158 디바이스 순차 Activate() — 실패 시 로그만
4 EquipmentSetup.cs:105-107 모니터 시퀀스 3종 Start

await이 걸려 있어 시퀀스 시작 순서 자체는 정상이다(연결 시도 완료 후 모니터 시작). CommonEquipmentSetup.cs:149의 OnPostDeviceActivated는 Activate() 실패해도 호출되지만, PLCBase.APIs.cs:334-341이 E2.NOT_CONNECTED로 조용히 반환하므로 여기서 알람은 발생하지 않는다.


3. 210002를 올리는 지점 — 3곳, 그중 2곳만 살아있다

위치 계층 상태
A WeldingCommon/Devices/PLC/PLCController.Config.cs:146 디바이스 (100ms 타이머) 활성
B Machines/.../Sequences/Monitoring/SeqAlarmMonitor.cs:182 시퀀스 활성
— WeldingCommon/Sequences/Monitoring/AlarmMonitoring.cs:169 시퀀스 (공통) 죽은 코드

AlarmMonitoring.Builder()를 부르는 곳은 SequenceManager.cs:83의 주석 한 줄뿐이다 (CommonSequenceIds.AlarmMonitor 전체 검색 → :51·:52·:83 전부 주석). 등록 0건이므로 주석을 풀어도 동작하지 않는다. B는 이 클래스를 PTOP가 복사해 간 살아있는 사본이다.

두 사본이 갈라진 흔적 (같은 260512 주석, 정반대 방향)

AlarmMonitoring (죽음) SeqAlarmMonitor (살아있음)
TIMEOUT_PLC_LIVE_CHECK 200000 (:52) 2000 (:54)
알람 호출 주석 (:169) 활성 (:182)
루프 지연 Thread.Sleep(100) (:142) 없음 — 빈 return Ok() (:145-148)

A와 B는 판정 기준이 다른데 알람 코드가 같다

A (PLCController) B (SeqAlarmMonitor)
판정 엣지 — live 비트가 바뀌어야 정상 레벨 — 2초 안에 true면 정상
임계 10000ms 2000ms
코드 AlarmCode.ALARM_DEVICE_PLC_DISCONNECT AlarmCode_PTOP....(상속)

AlarmCode_PTOP : AlarmCode(AlarmCode_PTOP.cs:10)이고 210002는 재정의되지 않았으므로 동일 코드다. → 화면에 210002가 떠도 어느 워치독이 올렸는지 구분할 수단이 없다. (§6의 진단 계측 사유)


4. A의 결함 분석 (PLCController.Config.cs:122-150)

 33  DateTime Livetimer;                              // 초기화 없음 = DateTime.MinValue
124      if (IsSimul) return;                         // 시뮬 미발생
129      if (updateLiveWait) {                        //   [에코 대기]
130          if (Now - Livetimer > 1000) { updateLiveWait = false;
132              WriteBit(PC_LIVE_STATUS, beforeLiveStatus); }
134      } else {                                     //   [감시]
135          bool isLive = ReadBit(PLC_LIVE_STATUS).data;
137          if (isLive != beforeLiveStatus) {
138              Livetimer = Now; beforeLiveStatus = isLive; updateLiveWait = true; }
144          if (Now - Livetimer > 10000)
146              AlarmManager.Alarm(ALARM_DEVICE_PLC_DISCONNECT);

① 임계값 조정(10000 → 20000)은 원리적으로 무효. Livetimer 대입 지점은 :138 하나뿐이고 토글 감지 시에만 실행된다. 기동 직후에는 토글이 없어 Livetimer = DateTime.MinValue → Now - MinValue ≈ 2026년 ≈ 6.4×10¹⁰ ms. 임계값을 얼마로 올리든 비교 결과는 항상 true. 임계값 문제가 아니라 초기화 누락이다.

② 리셋이 타이머보다 뒤에 있다.

185  _monitorPlcInterfaceMap.Start();     ← 타이머 시작(100ms 주기)
187  base.DoActivate(config);             ← 실제 PLC Open + 버퍼 생성 + 4회 Read + ManualDataUpdate
190  LoadMemoryMapFromFiles();            ← CSV 재파싱
195  beforeLiveStatus = false;            ← 리셋은 여기 (Livetimer는 아예 빠져 있음)

:187~:190 구간이 100ms를 넘으면 그 사이 tick이 발생한다. 그 구간에서는 PLCBase.APIs.cs:44-51에 따라 ReadBit이 (E2.NOT_READY, false)를 반환하므로 isLive가 항상 false → :137 토글 감지 영구 실패 → :146이 확정적으로 실행된다. ([추론] Aurora Device.Activate()가 IsActivated를 DoActivate 반환 뒤에 세운다는 전제. 바이너리라 미확인)

③ Livetimer 한 변수가 두 의미를 겸한다. :130은 에코 지연 1초 타이머, :144는 워치독 기준시각. updateLiveWait == true인 1초 동안 워치독 판정이 스킵된다.

④ ReadBit의 .err를 버린다. :135가 .data만 취해 "PLC가 하트비트를 안 준다"와 "PC가 아직 못 읽는다"가 둘 다 false로 뭉개진다.

⑤ 자동 해제가 없다. AlarmManager.cs:79-104의 Alarm()은 중복 억제만 하고 짝이 되는 ClearAlarm 호출이 없어, 통신 복구 후에도 210002가 남는다.


5. 수정 방향 (2026-08-10 적용 완료 — §7 참조)

타이머 이동만으로도, Livetimer 초기화만으로도 부족하다. ①과 ②가 독립된 결함이라 한쪽만 고치면 다른 쪽이 남는다. 두 줄이 같은 블록에 있어야 한다.

protected override int DoActivate(IDeviceConfig config)
{
    IsLogModePLCDetail = false;
    _monitorPlcInterfaceMap.UpdateRateUS = 100 * 1000;   // 설정만 남기고 Start() 제거

    var res = base.DoActivate(config);
    LoadMemoryMapFromFiles();
    if (res != E1.OK) return res;                        // 실패 시 타이머 안 돎

    beforeLiveStatus = false;
    updateLiveWait   = false;
    Livetimer = DateTime.Now;                            // ← 추가: 기준시각 확보
    _monitorPlcInterfaceMap.Start();                     // ← 이동: 기준 확보 뒤 기동
    return res;
}

beforeLiveStatus = false 기준값은 그대로 둬도 된다 — 실제 비트가 true면 첫 tick에서 토글로 잡혀 Livetimer가 갱신되고, false면 초기화된 기준으로 10초 유예가 걸려 양쪽 다 안전하다.

부수 효과: Activate 실패 시 타이머가 계속 도는 문제, [추론] LoadMemoryMapFromFiles() 중 타이머가 같은 맵을 읽는 겹침이 함께 사라진다.

이 수정으로 해결되지 않는 것: ③·④·⑤, 그리고 B(SeqAlarmMonitor.cs:182)의 2초 레벨 워치독. B가 같이 올리고 있다면 A만 고쳐서는 증상이 없어지지 않는다 → 그래서 §6을 먼저 한다.


6. 진단 계측 (2026-08-10 적용, 임시)

목적: 기동 시 210002가 A에서 오는지 B에서 오는지 확정한다.

파일 위치 내용
WeldingCommon/Devices/PLC/PLCController.Config.cs 필드부 / LiveStatusUpdate 알람 직전 [진단210002][A:PLCController.LiveStatusUpdate]
Machines/.../Sequences/Monitoring/SeqAlarmMonitor.cs 필드부 / CheckAlarmPLC 알람 직전 [진단210002][B:SeqAlarmMonitor.CheckAlarmPLC]
  • 두 곳 모두 최초 1회만 기록한다(_diagLoggedPlcDisconnect / _DiagLoggedPlcDisconnect 플래그). 100ms·무지연 루프라 플래그가 없으면 로그가 폭주한다.
  • 알람 동작 자체는 손대지 않았다 — 발생 경로만 식별한다.

계측 결과 — 2026-08-10 16:12 현장 로그 (원인 확정)

16:12:25.903  MicroTimer: timer interval: 100 [ms]          ← 타이머 기동
16:12:25.904  E PLCController: [210002][A:PLCController.LiveStatusUpdate]
              Livetimer=0001-01-01 00:00:00.000,
              elapsed=63921975145904ms,
              isLive=False, beforeLiveStatus=False,
              IsActivated=False, IsConnected=False
항목 값 확정된 사실
Livetimer 0001-01-01 DateTime.MinValue — §4-① 미초기화 확정
elapsed 63,921,975,145,904 ms (≈2026년) 임계값 조정 무효 확정
IsActivated·IsConnected 둘 다 False §4-② 연결 확립 전 발생 확정. IsActivated가 DoActivate 반환 뒤에 세팅된다는 [추론]도 함께 검증됨
isLive = beforeLiveStatus 둘 다 False 토글 미감지 → :138 미실행 → Livetimer 영구 MinValue
[B:...] 로그 미출현 시퀀스 계층(SeqAlarmMonitor)은 이 건과 무관

추가 확정 — 타이머 첫 콜백은 즉시 발생한다. 기동 로그 .903 → 알람 .904, 즉 약 1ms. MicroTimer는 첫 tick을 주기(100ms) 뒤가 아니라 Start() 직후에 쏜다. 따라서 §4-②에 달았던 "base.DoActivate()가 100ms를 넘으면"이라는 조건은 불필요하다 — Start() 직후 무조건 1회 발생하는 결정적 버그이며 경합이 아니다.

이로써 §5의 두 조치가 각각 단독으로는 무효임이 확정된다:

  • Livetimer 초기화만(:195) → 타이머는 :185에서 이미 즉시 발화 → 알람
  • Start() 이동만 → 이동 지점에서 즉시 발화, Livetimer는 여전히 MinValue → 알람

로그 검색 시 주의. 로그 파일에서 한글이 깨진다([진단210002] → [ 210002], 위치: → ġ:). 진단210002 로는 안 잡히므로 210002][ 로 검색할 것.

원복

2026-08-10 원복 완료. 두 파일의 #region [진단계측 2026-08-10] 블록 4개를 삭제했고 .cs 전체에서 잔존 0건을 확인했다. (검색에 걸리는 [진단계측 2026-08-04] 는 Recipe Save 건으로 별개다.)


7. 적용 내역 (2026-08-10)

대상: WeldingComponents/WeldingCommon/Devices/PLC/PLCController.Config.cs 1개 파일, DoActivate() 1개 메서드. SeqAlarmMonitor.cs 등 다른 파일은 미변경.

수정 전/후를 #region 2개로 기록해 두었다 (검색: 260810 기동 오알람).

region 위치 내용
... [수정 전] :185-191 원래 _monitorPlcInterfaceMap.Start(); 를 주석으로 보존
... [수정 후] :204-216 Livetimer = DateTime.Now; + _monitorPlcInterfaceMap.Start();

원복 절차: [수정 후] region 을 삭제하고 [수정 전] region 의 Start() 주석을 해제한다.

검증 결과 — 2026-08-10 현장 확인 완료

빌드 후 재기동 → 기동 시 210002 미발생 확인. 수정 유효.

이번 수정으로 바뀌지 않은 것

10000 임계값, 알람 발생 로직, §4-③(Livetimer 이중 용도)·④(.err 무시)·⑤(ClearAlarm 부재)는 그대로다. 확정된 원인(§4-①②)만 차단하는 최소 수정이다.

8. 잔여 과제

  • §4-③④⑤ — LiveStatusUpdate 재작성이 필요해 범위가 커짐. 별건으로 판단
  • SeqAlarmMonitor.cs — WaitTime 무지연 루프(:145-148), Laser/Chiller 즉시 알람(grace 없음), :190-193 _Laser null 시 NRE 위험, InitDev 1회 실행. 이번 건과 무관하나 미해결
  • AlarmMonitoring.cs — 죽은 코드. 정리 대상
  • Light 디바이스 SerialPort.Open() 실패 (16:12:25.875 E EquipmentSetup: Light connect fail.) — 본 건과 무관, 별도 확인 필요