jongkwan.dev
프로젝트 목록으로

PLC 통신 미들웨어

Modbus TCP 기반 PLC 통신 미들웨어, 4계층 분리와 Celery 전용 폴링

에이오팜·2024.09 - 2025.11·PLC 미들웨어 설계 및 개발
Python
Django
Celery
PyModbus
Pycomm3
Pymelsec

배경

선별 시스템에서 PLC 장비와 실시간으로 통신해 자동 분류 제어와 입출고 데이터를 주고받는 계층이다. 기존 통신 코드는 단일 파일에 책임이 혼재되어 있었고, 매직 넘버와 얕은 예외 처리로 현장 디버깅이 어려웠다. Modbus TCP의 125-레지스터 읽기 제한, IEEE 754 Float32 중량 파싱, 한글 문자열 인코딩 등 산업 프로토콜 특유의 제약도 같이 풀어야 했다.

시스템 구조

  • 4계층 구조: Presentation(Celery 태스크) → Service(도메인 로직) → Infrastructure(Modbus manager/repository/service) → Repository(Django ORM).
  • 큐·워커 격리: Celery 큐 plc_polling을 PLC 전용 워커(concurrency:1)에만 붙여 Modbus 프로토콜 충돌을 원천 차단한다.
  • Modbus 제약 해결: 최대 64개 자동 분류 출구 데이터를 125-레지스터 제한에 맞춰 2-Chunk 분할로 읽고, IEEE 754 Float32로 중량을 파싱한다.
  • 현장 진단 스크립트: 설치 현장용 PLC 진단 스크립트 6종을 별도로 작성해 점검 시간을 줄였다.

해결 과정

책임 분리 재구성. 단일 파일로 시작한 PLC 통신 코드를 PLC 통신 앱 내부에서 4계층(Presentation / Service / Infrastructure / Repository)으로 나눴다. Infrastructure 내부도 config·exceptions·manager·models·repository·service로 쪼개 설정값·예외·연결 상태·쿼리 레이어가 섞이지 않게 했다.

Celery 전용 워커로 폴링 격리. Modbus 연결이 동시에 여러 스레드에서 열리면 응답이 꼬이기 쉬워, 폴링을 PLC 전용 큐로 분리하고 전용 워커(concurrency:1)에 붙였다. Beat가 10초 주기로 inlet·outlet·통합 폴링 태스크를 트리거한다.

Modbus 125-레지스터 한계 돌파. 한 번에 읽을 수 있는 레지스터 수가 제한되어 있어, 자동 분류 출구 데이터를 2-Chunk로 나눠 읽고 애플리케이션 계층에서 결합한다. 중량 값은 16-bit 레지스터 2개를 IEEE 754 Float32로 해석한다.

커스텀 예외 계층과 자동 재연결. 예외를 계층화해 네트워크 단절·슬레이브 오류·타임아웃을 분리했다. 매니저가 연결 상태를 감시하고, 네트워크 불안정 시 재연결을 시도한다.

3사 PLC 확장 구조. PyModbus(Modbus TCP) 외에 Pycomm3(Allen-Bradley)·Pymelsec(Mitsubishi) 의존성을 프로젝트 레벨에서 함께 관리해, 신규 업체 PLC가 들어와도 어댑터만 추가하면 된다.

성과

  • 단일 파일 → 4계층 구조(Presentation / Service / Infrastructure / Repository)로 분해.
  • Modbus TCP 125-레지스터 제한을 2-Chunk 분할 읽기로 해결, 최대 64개 분류 출구 데이터 10초 주기 수집.
  • PLC 전용 큐 + 전용 워커(concurrency:1)로 프로토콜 충돌 원천 차단.
  • 현장용 PLC 진단 스크립트 6종 추가로 디버깅 시간 단축.
  • 전국 20곳 이상 현장의 다중 PLC 장비와 연동 운영.