Modbus TCP로 구현한 IoT 엣지 드라이버

Modbus TCP로 구현한 IoT 엣지 드라이버

1. 프로젝트 배경과 Modbus TCP 선택 이유

IoT 프로젝트를 수행하면서 가장 먼저 마주했던 과제는 현장의 다양한 설비로부터 데이터를 안정적으로 수집하는 일이었습니다.

현장에는 PLC, 센서, 컨트롤러 등 서로 제조사와 통신 방식이 다른 이기종 설비들이 혼재되어 있었으며, 각 장비의 상태 데이터를 실시간으로 수집해 상위 플랫폼으로 전달해야 했습니다.

산업 현장에서는 OPC-UA, LS XGT 등 다양한 산업용 프로토콜이 사용되고 있습니다. 그중 가장 널리 사용되고 있는 Modbus TCP 기반 통신을 기준으로 IoT 엣지 통신 드라이버 구현 경험을 공유하겠습니다.

Modbus는 구조가 단순하면서도 산업 현장에서 오랫동안 사용되어 온 대표적인 산업용 프로토콜입니다. 특히 제조사에 관계없이 다양한 장비에서 지원하고 있기 때문에 현장 적용성이 매우 높습니다.

기존 Modbus RTU는 직렬(Serial) 기반으로 동작했지만, Modbus TCP는 TCP/IP 기반 이더넷 환경에서 동작하기 때문에 속도와 확장성이 뛰어나다는 장점이 있습니다.

프로젝트에서는 수십 개 이상의 설비로부터 초 단위가 아닌 밀리초 단위 수준으로 데이터를 수집해야 했기 때문에 네트워크 효율성과 처리 성능이 매우 중요했습니다.

특히 엣지 환경은 언제든 네트워크 연결이 끊길 수 있기 때문에 네트워크 오프라인 상태에서 수집된 데이터도 유실되지 않아야 했습니다. 또한 엣지 환경 특성상 컴퓨팅 자원이 제한적이었기 때문에 CPU 사용량과 메모리 사용량까지 함께 고려해야 했습니다. 결과적으로 프로젝트에서는 다음 목표를 기준으로 통신 드라이버를 설계하였습니다.

- 밀리초 단위의 고속 데이터 수집

- 네트워크 단절 상황에서의 데이터 유실 방지

- 실시간 데이터 전달 구조 구현

- 제한된 엣지 자원의 효율적 활용

- 설비 제어를 위한 안정적인 Write 지원

- 상위 플랫폼과의 유연한 연동 구조 제공

이번 프로젝트는 단순히 Modbus 패킷을 파싱하는 수준이 아니라 실제 산업 현장에서 안정적으로 운영 가능한 데이터 수집 아키텍처를 구현하는 과정이었습니다.

2. Modbus TCP 프로토콜 이해

구현 과정에 들어가기 전 Modbus TCP 프로토콜 구조를 먼저 이해할 필요가 있었습니다.

Modbus는 기본적으로 Client-Server 구조를 따릅니다. 일반적으로 Master(Client)가 요청(Request)을 보내고 Slave(Server)가 응답(Response)을 반환하는 방식으로 동작합니다.

예를 들어 PLC나 센서 장비는 Slave 역할을 수행하고, IoT Gateway나 데이터 수집 애플리케이션은 Master 역할을 수행하게 됩니다. Modbus에서는 특정 주소(Address)에 저장된 값을 읽거나 쓰는 방식으로 동작합니다.

대표적으로 다음과 같은 기능 코드(Function Code)를 사용하였습니다.

- Holding Register Read

- Input Register Read

- Coil Read

- Single Register Write

- Multiple Register Write

프로젝트에서는 Holding Register 기반 데이터를 조회하고 일부 설비 제어를 위해 Write 기능도 함께 사용하였습니다. Modbus TCP는 기존 Modbus RTU 패킷 앞단에 MBAP(Modbus Application Protocol) Header를 추가한 구조입니다.

MBAP Header에는 다음과 같은 정보가 포함됩니다.

- Transaction Identifier

- Protocol Identifier

- Length

- Unit Identifier

이를 통해 TCP 기반 환경에서도 여러 요청을 안정적으로 구분할 수 있었습니다. 특히 TCP 기반으로 동작하기 때문에 기존 Serial 통신 대비 속도가 빠르고 네트워크 구성 유연성이 높았습니다. 프로젝트에서는 여러 설비에 대해 동시에 요청을 처리해야 했기 때문에 비동기 기반 TCP 처리 구조가 매우 중요했습니다.

