Historian에 밤마다 10분에서 20분씩 구멍이 생겼다. 클라이언트 화면은 connected, 노드는 다 보이고, 값도 돌아온다. 설정 화면에는 publishing interval 250 ms라고 적혀 있었다. 서버가 실제로 돌려준 값은 1000 ms였다. 아무도 그 응답을 안 봤다.
OPC UA 시운전에서 사람들이 적는 건 요청한 값이다. 운영에서 동작하는 건 서버가 고쳐 돌려준 값이다. 이 두 개가 다를 수 있다는 게 OPC UA가 다른 산업용 프로토콜과 다른 지점이고, 시운전 기록이 의미를 갖는 유일한 자리다.
서버가 고쳐 돌려주는 값
OPC UA Part 4의 CreateSession, CreateSubscription, CreateMonitoredItems는 전부 요청/수정 쌍이다. 클라이언트가 requested...를 보내면 서버는 revised...를 돌려줄 권한이 있다. 시운전 기록에 적어야 하는 쪽은 오른쪽 열이다.
| 요청 | 서버 응답 | 시운전에서 볼 것 |
|---|---|---|
requestedSessionTimeout | revisedSessionTimeout | 깎였으면 keepalive 주기를 그 아래로 내린다 |
requestedPublishingInterval | revisedPublishingInterval | 트렌드 해상도와 historian 간격의 실제 하한 |
requestedMaxKeepAliveCount | revisedMaxKeepAliveCount | 값이 안 바뀔 때 keep-alive가 오는 간격 |
requestedLifetimeCount | revisedLifetimeCount | Part 4는 keepAliveCount의 3배 이상을 요구한다 |
ReceiveBufferSize / SendBufferSize | Acknowledge | OPC UA Part 6 하한이 8192바이트다 |
Lifetime이 가장 조용하게 문다. Publish 요청이 서버에 하나도 남아 있지 않은 상태로 revisedLifetimeCount × revisedPublishingInterval이 지나면 서버가 구독을 지운다. 그 다음 호출은 Bad_SubscriptionIdInvalid로 떨어지는데, 많은 클라이언트는 이걸 화면에 안 띄우고 그냥 데이터가 멈춘다. 위의 historian 구멍이 이거였다. Publish 요청은 항상 여러 개 띄워 두는 쪽이 맞고, 하나만 띄워 두는 클라이언트는 응답 왕복 사이에 구독 수명을 태운다. 세션 타임아웃과 keepalive, sampling과 publishing의 구분에 각각 더 적어 뒀다.
버퍼는 Hello/Acknowledge에서 끝난다. 큰 구조체나 배열을 읽는데 Bad_ResponseTooLarge나 Bad_RequestTooLarge가 나오면 노드 권한 문제가 아니라 이 협상 결과다. MaxMessageSize와 MaxChunkCount는 0이 무제한이라서, 0을 받아 놓고 "제한 없음"이라 적으면 그건 기록이 아니다.
엔드포인트는 GetEndpoints가 돌려준 문자열을 그대로 쓴다
opc.tcp의 IANA 등록 포트는 4840이다. 그런데 디스커버리 엔드포인트와 운영 엔드포인트가 같은 포트에 있다는 보장은 없고, 서버가 GetEndpoints로 돌려주는 endpointUrl이 클라이언트가 붙어야 하는 문자열이다. 거기 적힌 호스트명을 IP로 바꿔 넣는 순간 두 가지가 깨진다.
- 서버가 세션 수립 중에 URL을 비교하고
Bad_TcpEndpointUrlInvalid를 돌려줄 수 있다. - 인증서의 SubjectAltName에 그 이름이 없으면
Bad_CertificateHostNameInvalid가 뜬다.
여기에 별개로 ApplicationUri 함정이 하나 더 있다. 애플리케이션 인스턴스 인증서의 SubjectAltName URI는 ApplicationDescription의 applicationUri와 글자 단위로 같아야 하고, 다르면 Bad_CertificateUriInvalid다. VM 이미지를 복제해 호스트명만 바꾼 클라이언트에서 제일 자주 본다. 증상과 추적 순서는 엔드포인트와 ApplicationUri 불일치 쪽에 있다.
인증서는 거부 목록만 증거로 남는다
신뢰 대화상자를 눌러서 통과시킨 연결은 기록이 없다. 양쪽 거부 목록(rejected certificates)에 뭐가 쌓였는지가 유일한 증거다. 시운전 때 한 번 비워 두고, 끝난 뒤 다시 보는 게 제일 싼 점검이다.
구분해 둘 코드는 세 개다. Bad_CertificateUntrusted는 신뢰 목록에 없다는 뜻이고, Bad_CertificateTimeInvalid는 유효기간 문제이고, Bad_SecurityChecksFailed는 서버가 이유를 안 알려주기로 한 경우다. 세 번째가 나오면 클라이언트 로그만 봐서는 못 풀고 서버 로그를 열어야 한다.
Security Policy는 OPC UA Part 7의 프로파일에서 고른다. Basic128Rsa15와 Basic256은 OPC Foundation이 폐기한 정책이라 신규 시운전에서 고를 이유가 없다. Basic256Sha256 이상에 Message Security Mode는 SignAndEncrypt가 기본값이어야 하고, None을 써야 하는 장비가 있으면 그 이유와 네트워크 통제를 같이 적어 둔다.
Secure channel 토큰에는 수명이 있다. Part 4의 OpenSecureChannel 설명은 수명의 75%가 지나기 전에 갱신하라고 권한다. 몇 시간에 한 번씩 끊기는 연결을 쫓을 때 이 지점을 먼저 본다 — 갱신을 안 하는 클라이언트가 아직 있다. 교체 절차는 인증서 갱신 체크리스트에 정리했다.
품질은 상위 2비트고, 중간이 있다
StatusCode의 심각도는 상위 2비트다. 0x00000000이 Good, 0x40000000이 Uncertain, 0x80000000이 Bad. 시운전에서 확인할 건 Bad가 아니라 Uncertain이다. HMI가 Uncertain을 Good과 같은 색으로 그리는 구성을 아직 자주 만나고, 그러면 통신이 끊긴 뒤 마지막 값이 초록색으로 남는다.
- 큐가 넘치면 값은 오는데 StatusCode에 Overflow 정보 비트가 붙는다. 이걸 버리는 클라이언트는 데이터를 잃은 사실 자체를 못 본다. MonitoredItem 큐 참고.
TimestampsToReturn은 Source=0, Server=1, Both=2, Neither=3이다. Historian이 쓰는 쪽을 정하고, Source를 쓰기로 했으면 장비 시계 동기까지 같이 확인한다. StatusCode와 SourceTimestamp에 사례를 적었다.- Read 서비스의
maxAge는 밀리초 단위고 0이 "장비에서 직접 읽어라"다. 캐시 값이 돌아오는지 아닌지가 여기서 갈린다.
구독 튜닝은 deadband 세 값에서 시작한다
모든 포인트를 1초로 돌리는 구성은 소형 게이트웨이를 먼저 죽인다. 공정 요구에서 거꾸로 내려오는 편이 맞다.
samplingInterval은 -1이 "publishing interval을 따라가라", 0이 "서버가 할 수 있는 가장 빠르게"다. 실수로 0을 넣어 둔 모니터 항목 몇 개가 서버 부하의 대부분인 경우가 있다.
DataChangeFilter의 deadbandType은 None=0, Absolute=1, Percent=2다. Percent는 노드가 EURange를 노출해야 성립하고, 없으면 서버가 필터를 거절한다 — PLC에서 올라온 raw 태그에서 자주 걸린다. trigger는 Status=0, StatusValue=1(기본), StatusValueTimestamp=2이고, 2를 쓰면 값이 안 바뀌어도 타임스탬프마다 올라오니 historian 용량 계산이 달라진다. 레벨 태그는 Absolute로 공학 단위 값을 직접 박는 쪽이 운영에서 설명하기 쉽다. 자세한 건 DataChange 필터와 deadband.
쓰기는 두 비트로 결정된다
노드의 AccessLevel은 비트 0이 CurrentRead, 비트 1이 CurrentWrite다. UserAccessLevel은 같은 비트를 지금 로그인한 사용자 기준으로 보여준다. 둘이 다를 때가 정상이고, 쓰기가 막히면 Bad_NotWritable과 Bad_UserAccessDenied 중 어느 쪽인지로 설정 문제와 권한 문제가 갈린다. Historian 전용 계정에 CurrentWrite가 서 있으면 그건 시운전에서 잡는다. UserAccessLevel과 WriteMask에 더 있다.
쓰기가 성공했다는 응답은 공정이 명령을 받았다는 뜻이 아니다. 컨트롤러가 mode, interlock, permissive, local 상태로 거절하면 OPC UA 쪽은 Good이고 설비는 안 움직인다. 피드백은 공정 값에서 읽어야 한다.
인수 전에 적어 둘 한 장
항목 이름이 아니라 값을 적는다. revised 값 다섯 개, 엔드포인트 URL 원문, 양쪽 인증서의 만료일, 매핑한 NodeId와 데이터형, 그리고 통신을 끊어 보고 화면이 어떻게 변했는지. 이 중 하나라도 비어 있으면 다음 엔지니어는 같은 시운전을 다시 한다.
다음에 볼 것은 서버 쪽 진단 노드다. Server 객체의 ServerDiagnostics 아래에 세션과 구독, 거부된 요청 수가 그대로 노출되는 서버가 많고, 클라이언트 로그보다 먼저 답을 주는 경우도 많다.