접속은 멀쩡한데 아무것도 안 들어오는 클라이언트
보호 엔지니어가 변전소 IED를 하나 넘겨주며 IP와 MMS 포트를 알려준다. 첫 시도에 접속된다. 모델 브라우징도 된다 — PROT/PTOC1.Op.general을 읽을 수 있고, MMXU1 아래 계측값도 폴링된다. 그런데 상태 변화가 오길 기다려도 아무것도 안 온다. 벤치에서 차단기가 트립되는데 SCADA 로그는 텅 비어 있다.
고장난 게 아니다. IED에 접속(association)은 했지만 리포트를 활성화하지 않았고, IEC 61850에서는 그게 전부다. Modbus나 평범한 OPC UA 구독과 달리, 흥미로운 데이터는 접속을 열었다고 흐르지 않는다 — 데이터는 *리포트 컨트롤 블록(Report Control Block)*이 활성화되고, 데이터셋을 가리키고, 내가 원하는 이벤트에 트리거되도록 설정돼야 흐른다. 이 셋 중 하나라도 틀리면 회선은 살아 있는 것처럼 보이면서 아무것도 말해주지 않는다.
폴이 아니라 리포트
IEC 61850-7-2(ACSI, 61850-8-1이 MMS로 매핑)는 리포팅을 로지컬 노드 안에 사는 두 객체로 정의한다 — 거의 항상 LLN0이다:
- URCB — Unbuffered Report Control Block.
- BRCB — Buffered Report Control Block.
둘 다 데이터셋(DATA-SET)을 가리킨다. XCBR1.Pos.stVal, XCBR1.Pos.q, XCBR1.Pos.t 같은 데이터 속성의 순서 있는 목록이다. 그 데이터셋의 멤버가 블록이 감지하라고 지시받은 방식으로 변하면, IED는 리포트를 조립해 클라이언트로 밀어 보낸다. 요청은 없다. 예외 보고(report-by-exception)를 장비 단에서 하는 것이고, DNP3 비요청 응답을 설정해봤다면 개념은 그대로 옮겨온다 — 다만 배관이 더 노골적이고, 그 배관의 더 많은 부분이 당신 책임이다.
두 블록의 차이는 단어 하나이고, 그 단어가 새벽 2시에 중요해진다: buffered.
버퍼링이 실제로 지켜주는 것
URCB는 클라이언트가 접속해 있고 블록을 활성화한 동안에만 리포트한다. 접속이 끊기면 — 무선 순간 장애, 스위치 재부팅, SCADA 프런트엔드 재시작 — 그 공백 동안 발생한 이벤트는 사라진다. 재접속해서 다시 활성화하면 다음 변화는 받지만, 놓친 것은 영영 못 받는다. 몇 초마다 샘플링되는 계측값이라면 대개 괜찮다. 하지만 접속이 끊긴 사이 상태가 바뀐 차단기라면, 이건 누락에 의한 거짓말이다.
BRCB는 내부 이벤트 버퍼를 유지한다. 읽는 클라이언트가 없거나 접속이 끊기면, 자격을 갖춘 이벤트가 각각 EntryID를 찍힌 채 그 버퍼에 쌓인다. 재접속하면 클라이언트는 마지막으로 처리한 EntryID를 읽어 BRCB에 다시 써 넣고, IED는 그 지점 이후 전부를 재전송한다. 통신 두절을 general interrogation 폭주 없이 메우는 방식이 이거다.
함정: 버퍼는 유한하다. 벤더는 깊이를 크게 광고하지 않고, 보통 수백에서 수천 엔트리 정도다. 수다스러운 데이터셋에서 충분히 오래 끊겨 있으면 가장 오래된 엔트리가 밀려 떨어진다. 그러면 IED는 다음 리포트 헤더에 BufOvfl 플래그를 세운다. SCADA 드라이버가 이 플래그를 무시하면, 구멍 뚫린 그림을 조용히 믿게 된다 — BufOvfl은 로그 한 줄이 아니라 full general interrogation을 부르는 트리거로 다뤄라.
내가 쓰는 경험칙: 차단기 위치, 보호 동작/픽업, 단로기 상태, 탭 위치 — 천이(transition) 자체가 이벤트인 것은 전부 buffered 데이터셋에 넣는다. 이미 데드밴드 걸어 주기적으로 갱신하는 MMXU 아래 아날로그 계측값은 unbuffered 블록에 둬도 된다. 전부를 하나의 거대한 BRCB에 쏟지 마라 — 버퍼가 필요 없던 데이터에 버퍼 메모리를 내는 셈이다.
리포트가 아예 발생할지 결정하는 세 개의 손잡이
블록이 올바른 데이터셋을 가리키게 하고 나면, 세 설정이 동작을 결정한다. 내가 디버깅한 리포팅 문제는 전부 이 셋 중 하나로 귀결됐다.
TrgOps — 트리거 옵션. 무엇을 이벤트로 볼지 고르는 비트스트링:
dchg(data-change) — 값이 변함. 상태에는 이걸 주로 쓴다.qchg(quality-change) — 품질이 변함. 예: 어떤 포인트가questionable이나invalid로 감.dupd(data-update) — 값이 동일해도 속성이 갱신됨(재발행된 명령 같은 데 쓰임).period(integrity) — 변화 여부와 무관하게 타이머에 맞춰 데이터셋 전체를 주기적으로 덤프.gi(general-interrogation) — 클라이언트가GI = TRUE를 쓰면 데이터셋 전체를 요청 시 전송.
전형적인 실수: stVal과 q 멤버로 가득한 데이터셋인데 URCB에는 dchg만 켜져 있다. 품질이 invalid로 뒤집혀도 값은 안 변하니 리포트가 없다. 포인트가 나빠지는 걸 신경 쓴다면 — 보호 데이터라면 신경 써야 한다 — qchg도 켜고, q가 실제로 데이터셋 멤버인지 확인하라.
bufTm — 버퍼 시간(ms). 첫 변화 이후 IED가 더 많은 변화를 하나의 리포트로 모으려고 기다리는 시간. 0으로 두면 3상 이벤트가 몇 마이크로초 간격으로 도착하는 세 개의 리포트가 된다. 10–50 ms 정도로 두면 이벤트 전체가 변한 멤버를 모두 담은 리포트 하나로 합쳐진다 — SCADA에 훨씬 부담이 덜하고 "이것들은 같이 변했다"는 묶음도 보존된다. 61850에서 디바운스에 가장 가까운 것이 이거고, 잘 안 쓰인다.
intgPd — 무결성 주기(ms). period가 켜져 있을 때 데이터셋 전체를 재전송하는 간격. URCB에서 상태 변화를 놓쳤을 때의 안전망이다 — 60000(60초) 정도로 두면 dchg 리포트가 유실돼도 그림이 스스로 복구된다. BRCB에서는 버퍼가 이미 커버하므로 무결성 주기가 아예 필요 없는 경우가 많다. 거기서 촘촘한 무결성 주기를 돌리면 대역폭만 낭비다.
예약(reservation) 함정
이건 현장 시간을 가장 많이 잡아먹고 "어제는 됐는데"로 나타난다.
하나의 RCB 인스턴스는 한 번에 클라이언트 하나만 담당한다. 여러 SCADA 마스터 — 주/예비, 또는 SCADA와 엔지니어링 툴 — 를 다루려면 벤더는 여러 복사본을 만들어 둔다: LLN0.RP.urcbStatus01, ...02, ...03 식으로. 각 클라이언트는 자기 고유 인스턴스를 예약하고 활성화해야 한다.
URCB는 클라이언트가 Resv = TRUE를 써서 예약한다. 그 인스턴스는 접속이 끊길 때까지 당신 것이다. BRCB는 ResvTms(Edition 2)를 쓴다 — 양수 값은 그 초 수만큼 블록을 예약하고, -1은 해제 전까지 영구 예약이다. 예약의 요점은 다른 클라이언트가 당신의 버퍼 위치를 밑에서 빼앗지 못하게 하는 것이다.
어긋나는 지점: 두 클라이언트가 같은 인스턴스 번호로 설정된 경우. 첫 번째가 잡고, 두 번째가 접속해 예약된 것을 발견하면 아무것도 못 받는다 — 오류 대화상자도 없이 그냥 침묵. 또는 이중화 SCADA 쌍이 둘 다 urcbStatus01을 잡으려다 failover마다 예약을 앞뒤로 흔들어, 양쪽이 간헐적으로 리포트를 잃는다. 해법은 지루하지만 필수다: SCL에서 모든 클라이언트에 각자의 RCB 인스턴스를 주고, 적어둬라 — IED는 경고해주지 않는다.
ConfRev: 클라이언트가 멀쩡한 데이터를 거부하게 만드는 숫자
모든 RCB는 ConfRev를 지닌다. 담당 데이터셋의 구성 개정 카운터다. 누군가 데이터셋을 편집하면 — 포인트 추가, 삭제, 멤버 순서 변경 — 제대로 된 툴은 ConfRev를 올린다. 클라이언트는 각 리포트의 ConfRev를 기대값과 비교해 일치하지 않는 리포트를 거부해야 한다. 데이터셋 멤버십이 바뀌었다는 건 클라이언트의 위치 기반 디코딩이 이제 틀렸다는 뜻이기 때문이다.
내가 자주 보는 실패: 통합업체가 .CID 파일을 편집해 데이터셋에 포인트 세 개를 추가하고 다운로드했는데, SCADA 쪽은 여전히 옛 데이터셋 정의와 옛 기대 ConfRev를 가지고 있다. 그러면 클라이언트가 모든 리포트를 거부하거나(최선 — 알아챈다), 허술한 드라이버가 그냥 디코딩해서 새 멤버를 엉뚱한 태그에 매핑한다(최악 — 차단기 상태가 계측값 밑에 나타난다). 데이터셋을 바꾼 뒤에는 모델을 SCADA로 다시 내보내고, 값 하나라도 믿기 전에 양쪽 ConfRev가 일치하는지 확인하라.
조용한 실패를 피하는 시운전 순서
새 IED에서 61850 리포팅을 올릴 때 나는 이 순서로 간다:
- 모델을 읽고 데이터셋이 존재하는지, 각 상태 포인트에
stVal,q,t가 들어 있는지 확인한다 — 값만이 아니라 품질과 시간까지. - 데이터셋별로 맞는 블록 유형을 고른다: 천이는 BRCB, 주기적으로 갱신되는 아날로그는 URCB.
- 내 고유 RCB 인스턴스를 예약하고, 어느 인스턴스 번호가 어느 클라이언트 것인지 기록한다.
- 데이터가 실제로 필요로 하는 것에 맞춰
TrgOps를 설정한다 — 상태에는dchg+qchg, 자가 복구 리프레시가 중요한 곳에는period추가. - 동시 변화를 합치도록
bufTm(10–50 ms)을, 무결성 리프레시를 쓰는 곳에는intgPd를 설정한다. RptEna = TRUE를 쓴다. 이제서야 리포트가 흐른다.GI = TRUE를 한 번 쏘아 초기 전체 그림을 당긴 뒤, 실제 천이를 강제로 일으켜 리포트가 올바른 EntryID, RaisonCode(포함 사유), 품질과 함께 도착하는지 확인한다.
6단계를 빼먹으면 이 글 맨 위의 그 클라이언트가 된다 — 접속되고, 브라우징되고, 귀먹은. 접속은 애초에 요점이 아니었다. 요점은 활성화된 리포트다.