또한 일부 설비는 응답 지연이 발생하거나 특정 시간 동안 응답이 끊어지는 경우도 있었기 때문에 타임아웃 처리와 재시도 구조도 함께 고려하였습니다.

결과적으로 단순 프로토콜 구현을 넘어서 네트워크 장애 상황까지 고려한 안정적인 통신 구조를 설계하는 것이 핵심 과제가 되었습니다.

3. 비동기 기반 데이터 수집 아키텍처

프로젝트의 가장 중요한 요구사항 중 하나는 밀리초 단위 수준의 매우 빠른 데이터 수집이었습니다.

만약 상위 애플리케이션에서 데이터가 필요할 때마다 동기 방식으로 장비에 Read 요청을 보내는 구조를 사용한다면 네트워크 I/O 대기 시간 때문에 원하는 성능을 얻기 어려웠습니다.

특히 현장 장비는 응답 시간이 일정하지 않았고 순간적인 네트워크 지연도 자주 발생하였습니다.

이 문제를 해결하기 위해 데이터 수집 구조를 비동기 Polling 기반 아키텍처로 설계하였습니다.

구조는 크게 다음 단계로 구성하였습니다.

1. 비동기 Polling 수행

2. 이벤트(Event) 생성

3. MQTT Publisher 전달

4. 상위 플랫폼 Publish

먼저 내부 Polling Thread가 설정된 주기마다 Modbus 장비에 비동기 Read 요청을 지속적으로 수행하였습니다. 수집된 데이터는 단순히 즉시 전송하지 않고 Event 객체 형태로 변환한 뒤 내부 Queue에 전달하였습니다.

그 이후 독립적으로 동작하는 MQTT Publisher가 Queue를 소비하여 MQTT Broker로 데이터를 발행하는 구조를 구성하였습니다. 이러한 구조의 가장 큰 장점은 Read와 Publish 과정을 완전히 분리할 수 있다는 점이었습니다.

만약 MQTT Publish 속도가 느려지더라도 Polling Thread 자체는 영향을 받지 않기 때문에 데이터 수집 주기를 안정적으로 유지할 수 있었습니다. 또한 Thread 간 결합도를 낮출 수 있었고 각 처리 단계의 독립적인 확장도 가능하였습니다.

예를 들어 향후 Kafka 기반 Publish 구조로 변경하더라도 Polling 구조 자체는 그대로 유지할 수 있도록 설계하였습니다. 실제 운영 환경에서는 수십 개 장비에 대해 동시에 Polling을 수행해야 했기 때문에 Thread Pool 기반 구조를 적용하였고 CPU 사용량과 메모리 사용량을 지속적으로 모니터링하며 최적화 작업도 함께 진행하였습니다.

4. 데이터 유실 방지와 MQTT 장애 대응

IoT 엣지 환경에서는 네트워크 장애가 매우 빈번하게 발생합니다. 특히 공장 환경에서는 네트워크 품질이 일정하지 않거나 외부 간섭으로 인해 MQTT Broker 연결이 순간적으로 끊어지는 경우가 자주 발생하였습니다.

초기 구현 구조에서는 MQTT 연결이 끊어질 경우 Queue 내부 데이터가 모두 유실될 가능성이 존재하였습니다. 이 문제를 해결하기 위해 장애 대응 구조를 추가적으로 설계하였습니다.

먼저 MQTT Publish 실패 시 데이터를 즉시 폐기하지 않고 별도의 InfluxDB에 저장하도록 구현하였습니다.

즉, 실시간 Publish 실패 데이터는 임시 저장소 역할을 하는 시계열 데이터베이스에 적재되도록 구성하였습니다.

이후 MQTT 연결이 복구되면 저장되어 있던 데이터를 다시 읽어와 재전송하도록 구현하였습니다. 이 구조를 통해 네트워크 장애 상황에서도 데이터 유실을 최소화할 수 있었습니다.

특히 장애 복구 이후 과거 데이터와 현재 실시간 데이터가 혼합되는 문제를 방지하기 위해 별도의 MQTT Topic을 사용하였습니다.

예를 들어 실시간 데이터는 .../REALTIME/... Topic으로 발행하고 장애 복구 데이터는 .../OFFLINE/... Topic으로 구분해 발행하도록 구성하였습니다.

