← 전체 글
Modbus/약 13분 읽기/ 조회

Modbus write 성공 응답은 설비가 움직였다는 뜻이 아니다

Modbus TCP write 응답은 transaction이 접수됐다는 의미일 뿐, 설비가 실제로 동작했다는 뜻이 아니다. Function code, permissive, readback, retry 함정까지 Modbus write에 command discipline을 붙이는 방법.

ModbusSCADAHMI문제 해결체크리스트

Write는 성공했는데 pump는 안 돌았다

운전자가 start 버튼을 누르고 화면에는 깔끔하게 command sent가 떴는데, 정작 아무것도 안 도는 걸 옆에서 본 적이 있다. Modbus TCP write는 정상 응답을 돌려줬다. Address와 value가 echo됐고 exception도 없었다. Gateway는 만족했고, 그 뒤의 480 V 드라이브는 local mode라 요청을 전부 무시했다.

이 간극이 Modbus write의 핵심 문제다. Read 실패는 시끄럽다. Stale 값, bad quality, timeout이 화면에 그대로 뜬다. 반면 "성공"했는데 아무 일도 안 일어난 write는 조용하다. 그리고 그걸 눈치챌 때쯤이면 운전자는 이미 버튼을 세 번 더 눌러 놨다.

Modbus TCP 문제는 대부분 read 쪽에서 먼저 잡힌다. Offset 하나 밀림, word order 교체, polling timeout. Write는 드물지만 파장이 크다. 잘못된 write 한 번이 motor start, totalizer reset, setpoint 이동, 그리고 누군가 진단에 필요했던 fault clear로 이어진다. 그래서 writable point는 spreadsheet 한 칸이 아니라 작은 제어 인터페이스로 봐야 한다.

지금 어떤 function code를 보내는지 알고 있어야 한다

생각보다 중요하다. Function code마다 동작이 다르고, 벤더 구현도 제각각이기 때문이다.

  • FC05 — Write Single Coil. Value 필드는 1이 아니다. Modbus Application Protocol 스펙(V1.1b3, §6.5) 기준으로 ON은 0xFF00, OFF는 0x0000이고, 그 외 값은 illegal이라 엄격한 장비는 exception 0x03(ILLEGAL DATA VALUE)으로 답한다. 일부 gateway는 관대해서 0x0001도 ON으로 받아준다 — 그래서 랩에서는 되던 write가 더 엄격한 slave가 있는 현장에서 깨진다.
  • FC06 — Write Single Register. 16-bit holding register 한 개.
  • FC15 (0x0F) / FC16 (0x10) — Write Multiple Coils / Registers. 32-bit 값(float, DINT)은 FC16으로 써서 두 word를 한 transaction에 넣어야 한다. FC06 두 번으로 16-bit 반씩 쓰면, 그 사이 순간에 장비는 절반만 갱신된 값을 본다. Display tag라면 상관없지만 PLC가 쓰기 중간에 반응하는 setpoint라면 위험하다.

Master 라이브러리가 실제로 무엇을 내보내는지 확인해라. "간헐적" write 버그가 알고 보면 FC06만 구현한 장비에 라이브러리가 FC16을 보내고 있던 경우가 한두 번이 아니다.

Command map과 read map을 분리한다

Writable point를 read-only 값 사이에 섞어 두면 검토가 느슨해진다. Command 영역을 따로 빼고, 각 writable coil·register에 아래를 남긴다.

항목확인 이유
Device와 unit idUnit id가 gateway를 거쳐 올바른 serial slave로 라우팅된다
Function codeFC05/06/15/16 — 그리고 장비가 실제로 지원하는지
Vendor address zero-based address40001 표기와 0 offset 혼동 방지
Data type과 허용 범위signed/unsigned, scaling, 32-bit word order
Write가 만드는 현장 동작tag 이름이 아니라 실제 물리 효과
필요한 permissive어떤 조건에서만 command가 허용되는지
기대 feedbackHMI가 완료 여부를 무엇으로 판단하는지
Reset 방식momentary, latched, PLC auto-clear

