연결은 멀쩡한데, 값이 다른 meter 것이다
Driver를 10.20.4.31:502에 붙이면 channel은 green으로 뜨고 값도 들어온다. 그런데 값이 이상하다. 범위를 벗어난 게 아니라 다른 장비 값이다. Boiler feed pump여야 할 kW tag가 compressor를 따라 움직인다. Driver log는 아무 불평도 안 한다. TCP 입장에서는 전부 정상이니까.
원인은 거의 항상 unit identifier다. Modbus Messaging Implementation Guide(V1.0b)는 이 부분을 아주 단호하게 적어 놨다. TCP/IP에서는 server를 IP로 지정하므로 "MODBUS Unit Identifier is useless", 권장값은 0xFF. 이 문장이 자주 인용되고 실제로 맞다 — gateway가 serial trunk 앞에 붙기 전까지는. Gateway가 끼면 MBAP header 7번째 byte의 unit ID가 gateway에게 "RS-485 slave 몇 번으로 넘겨라"라고 알려주는 유일한 값이 된다. IP는 gateway까지만 데려다주고, 그 뒤 장비를 고르는 건 unit ID다.
그래서 ping이 되고 502 port가 열렸다는 건 딱 하나만 증명한다. Gateway가 살아 있다는 것. 내가 원한 slave가 bus에 붙어 있는지, 전원이 들어왔는지, address가 맞는지, 응답을 하는지에 대해서는 아무것도 말해 주지 않는다.
어디서 터지나
Ethernet gateway 하나가 daisy chain으로 갈라지는 현장이다 — panel에 들어온 게 Moxa MGate든 Anybus든 Red Lion이든:
- Power meter 여러 대가 RS-485 loop 하나에 묶이고, 각자 DIP switch나 keypad로 slave address를 다르게 잡은 경우.
- VFD가 serial option card 뒤에 붙는데, drive의 Modbus address와 keypad "station number"가 서로 다른 설정이라 헷갈리는 경우.
- Remote I/O block이나 package skid가 내부 controller 여러 개를 gateway IP 하나로 내보내는 경우.
Mapping은 gateway가 쥐고 있다. 시운전 때 할 일은 SCADA 쪽을 그 mapping에 맞추는 것, 그것도 slave 하나씩 맞추는 것이다.
Tag 만지기 전에 gateway가 어느 mode인지부터 안다
Gateway는 unit ID를 똑같이 해석하지 않는다. 그리고 manual은 이걸 "Modbus routing"이나 "slave ID map" 같은 이름으로 구석에 묻어 둔다. 대부분 네 가지 동작으로 정리된다.
- Transparent bridge — unit ID를 RTU slave address로 그대로 넘긴다. Unit ID 7 → 선로상 slave 7.
- Remapped — gateway가 표를 가진다(virtual ID → 물리 port + 실제 slave address). Unit ID 7이 port 2의 slave 3으로 갈 수도 있다. "값이 다른 장비 것"이 여기서 나온다. 누가 표를 고쳤거나, firmware update 후 default로 돌아갔거나.
- Single-device / server mode — unit ID를 무시하고 항상 한 slave만 읽거나 gateway 자체 register table에서 답한다. 어떤 unit ID를 poll해도 같은 값이 나온다.
- 모르는 ID에 reject vs. drop — 어떤 gateway는 route 안 되는 ID에 exception 0x0B(gateway target device failed to respond)나 0x0A(gateway path unavailable)를 돌려준다. 어떤 gateway는 그냥 먹어 버리고 timeout이 나게 둔다.
마지막 갈림길이 반나절을 잡아먹는다. Unit ID 1은 답하는데 7은 exception 없이 timeout이면, silent-drop gateway는 이걸 네트워크 장애처럼 보이게 만든다. Firewall rule 다시 쓰지 말고, routing table과 slave 본체의 address switch부터 봐라. 유효한 RTU address는 1–247이고 0은 broadcast, 248–255는 reserved다. 누가 "친절하게" 250으로 맞춰 놓은 slave는 규격을 지키는 gateway를 통해서는 절대 답하지 않는다.
Tag 400개 import하기 전에 register 하나를 증명한다
Unit ID 문제와 register map 문제는 서로의 옷을 입는다. 틀린 register는 틀린 slave처럼 보이고, 틀린 slave는 틀린 register처럼 보인다. 빠져나오는 방법은 하나뿐이다. Bulk import 전에 known-good read 하나를 손으로 확정하는 것 — bench Modbus poller든 mbpoll이든.
독립적으로 확인 가능한 register를 골라라. Firmware/model register, faceplate에서 눈으로 읽을 수 있는 meter의 line voltage, drive status word, 천천히 변하는 counter. 값이 안 맞으면 늘 나오는 용의자들을 훑는다.
- One-based 문서(
40001) vs. zero-based driver offset(0). Base off-by-one은 내가 제일 자주 만나는 map error다. - Holding register(FC03)와 input register(FC04)는 별도 address space다 — 40001과 30001은 같은 데이터가 아니다.
- 32-bit 값: word order와 byte order. 값이 65536배로 크거나 반쪽이 뒤집혀 있으면 배선 문제가 아니라 word-swap 신호다.
- Signed vs. unsigned vs. IEEE-754 float vs. scaled integer.
- 지원하지 않는 gap을 가로지르는 block read — 장비가 block 전체를 reject하므로 "될 법한" 범위가 아무것도 안 돌려준다.
Single read를 증명하고, 그다음 import를 믿어라.
시간이 두 겹으로 걸린다, 그중 하나가 retry를 겹친다
TCP-to-serial gateway는 시계 두 개 위에서 산다. Driver는 TCP 밀리초를 세고, gateway는 RTU t3.5 inter-frame gap(9600 8E1에서 약 4 ms의 공백, 느려지면 더 길다)을 포함한 serial request-and-response를 센다. SCADA timeout을 gateway의 serial timeout보다 짧게 잡으면 그 전형적인 난장판이 나온다. Driver가 포기하고 retry하는데, 같은 slave에 대한 요청 두 개가 첫 요청이 아직 선로에 있는 채로 queue에 쌓인다. Log는 불량 slave처럼 보이는 겹친 timeout으로 채워진다.
그러니 driver timeout을 gateway serial timeout보다 넉넉히 길게 둬라 — 같게가 아니라 길게. 느린 multi-drop trunk에서는 retry 횟수도 줄여라. Meter 하나가 느린 chain을 두드려 봐야 멀쩡한 slave만 굶는다. Unit ID 여러 개가 같은 RS-485 trunk를 공유하면 poll group을 분산해서 gateway가 전부를 queue 하나로 직렬화하지 않게 한다 — 그리고 실제로 밀리고 있을 때는 request queue depth counter가 bad-quality tag보다 먼저 알려 준다.
긴 chain에서 죽은 slave 하나가 모든 address를 간헐 장애처럼 보이게 만들 수 있다. Gateway가 다른 slave로 넘어가기 전에 그 시체를 기다리며 serial timeout을 다 쓰기 때문이다. 선로를 scope로 보거나 gateway 자체의 CRC-error, no-response counter를 읽어라. SCADA quality flag만 보면 유령을 쫓게 된다.
Write: ACK는 거짓말이라고 가정한다
틀린 slave로 간 read는 성가신 정도다. 틀린 slave로 간 write는 건드릴 생각 없던 actuator를 움직인다. 그리고 TCP acknowledgement는 장비의 확인이 아니다 — 많은 gateway가 request를 queue에 넣은 순간 ACK를 보내고, serial write 실패는 아무도 안 보는 diagnostic counter로 조용히 흘려보낸다.
Gateway 경유 write를 열기 전에:
- 정확한 unit ID 와 register를 bench나 maintenance window에서 확인한다 — 도면 말고.
- Function code 지원을 확인한다: single-register(FC06) vs. multiple-register(FC16). 하나만 받는 장비도 있다.
- Gateway가 특정 write를 rewrite하거나 block하는지 확인한다("안전"을 이유로 그러는 놈들이 있다).
- HMI에 write button만 두지 말고 confirmation과 command feedback을 둔다.
- Unit ID, register, value, operator, result — 다섯 개 다 log로 남긴다.
- Write를 일부러 실패시키고 운전자가 실제로 그 실패를 보는지 확인한다. 안 보이면 feedback 경로가 깨진 것이고, 싸게 알아낸 것이다.
증상 표 읽는 법
값이 이상할 때 설정 다섯 개를 한꺼번에 바꾸지 마라. 하나 바꾸고 다시 시험하고, 증상으로 범위를 좁혀라.
| 증상 | 먼저 볼 곳 |
|---|---|
| TCP 연결은 되는데 모든 read timeout | Unit ID route, serial 극성, serial param(baud/parity/stop), gateway downstream timeout |
| Unit ID 1은 되고 나머지는 실패 | Gateway routing table, slave address switch, trunk의 중복 RTU address |
| 값은 정상인데 다른 장비 것 | Remapped unit ID, 중복된 register map, 옛 ID를 가리키는 복사 tag group |
| 일부 register는 되고 block read는 실패 | 지원 안 되는 address gap, max block size, FC03/FC04 mismatch |
| 값이 튀거나 간헐적 | RS-485 termination과 biasing, shield 접지, baud mismatch, chain을 막는 불량 slave |
| Write는 success인데 장비는 안 바뀜 | Gateway write handling, wrong function code, device write-protection, 엉뚱한 곳 가리키는 feedback tag |
가능하면 정상 request 하나와 실패 request 하나를 선로에서 capture해라. Unit ID, function code, starting address의 차이가 driver log를 한 시간 뒤지는 것보다 원인을 빨리 짚어 준다.
다음 엔지니어에게 남길 것
Routing table은 네트워크 부속 정보가 아니라 제어 시스템 설정이다. 인수인계도 그렇게 다뤄라. 2년 뒤 새벽 2시에 이 gateway를 교체할 사람이 bus를 처음부터 다시 알아내게 두면 안 된다.
- Gateway IP, firmware version, config-backup 위치, admin access 담당자.
- Serial port 설정과 trunk의 물리 설명(어느 panel, 어느 meter, 어떤 순서).
- Unit ID 목록과 device 이름, panel 위치, register-map version.
- Driver에 설정된 poll group과 timeout.
- 검토 없이 재사용하면 안 되는 reserved/retired unit ID.
- 시운전에 쓴 test register — 다음 사람이 나와 똑같이 read 하나를 증명할 수 있도록.