이를 통해 상위 플랫폼에서는 데이터 성격을 명확하게 구분할 수 있었고 실시간 분석 로직에도 영향을 주지 않을 수 있었습니다. 결과적으로 프로젝트에서는 단순 실시간 처리뿐만 아니라 장애 상황에서도 안정적으로 데이터를 보존할 수 있는 구조를 구축할 수 있었습니다.

5. 동기 기반 Write 처리와 안정성 확보

데이터 조회(Read)와 달리 설비 제어를 위한 Write 기능은 훨씬 더 높은 안정성이 요구되었습니다. 설비 상태를 변경하는 Write 요청은 단순 데이터 조회와 다르게 실제 장비 동작과 직결됩니다.

예를 들어 설비 시작, 정지, 밸브 제어, 모터 제어 등의 명령은 잘못 처리될 경우 생산 설비 장애나 안전 사고로 이어질 수 있었습니다. 이 때문에 Write 기능은 비동기 방식이 아닌 동기 방식으로 구현하였습니다.

Write 요청이 발생하면 즉시 Modbus Write 요청을 수행하고 설비로부터 정상 응답(ACK)을 받을 때까지 요청 Thread가 결과를 대기하도록 구성하였습니다.
이러한 구조를 선택한 이유는 크게 두 가지였습니다.

첫 번째는 명확한 인과율 보장이었습니다.

설비 제어 로직은 일반적으로 순차 제어 형태로 동작합니다. 즉, 이전 Write 작업이 정상적으로 완료되어야 다음 단계 제어를 수행할 수 있었습니다.
만약 비동기 방식으로 처리한다면 실제 설비 상태가 아직 변경되지 않았는데 다음 제어 로직이 먼저 수행될 위험성이 존재하였습니다.

두 번째는 즉각적인 에러 핸들링이었습니다.

Write 실패 시 상위 시스템은 즉시 장애 상황을 인지해야 했습니다. Modbus의 경우 Write 요청의 정상 응답은 항상 요청 데이터의 Echo 값입니다. 때문에 Write 요청이 성공 수행되었다는 응답을 받더라도 실제로 값이 반영되었는지 확인하는 과정이 필요하였습니다. 이를 위해 Write 요청 응답 이후 실제 레지스터 값을 다시 조회하여 요청한 값이 정확하게 반영되었는지 추가 검증하는 방식을 사용하였습니다. 이러한 구조를 통해 실제 운영 환경에서도 보다 안정적인 설비 제어를 수행할 수 있었습니다.

6. 마치며

이번 Modbus TCP 기반 IoT 통신 드라이버 구현 경험은 단순한 프로토콜 개발 이상의 의미를 가지고 있었습니다.

처음에는 단순히 Modbus 패킷을 송수신하는 수준으로 생각했지만 실제 프로젝트를 진행하면서 가장 중요한 것은 제한된 엣지 환경 안에서 데이터를 얼마나 안정적으로 수집하고 전달할 수 있는지라는 점을 체감할 수 있었습니다.

특히 다음 요소들이 매우 중요하였습니다.

- 비동기 기반 고속 데이터 수집

- 네트워크 장애 대응 구조

- 데이터 유실 방지

- 안정적인 설비 제어

- 제한된 자원 최적화

- 실시간 처리와 안정성의 균형

실제 산업 현장에서는 네트워크 품질이 항상 일정하지 않고 장비 응답 속도 역시 예측하기 어렵습니다. 따라서 단순 기능 구현보다 장애 상황에서 얼마나 안정적으로 동작할 수 있는지가 훨씬 중요하였습니다.

이번 프로젝트를 통해 Polling 구조 설계, 비동기 이벤트 처리, MQTT 기반 데이터 전달, 장애 복구 아키텍처 등 다양한 IoT 엣지 시스템 설계 경험을 얻을 수 있었습니다.

향후에는 Modbus뿐만 아니라 OPC-UA, MQTT Sparkplug, BACnet 등 다양한 산업용 프로토콜 환경에서도 유사한 아키텍처를 확장 적용할 수 있을 것으로 기대하고 있습니다.

특히 IoT 환경에서는 단순 기술 스택보다 현장 환경과 운영 안정성을 고려한 아키텍처 설계가 무엇보다 중요하다는 점을 다시 한번 확인할 수 있었던 프로젝트였습니다.

chnsik

Site footer