Feedback 항목이 비어 있는 writable address는 미완성 설계다. Write가 증명하는 건 TCP transaction이 접수됐다는 것뿐이다. 현장에서 뭔가 움직였다는 뜻이 아니다.

"안 된다"고 말하는 주체는 PLC여야 한다

Start, stop, reset, mode 변경, acknowledge는 PLC가 검증하는 command bit나 command code 구조를 신뢰한다. HMI는 요청하고 PLC가 판단한다.

  1. HMI가 momentary request bit 또는 command code를 write한다(FC05 또는 FC06).
  2. PLC가 local/remote, auto/manual, safety permissive, 설비 availability를 확인한다.
  3. PLC가 accepted/rejected 결과를 reason code와 함께 latch한다.
  4. Request는 HMI가 clear하거나 PLC가 한 scan 뒤 auto-clear한다.
  5. HMI는 request bit가 아니라 실제 설비 상태로 feedback을 표시한다.

피하고 싶은 설계는 HMI가 PLC sequence 내부 state bit에 직접 write하는 구조다. Interlock을 우회하고, 두 곳이 같은 memory를 쓰기 때문에 원인 추적이 괴로워진다. PLC가 앞에 없는 단독 Modbus 장비(smart valve, soft starter)라면 같은 검사를 SCADA command layer에 넣고 허용 범위를 좁게 잡는다.

응답은 "transaction 접수"만 의미한다

다시 조용한 실패 얘기다. Modbus write 응답은 — FC05·FC06에서 돌려주는 address/value echo까지 포함해서 — server가 transaction을 받았다는 뜻이다. 그 이상은 아니다. Valve가 열렸거나 drive가 돌았거나 setpoint가 부하 상태에서 적용됐다는 증명이 아니다.

그래서 command마다 결과를 두 개로 나눠 들고 있어야 한다.

  • 통신 결과: accepted, timeout, exception code, connection closed, retry exhausted.
  • 공정 결과: 설비가 정해진 시간 안에 요청한 상태에 도달했는가.

HMI는 이 둘을 다르게 말해야 한다. Write 실패: gateway 10.20.4.7:502 timeout은 통신 문제다. Command 접수됐으나 pump가 8초 내 run 확인 없음은 현장 문제이고, 전혀 다른 사람을 부른다. 이건 serial-behind-gateway 구조에서 가장 심하다. 많은 gateway가 자기 buffer에서 write를 ACK하고, RTU slave가 뭔가 하기 전에 — 혹은 offline인 상태에서 — success를 돌려준다.

확인창은 값어치가 있는 곳에만

모든 write에 확인창이 필요하진 않다. 다 붙이면 운전자는 습관적으로 눌러 넘기고, 그러다 정작 중요한 확인창도 눌러 넘긴다. 결과가 마찰을 정당화하는 곳에만 쓴다.

  • 대형 rotating equipment 원격 start.
  • Isolation valve open/close.
  • Local↔remote, manual↔auto mode 변경.
  • Production count, batch, totalizer reset.
  • Safety, 제품 품질, utility load에 영향을 주는 setpoint 변경.
  • 아직 진단에 필요할 수 있는 alarm/fault clear.

그리고 문구는 구체적으로. 설비 이름, 현재 상태, 요청 동작, target value. 정말 실행합니까?는 아무것도 확인하지 않는다. P-101 start? (현재 STOPPED)는 현장 동작을 확인한다.

Setpoint는 write 전에 제한하고 write 후에 readback한다

숫자 write는 text box 하나로 끝내면 안 된다. Engineering unit, min/max, decimal 처리, 나가기 직전 값 preview가 필요하다. Commissioning 때 지저분한 경우를 실제로 만들어 본다.

  • 화면 scaling과 register scaling이 같은가?
  • Signed인가 unsigned인가?
  • 장비가 정수 그대로 받는가, 0.1 단위인가, 0.01 단위인가? ("50.0 °C"인데 register에는 500이 들어가는 배수 10 사고가 전형적이다.)
  • 범위를 벗어난 값 — 장비가 clamp하는가, reject하는가, wrap하는가, 조용히 받는가?
  • Reboot 후 값이 유지되는가?

