FUXA SCADA + OpenPLC + 3가지 산업 프로토콜(Modbus/OPC-UA/S7Comm) 통합한 볼 부상 시뮬레이션 학습 프로젝트.
Docker Compose 13 컨테이너 + Machbase Neo 히스토리안.
- Python 69.4%
- JavaScript 25%
- Smalltalk 4.1%
- Dockerfile 1.5%
| docs | ||
| fuxa | ||
| openplc | ||
| simulator | ||
| .gitignore | ||
| CLAUDE.md | ||
| DEVELOPMENT-PLAN.md | ||
| docker-compose.yml | ||
| README.md | ||
Ball Levitation SCADA
볼 부상 시뮬레이션 6대 설비를 오픈소스 SCADA(FUXA)로 통합 감시/제어하는 프로젝트.
3종 산업용 프로토콜(Modbus TCP, OPC-UA, S7Comm)을 활용하여 docker compose up 한 줄로 13개 컨테이너가 구동된다.
아키텍처
block-beta
columns 6
block:SCADA["SCADA 인프라"]:6
columns 1
FUXA["fuxa - FUXA 웹 HMI :18881"]
end
space:6
block:GM["Modbus TCP"]:2
columns 2
PLC1["plc-1"] PLC2["plc-2"]
space space
SIM1["sim-1"] SIM2["sim-2"]
end
block:GO["OPC-UA"]:2
columns 2
PLC3["plc-3"] PLC4["plc-4"]
space space
SIM3["sim-3"] SIM4["sim-4"]
end
block:GS["S7Comm"]:2
columns 2
PLC5["plc-5"] PLC6["plc-6"]
space space
SIM5["sim-5"] SIM6["sim-6"]
end
FUXA -- "Modbus TCP" --> GM
FUXA -- "OPC-UA" --> GO
FUXA -- "S7Comm" --> GS
PLC1 <--> SIM1
PLC2 <--> SIM2
PLC3 <--> SIM3
PLC4 <--> SIM4
PLC5 <--> SIM5
PLC6 <--> SIM6
각 PLC는 PID 제어 프로그램(IEC 61131-3 ST)을 실행하며, 시뮬레이터가 볼 부상 물리 모델을 계산하여 Modbus TCP로 PLC와 통신한다.
빠른 시작
최초 실행 (1회)
# 공용 네트워크 생성
docker network create infra-net
# 히스토리안 인프라 기동 (Machbase Neo + Grafana)
cd ../infra && docker compose up -d && cd ../ball-levitation-scada
# Machbase 스키마 생성 (Web UI → http://localhost:5654 → SQL 탭)
# CREATE TAG TABLE SCADA_TAGS (
# name VARCHAR(100) PRIMARY KEY,
# time DATETIME BASETIME,
# value DOUBLE SUMMARIZED
# ) WITH ROLLUP EXTENSION;
# CREATE RETENTION ret_7d_1h DURATION 7 DAY INTERVAL 1 HOUR;
# ALTER TABLE SCADA_TAGS ADD RETENTION ret_7d_1h;
SCADA 스택 실행
# 전체 시스템 기동 (13개 컨테이너)
docker compose up -d --build
# PLC 초기화 대기 (약 30초)
# 시뮬레이터가 PLC 상태를 확인 후 자동으로 프로그램 업로드/실행
# FUXA 장치/태그 등록
uv run --with requests python fuxa/setup_devices.py
# HMI 화면 + 서버 스크립트 구성 (히스토리안 전송 포함)
uv run --with requests python fuxa/setup_views.py
# FUXA 재시작 (장치 연결 활성화)
docker compose restart fuxa
- FUXA 웹 UI: http://localhost:18881
- Machbase 대시보드: http://localhost:5654
클린 재실행 (SCADA 스택만 초기화)
Machbase 데이터는 유지하면서 SCADA 컨테이너만 완전히 재설치할 때 사용한다.
# SCADA 컨테이너 + 볼륨 삭제 (infra는 건드리지 않음)
docker compose down -v
# 이미지 재빌드 및 기동
docker compose up -d --build
# FUXA 설정 재등록
uv run --with requests python fuxa/setup_devices.py
uv run --with requests python fuxa/setup_views.py
docker compose restart fuxa
컨테이너 구성
| 컨테이너 | 역할 | 이미지 | 호스트 포트 |
|---|---|---|---|
| fuxa | SCADA HMI | scada-fuxa (패치 적용) | 18881 |
| plc-1, plc-2 | Modbus TCP PLC | openplc-runtime | - |
| plc-3, plc-4 | OPC-UA PLC | openplc-runtime | 4841, 4842 |
| plc-5, plc-6 | S7Comm PLC | openplc-runtime | 1021, 1022 |
| sim-1 ~ sim-6 | 물리 시뮬레이터 | Python (커스텀) | - |
HMI 화면
개요
6대 설비 상태를 프로토콜별 3열로 배치. PV/SP/MV 값과 프로그레스 바로 한눈에 상태 확인.
조정 + 트렌드
호기당 1행(총 6행) 구성. 좌측에 PV/MV/SP 실시간 차트, 우측에 PID 파라미터(SP/KP/KI/KD) 입력 위젯.
- 차트 Y축: 0~1000 고정 범위
- 차트 X축: 최근 5분 롤링 버퍼
- 데이터 수집: 250ms 주기 (초당 4회)
- 목표높이 랜덤변경: 우측 상단 토글 스위치 활성화 시, 1분 간격으로 6대 설비의 SP를 200~800 랜덤 정수로 자동 변경 (FUXA 서버 스크립트로 동작, 브라우저 종료 후에도 유지)
알람
PV 값 기반 상태 표시 테이블. NORMAL/RISING/HIGH/LOW/VERY HIGH 5단계 색상 코딩.
태그 설계
호기당 6개 태그, 총 36개:
| 태그 | 설명 | 방향 |
|---|---|---|
| BALL_xx.PV | 볼 위치 (0~1000) | 읽기 |
| BALL_xx.MV | 바람세기 (0~1000) | 읽기 |
| BALL_xx.SP | 설정값 | 읽기/쓰기 |
| BALL_xx.KP | 비례 게인 | 읽기/쓰기 |
| BALL_xx.KI | 적분 게인 | 읽기/쓰기 |
| BALL_xx.KD | 미분 게인 | 읽기/쓰기 |
히스토리안 (Machbase Neo)
별도 Compose(infra/docker-compose.yml)로 운영되는 Machbase Neo에 태그 데이터를 저장한다.
- 데이터 흐름: FUXA 서버 스크립트 → MQTT Publish → Machbase Neo Tag Table
- 전송 방식: MQTT v5 append 모드 (포트 5653), 지속 연결로 HTTP보다 오버헤드 적음
- 샘플링: 250ms 간격, 1초 배치(144행) 전송
- 테이블:
SCADA_TAGS(36개 태그, WITH ROLLUP EXTENSION) - 보관: 7일 원시 데이터, 초/분/시 자동 롤업
- 대시보드: Machbase Neo 내장 대시보드 (호기당 PV/SP/MV 차트)
SCADA_TAGS 테이블
36개 태그 시리즈, 초/분/시 롤업, 7일 retention 정책이 적용된 Tag Table.
Machbase 대시보드
6대 설비의 PV/SP/MV를 실시간으로 표시. 최근 5분, 5초 자동 새로고침.
프로토콜별 설정
| 프로토콜 | 호기 | PLC 플러그인 | FUXA 연결 |
|---|---|---|---|
| Modbus TCP | 1~2 | 기본 내장 | ModbusTCP, HR 10~15 |
| OPC-UA | 3~4 | opcua (4840) | OPCUA, ns=2;i=1~6 |
| S7Comm | 5~6 | s7comm (102) | SiemensS7, DB20.DBW20~30 |
디렉토리 구조
ball-levitation-scada/
docker-compose.yml # 13개 컨테이너 정의
openplc/
pid_control.st # PLC PID 제어 프로그램 (IEC 61131-3 ST)
opcua.json # OPC-UA 플러그인 설정
s7comm_config.json # S7Comm 플러그인 설정
plugins-default.conf # OpenPLC 플러그인 활성화 설정
simulator/
simulator.py # 볼 부상 물리 시뮬레이터
Dockerfile
fuxa/
Dockerfile # FUXA 패치 (node-snap7, mqtt.js, DNS resolve)
s7-index-patched.js # S7 드라이버 호스트네임 지원 패치
setup_devices.py # 장치/태그 자동 등록 스크립트
setup_views.py # HMI 화면 자동 구성 스크립트 (FuxaServer 내부 장치, 서버 스크립트 포함)
random_sp.py # SP 랜덤 변경 CLI 스크립트 (독립 실행용)
FUXA-API.md # FUXA REST API 참조 문서
docs/
phase3-opcua-implementation.md
phase4-s7comm-implementation.md
phase5-hmi-implementation.md
phase5-chart-configuration.md
phase5-htmlinput-debug.md
phase6-historian-implementation.md
mqtt-historian-research.md # Machbase MQTT 브로커 + FUXA 전환 조사
s7comm-write-investigation.md
기술 스택
- SCADA: FUXA (오픈소스, Node.js)
- PLC: OpenPLC Runtime (IEC 61131-3)
- 시뮬레이터: Python (pymodbus)
- 컨테이너: Docker Compose
- 차트 엔진: uPlot (FUXA 내장)
FUXA 패치 사항
FUXA 공식 이미지에 다음 패치를 적용하여 사용한다 (fuxa/Dockerfile):
- node-snap7 설치: S7Comm 프로토콜 지원 (기본 미포함)
- mqtt.js 설치: Machbase Neo MQTT 브로커로 히스토리안 데이터 전송 (기본 미포함)
- S7 드라이버 DNS resolve: snap7이 호스트네임을 지원하지 않아 IP 변환 패치 적용
- null-safe 가드:
setDeviceConnectionStatusTypeError 방지 - S7 DBWrite 패치:
WriteMultiVars가 DB 영역에서 데이터 미전송하는 버그 우회 (setValue에서 DBWrite 사용)
관련 프로젝트
- ball-levitation-sim - PLC 1대 + 시뮬레이터 기반 프로젝트 (이 프로젝트의 원형)




