← 전체 글
태그/약 12분 읽기/ 조회

작업자가 믿기 전에 멈춘 SCADA 값을 잡아내는 법

SCADA 화면은 장비가 멈춘 뒤에도 그럴듯한 숫자를 태연히 보여준다. 하트비트, 와치독, stale-data 태그로 데이터의 신선도를 눈에 보이게 만드는 방법 — 실제 프로토콜 품질, 현실적인 타임아웃, 그리고 그걸 증명하는 시운전 절차까지.

SCADA태그문제 해결네트워킹체크리스트

내가 겪은 최악의 통신 장애는 흡입 압력이 4.2 bar로 잔잔한 초록색을 유지하고 있었는데, 사실 트랜스미터가 20분째 오프라인이었던 경우다. 마지막으로 폴링한 값이 그대로 멈춰 있었다. 운전자가 의심할 이유가 없었다 — 색은 정상, 트렌드는 평평, 화면 어디에도 "이 숫자는 죽었다"는 표시가 없었다. 하트비트 태그는 바로 이 실패 모드를 죽이려고 존재한다: 살아있는 것처럼 보이는 얼어붙은 값.

하트비트는 원격 시스템이 주기적으로 바꾸는 값이고, 와치독은 그 변화가 멈추면 알람을 내는 로직이다. 이 둘은 원시 값이 답하지 못하는 질문에 답한다: 이 숫자는 지금 값인가, 아니면 시체를 보고 있는가?

프로토콜이 알아서 알려주지 않는다

첫 번째 실수는 드라이버가 이미 이걸 처리해 준다고 가정하는 것이다. 프로토콜에 따라 크게 다르고, 그 차이가 중요하다.

Modbus에는 데이터 품질이 없다. 전혀. Read Holding Registers는 16-bit word를 돌려주거나 타임아웃이 나거나 둘 중 하나다. TCP 소켓은 살아 있는데 PLC 태스크가 멈추면, 마지막 값이 계속 돌아오고 드라이버는 "good"이라고 보고한다. 장비가 Modbus를 쓴다면 하트비트를 반드시 추가해야 한다 — 프로토콜은 로직이 죽었다는 걸 절대 알려주지 않는다.

DNP3(IEEE 1815)는 포인트마다 flag를 싣는다. 그리고 이건 실제로 유용하다. 모든 static point는 flags octet에 ONLINE(bit 0), RESTART(bit 1), COMM_LOST(bit 2)를 담는다. 마스터가 이걸 노출하면, 포인트가 COMM_LOST가 되거나 ONLINE을 잃는 것은 진짜 품질 신호다 — 다만 이것도 아웃스테이션까지의 링크를 말할 뿐, 아웃스테이션 하위 로직이 도는지는 말하지 않는다.

OPC UA는 값마다 StatusCode와 두 개의 timestamp를 준다. Good0x00000000이고, 신경 써야 할 것은 Bad_NoCommunication(0x80310000), UncertainLastUsableValue(0x40900000), Bad_OutOfService다. 그만큼 중요한 것: 모든 DataValue는 source timestamp와 server timestamp를 담는다. source timestamp를 트렌드하라. 값이 Good인데 source timestamp가 30초째 안 움직인다면, 서버가 stale 샘플을 성실히 재발행하고 있는 것이다 — 좋은 StatusCode, 죽은 데이터. Good만 믿고 멈추는 사람들이 자주 걸리는 함정이다.

정리하면 계층적 진실은 이렇다: 링크 살아 있음 ≠ 폴링 성공 ≠ 컨트롤러 실행 중 ≠ 값이 신선함. 이걸 전부 하나의 초록 "Comms OK" 램프로 뭉치지 마라. 장애 중에 기술자는 어느 계층이 깨졌는지 알아야 하는데, 단일 boolean은 그 정보를 버린다.

실제로 배포하는 태그 세트

원격 컨트롤러, 스키드, 게이트웨이, 엣지 노드마다 단일 플래그가 아니라 작은 블록을 만든다:

태그담는 값알람
Heartbeat_Raw장비에서 그대로 온 카운터, 비트, timestamp직접 알람 안 함
Heartbeat_Age_sHeartbeat_Raw가 마지막으로 바뀐 뒤 경과 초설정 한계 초과 시 알람
Poll_OK드라이버/프로토콜 상태(StatusCode, DNP3 flag)false일 때 이벤트/알람
Data_Stale그래픽과 알람 로직이 참조하는 파생 boolean종속 프로세스 알람 억제
Last_Good_Update마지막으로 받아들인 변화의 timestamp진단 팝업에 표시

값을 하는 건 Heartbeat_Age_s다. 원시 카운터에 알람을 거는 건 쓸모없다 — 기대값을 알아야 하니까. 경과 시간에 알람을 걸면 자명하고 팝업에 자연스럽게 읽힌다: "마지막 정상 갱신 14분 전"이면 프로세스 문제냐 통신 문제냐 하는 논쟁이 끝난다.