중요한 값은 write 후 register를 readback해서 요청값과 비교한다. 장비가 반올림하거나 clamp하면 HMI는 운전자가 친 예쁜 숫자가 아니라 적용된 값을 보여줘야 한다.

Retry는 read의 습관이고, write에서는 사고를 만든다

Auto-retry는 read에선 좋고 command에선 정말 위험하다. 딱 한 경우 때문이다 — 첫 write는 장비가 실행했는데 응답 packet만 유실된 경우. Retry하면 두 번 실행된 것이 된다.

Write retry를 켜기 전에 답해야 한다.

  • Idempotent한 command인가? Setpoint 50.0 재전송은 대체로 안전하다. Jog/step은 아니다.
  • 장비가 확인할 수 있는 sequence number나 transaction result를 제공하는가?
  • Feedback이 오거나 timeout 날 때까지 HMI button을 disable할 수 있는가?
  • Timeout 뒤에 운전자가 failed도 done도 아닌 unknown으로 명확히 보는가?

Momentary action command의 내 기본값은 보수적이다. 한 번만 보내고, process feedback을 기다리고, 확인 전에 통신이 끊기면 command status unknown으로 남기고 멈춘다. MBAP transaction identifier는 응답을 요청에 매칭해 주지만, slave가 이미 실행한 뒤 응답이 유실된 상황은 막아 주지 않는다. 그건 프로토콜이 아니라 우리 로직이 처리할 몫이다.

요청과 결과를 한 줄로 log한다

40023에 1 write는 나중의 엔지니어에게 아무것도 알려주지 않는다. Log는 사건을 처음부터 끝까지 재구성할 수 있어야 한다.

  • User, station, timestamp.
  • 설비 이름과 command label.
  • Engineering unit 기준 requested value.
  • Raw Modbus address와 raw value.
  • 통신 결과와 exception code(0x02 illegal data address, 0x04 server device failure, 0x06 server busy…).
  • Process feedback 결과와 timeout.
  • PLC 또는 device의 rejection reason.

이게 shift handover와 incident review를 말싸움에서 판독으로 바꾼다. 요청된 command와 완료된 field action이 기록상 분리돼 보이기 때문이다.

FAT에서 일부러 깨봐야 할 실패들

아래는 happy path 한 번의 click으로는 안 보인다. 일부러 깨보자.

  • Offset 하나 밀려 옆 register에 write.
  • Read용 32-bit word order는 맞고 write용은 틀림.
  • HMI는 maintained 1을 쓰는데 장비는 pulse를 기대.
  • PLC가 command를 SCADA poll보다 빨리 auto-clear해서 운전자가 반영을 못 봄.
  • Gateway는 ACK했지만 뒤쪽 serial slave는 offline.
  • Local mode에서 바꾼 setpoint가 remote 복귀 후 갑자기 적용.
  • 두 HMI station이 ownership 없이 같은 값을 번갈아 write.

운영 투입 전 확인 순서

  1. Writable address가 read-only data와 분리되어 검토됐다.
  2. HMI가 write 전 설비 context와 engineering unit을 보여준다.
  3. PLC 또는 device permissive가 unsafe request를 reason과 함께 reject한다.
  4. Setpoint에 bound, scaling 검증, readback 확인이 있다.
  5. 통신 quality가 bad일 때 command button이 disable되거나 명확히 표시된다.
  6. Timeout, exception, rejected, unknown 상태가 각각 다른 메시지를 갖는다.
  7. Command log에 user, station, raw value, engineering value, feedback이 남는다.
  8. 여러 station이 하나의 write를 두고 다투지 않도록 테스트됐다.

Modbus TCP가 이만큼 살아남은 건 단순하기 때문이다. 함정은 그 단순함이 wire에서 끝난다는 점이다. 더 풍부한 프로토콜이 공짜로 주는 command 안전성을, 여기서는 프로젝트가 직접 만들어 넣어야 한다.