A Docker-based virtual industrial control system featuring physics-based simulation with Modelica, PLC control with OpenPLC, SCADA monitoring with SCADA-LTS, and 3D visualization with Godot.
Code released under MIT license.
V-ICS emulates a complete industrial control system stack with:
- Physics Simulation (Modelica) – Realistic thermal dynamics
- PLC Control (OpenPLC) – Programmable logic controller
- SCADA Monitoring (SCADA-LTS) – Supervisory control and data acquisition
- Communication Layer (Modbus TCP) – Industry-standard protocol
- 3D Visualization (Godot) – Interactive game-based interface
Perfect for:
- ICS security training and research
- Control system testing
- Educational demonstrations
- Cyber-physical system development
┌─────────────────┐
│ Godot (3D UI) │ ← Player interaction
└────────┬────────┘
│ WebSocket/HTTP
┌────────▼────────┐
│ Rust Bridge │ ← Game ↔ PLC connector
└────────┬────────┘
│ Modbus TCP
┌────────▼────────┐ ┌──────────────────┐
│ OpenPLC │────→│ SCADA-LTS │
│ (PLC Runtime) │ │ (Monitoring) │
└────────┬────────┘ └──────────────────┘
│ Modbus TCP
┌────────▼────────┐
│ Modbus Server │ ← Rust FFI wrapper
│ (Rust/Tokio) │
└────────┬────────┘
│
┌────────▼────────┐
│ Modelica │ ← Physics simulation
│ SimpleThermalMVP│ (OpenModelica)
└─────────────────┘
- Docker Desktop (Download)
- Git (for cloning the repository)
- 8GB RAM minimum (recommended: 16GB)
# 1. Clone repository
git clone https://github.com/bondlegend4/V-ICS.git
cd V-ICS
# 2. Initialize submodules
git submodule update --init --recursive
# 3. Build OpenPLC image
docker build -t openplc:v3 ./openplc/OpenPLC_v3
# 4. Start all services
docker compose up -d --build
# 5. Wait for services to initialize (~ 2 minutes)
docker compose logs -f| Service | URL | Default Credentials |
|---|---|---|
| OpenPLC | http://localhost:8082 | openplc / openplc |
| SCADA-LTS | http://localhost:8080/Scada-LTS | admin / admin |
| Python Bridge | http://localhost:5001/status | N/A (status endpoint) |
| Modbus Server | tcp://localhost:5502 | N/A (Modbus TCP) |
| MySQL | localhost:3306 | root / root |
The Rust-based Modbus server exposes the Modelica thermal simulation:
Registers:
100(0x64): Temperature in K × 100 (e.g., 27315 = 273.15 K)101(0x65): Heater state (0=OFF, 100=ON)
Coils:
0: Heater control (write TRUE to turn on)
Test with modbus-cli:
# Run integration test from inside container (if needed)
docker compose exec modbus-server cargo test --test modbus_client_test
**Monitor logs:**
```bash
docker compose logs -f modbus-serverStep 1: Access OpenPLC
- Navigate to http://localhost:8082
- Login:
openplc/openplc
Step 2: Configure Modbus Slave Device
- Go to Hardware → Slave Devices
- Click Add new device
- Configure:
- Device Name:
Thermal - Device Type: Generic ModbusTCP
- IP Address:
modbus-server - Port:
5502 - Slave ID:
1 - Registers:
- AI (Analog Input):
%IW100to%IW101(temperature, heater state) - DO (Digital Output):
%QX0(heater control)
- AI (Analog Input):
- Device Name:
Step 3: Upload PLC Program
- Go to Programs → Upload Program
- Upload your Structured Text (ST) program
- Example ST code:
PROGRAM ThermalControl
VAR
temperature AT %IW100 : INT; // Temperature × 100
heater_state AT %IW101 : INT; // 0=OFF, 100=ON
heater_control AT %QX0 : BOOL; // Heater output
setpoint : INT := 29315; // 293.15 K (20°C)
END_VAR
// Simple bang-bang control
IF temperature < setpoint - 200 THEN
heater_control := TRUE;
ELSIF temperature > setpoint + 200 THEN
heater_control := FALSE;
END_IF;
END_PROGRAM- Click Start PLC to run
Verify Connection:
- Check Monitoring page for live values
- Status should show "Connected" for Thermal device
Step 1: Access SCADA-LTS
- Navigate to http://localhost:8080/Scada-LTS
- Login:
admin/admin
Step 2: Import Configuration (Already done if you imported settings.json)
- Go to Data Sources → verify
Modelica - Thermalis present - Check Data Points → verify
temperaturepoint exists - Enable the data source if disabled
Step 3: Create Watchlist
- Go to Watch Lists → Add new
- Name it "Thermal Monitoring"
- Add data points:
temperature(from DS_THERMAL)heater_state(if configured)
Step 4: Create Dashboard
- Go to Graphical Views → Add new
- Add components:
- Analog Graphic: Temperature gauge (0-500 K)
- Binary Graphic: Heater status (ON/OFF)
- Chart: Temperature trend over time
View Real-time Data:
- Navigate to your watch list
- You should see live temperature updates every 1 second
Scenario: Manual Temperature Control
# Start all services
docker compose up -d
# Check Modbus server logs
docker compose logs -f modbus-server
# Run integration test from inside container (if needed)
docker compose exec modbus-server cargo test --test modbus_client_testExpected Behavior:
- Temperature starts at ~250 K (ambient)
- Turning heater ON causes temperature to rise
- Turning heater OFF causes temperature to cool back down
- OpenPLC reads these values at %IW100
- SCADA-LTS shows live graph of temperature changes
Run Rust integration test:
# Terminal 1: Start server
cd modelica-rust-modbus-server
cargo run
# Terminal 2: Run tests
cargo test --test modbus_client_test -- --nocaptureRun example client:
# Start server first
cargo run
# In another terminal
cargo run --example simple_clientExpected test output:
✓ Connected to Modbus server
✓ Temperature: 250.00 K (raw: 25000)
✓ Heater state: 0 (0=OFF, 100=ON)
✓ Turned heater ON
✓ Heater confirmed ON
✓ Temperature after heating: 251.25 K
✓ Turned heater OFF
✓ Heater confirmed OFF
✓ Multi-register read: temp=25125, heater=0
✅ All tests passed!
# Start all services
docker compose up -d
# Check Modbus server logs
docker compose logs -f modbus-server
# Run integration test from inside container (if needed)
docker compose exec modbus-server cargo test --test modbus_client_testProblem: Device "Thermal" shows "Disconnected"
Solutions:
- Verify Modbus server is running:
docker ps - Check hostname is
modbus-server(notlocalhost) - Verify port is
5502 - Check firewall/network settings in Docker
Problem: Values not changing in Monitoring page
Solutions:
- Verify PLC program is running (green "Running" status)
- Check that %IW100 address is correct (not %IW40001)
- Review register mapping in Hardware → Slave Devices
Problem: Data source shows "Not connected"
Solutions:
- Verify
hostis set tomodbus-server(notlocalhost) - Check port is
5502 - Enable the data source (may be disabled after import)
- Check SCADA logs:
docker compose logs scadalts
Problem: Values show as "N/A"
Solutions:
- Check data point offset is
100(not40001) - Verify multiplier is
0.01for proper scaling - Enable data point logging
- Check that Modbus server has data in register 100
View all logs:
docker compose logs -fRestart everything:
docker compose down
docker compose up -d --buildCheck service health:
docker compose psClean restart (removes volumes):
docker compose down -v
docker compose up -d --buildModbus Server:
cd modelica-rust-modbus-server
cargo runTesting:
# Run Rust tests
cd modelica-rust-modbus-server
cargo test -- --nocapture
# Run example client
cargo run --example simple_client- Create Modelica model in
modelica-rust-ffi/space-colony-modelica-core/models/ - Build component:
./scripts/build_component.sh YourModel - Add Rust wrapper in
modelica-rust-ffi/src/components/ - Update
build.rsto compile new component - Expose via Modbus registers in
main.rs
# Start services
make all
# Run integration tests
cd tests
python3 integration_test.py| Address | Type | Description | Units | R/W |
|---|---|---|---|---|
| 100 | Holding Register | Temperature | K × 100 | R |
| 101 | Holding Register | Heater State | % (0 or 100) | R |
| 0 | Coil | Heater Control | Boolean | R/W |
All services communicate via Docker network ics:
modbus-server:5502- Physics simulationopenplc:502- PLC Modbus slavescadalts:8080- SCADA web interfacedatabase:3306- MySQL backend
User → SCADA-LTS (read temperature)
↓ Modbus TCP
modbus-server:5502 (read register 100)
↓ FFI
OpenModelica C Runtime (simulate physics)
User → OpenPLC (write heater control)
↓ Modbus TCP
modbus-server:5502 (write coil 0)
↓ FFI
OpenModelica (update heater state)
See CONTRIBUTING.md for guidelines.
MIT License - see LICENSE for details.
- Issues: https://github.com/bondlegend4/V-ICS/issues
- Wiki: https://github.com/bondlegend4/V-ICS/wiki
- Discussions: https://github.com/bondlegend4/V-ICS/discussions
V-ICS - Virtual Industrial Control System for Education and Research