원시 하트비트 자체에 대한 주의: wrap을 조심하고, 증가가 아니라 변화를 감지하라. UINT16 카운터는 65535 → 0으로 넘어가고, INT1632767 → −32768로 넘어간다. stale 로직이 new > old라면 wrap마다 오알람이 난다. 올바른 판정은 new != old에 age 타이머를 더한 것이다. 토글 비트는 별도 가드가 필요하다 — true에 영구히 붙은 비트는 "0이냐 1이냐"만 보면 정상 비트와 똑같아 보이므로, 창 안에서 실제로 전이했는지 확인해야 한다.

평범한 PLC-SCADA 링크라면, 증가하는 unsigned integer에 드라이버가 노출하는 품질 태그를 얹은 것이 든든한 기본이다. Sparkplug B라면 일부는 공짜로 얻는다 — bdSeq와 MQTT Last Will의 NDEATH/DDEATH가 진짜 사망 진단서를 준다 — 하지만 그 위에 age 체크는 여전히 필요하다.

타임아웃은 템플릿이 아니라 프로세스에서 정한다

stale 한계는 갱신 주기와 틀렸을 때의 비용을 따라간다. 전 사이트에 맞는 단일 숫자는 없다.

  • 플랜트 LAN의 로컬 PLC, 1초 하트비트 → 5~10초에 stale. 유용할 만큼 빡빡하되, 스캔 한 번 빠지는 건 넘길 만큼 여유.
  • 셀룰러 텔레메트리, 30초 발행 → 초가 아니라 발행 2~3회 놓침에 stale. 절대 시간이 아니라 "놓친 주기"로 설정한다.
  • 배치/페이즈 인터페이스 → 페이즈 사이의 정상 최대 침묵 시간에 맞춘다. 안 그러면 hold 스텝마다 nuisance 알람.
  • 히스토리안 컬렉터, 5초 스캔 → 서비스 재시작 때 모든 태그를 stale로 찍지 않을 만큼 여유.

틀리는 방식은 대칭이다. 너무 빡빡하면 정상 네트워크 지터가 늑대소년이 되고, 결국 운전자가 알람을 음소거한다 — 그 순간 태그는 쓸모없는 것보다 나쁘다. 무시하도록 훈련시켰으니까. 너무 느슨하면 몇 분 동안 죽은 데이터로 플랜트를 운전한다. 애매하면 nuisance 쪽의 느슨한 값에서 시작해서, 스펙 시트가 아니라 실제 지터를 보며 시운전 중에 조인다.

"stale"이 알람만 내지 말고 그림을 바꾸게 하라

리스트에 묻힌 stale 알람은 놓치기 쉽다. stale 상태는 값이 그려지는 방식을 눈에 띄게 바꿔야 한다:

  • stale 아날로그 값은 별도 품질 스타일 — 해칭, "?" 배지, 회색 텍스트 — 을 쓰고 정상 초록은 절대 아니다. 실은 실패한 읽기인 저압 값이 진짜 저압 트립과 똑같아 보이면 안 된다.
  • stale 측정에 의존하는 프로세스 알람을 억제하거나 한정하라. 안 그러면 0을 읽는 죽은 트랜스미터가 유령 low-low를 내고 운전자가 현장에서 헛수고한다.
  • 통신/stale 알람은 프로세스 알람과 별도 그룹으로 둬서 네트워크 이벤트가 진짜 프로세스 리스트를 덮지 않게 한다.
  • Heartbeat_Raw, Heartbeat_Age_s, StatusCode, Last_Good_Update를 진단 팝업에 올려라. 이걸 전부 엔지니어링 도구 뒤에 숨기면 장애마다 누군가 SCADA 서버에 RDP로 들어가는 것으로 시작한다.

인수인계 전에 증명하라

코드를 읽는 건 증명이 아니다. 각 조건을 강제로 만들고 HMI를 지켜봐라:

  1. 하트비트를 손으로 한 스텝 올리고 SCADA가 움직임을 보는지, Heartbeat_Age_s가 리셋되는지 확인.
  2. 네트워크 경로는 살린 채 하트비트를 얼린다 — 이게 Modbus가 혼자 못 잡는 경우이고, 그래서 가장 중요한 경우다. Data_Stale이 예상 시간 안에 서는지 확인.
  3. 네트워크를 뽑고 드라이버 레벨 품질(Poll_OK, StatusCode)이 바뀌는지, 위의 얼어붙은 로직 케이스와 구분되는지 확인.
  4. 종속 프로세스 알람이 실제로 억제되거나 표시되는지, 아직도 울리고 있지 않은지 확인.
  5. 이벤트 로그가 stale 전이 복구를 모두 기록하는지 확인 — 장애 지속 시간을 계산하려면 두 timestamp가 다 필요하다.
  6. PLC 재시작, SCADA 서비스 재시작, 네트워크 재연결 뒤에 반복한다. wrap 버그와 stuck-bit 버그는 재시작에서 드러난다.

정상 케이스, stale 케이스, 복구 케이스 각각의 이벤트 export를 하나씩 남겨라. 다음 엔지니어가 "진단이 정말 테스트된 적 있냐"고 물을 때 건네줄 게 바로 그 세 개다.