A working digital-twin integration, running locally: a real FlexSim 2027 warehouse model, a Python bridge that exposes its live state over HTTP/JSON, a live dashboard, and a physics-based mock robot fleet standing in for a future ROS2-connected real system. The two sides are compared side by side, so a question like "how many robots before this queue backs up?" has a testable answer today, not just a theoretical one.
On top of that foundation, an evolving Robot Management System (RMS) already schedules tasks against real FlexSim robots: it reads live fleet and queue state through the bridge, picks the best robot for a task with a weighted, tested scoring policy, and dispatches a command back through the same API, all visible on the dashboard. See Vision: toward a Robot Management System (RMS) for what's implemented today versus planned.
One double-click, or four separate ones if you'd rather start each piece on its own.
- Install Python (only once, if you don't already have it): python.org/downloads, download, run the installer, check "Add python.exe to PATH", then Install.
- Download or
git clonethis repository. - Double-click
start_all.batin the repository root. It opens the bridge in its own window (setting things up the first time, about a minute), opens the mock real-environment fleet in a second window, starts a repeating RMS scheduling loop in a third window so the dashboard's "RMS Scheduling Decision" panel keeps updating on its own, and opens your browser to the live dashboard automatically. It waits for each piece to actually be ready rather than guessing a fixed delay, so first-time setup taking a minute or more is fine. Close any of the three windows to stop that piece.
Prefer to start things one at a time (or only some of them)? Every
piece start_all.bat runs also works completely on its own:
| Double-click | Starts |
|---|---|
bridge\start.bat |
Just the bridge + dashboard |
bridge\start_ros2_sim.bat |
Just the mock robot fleet (after the bridge is up) |
bridge\start_rms_loop.bat |
Just the repeating RMS scheduler (needs robot telemetry) |
python examples\live_flexsim_rms_demo.py |
One RMS scheduling run, then exit (needs a command line) |
That's it. http://127.0.0.1:8000/dashboard is now live in your browser.
To connect the actual FlexSim 2027 model instead of just the mock
simulation, see Connecting FlexSim below.
Quick start for developers (command line)
git clone https://github.com/Pouya-Mansournia/flexsim-digital-twin.git
cd flexsim-digital-twin\bridge
.\run.ps1This creates a .venv, installs dependencies, and starts the bridge on
http://127.0.0.1:8000. In a second terminal, to also run the mock real
environment:
cd flexsim-digital-twin\bridge
.venv\Scripts\python.exe ros2_sim\simulator.py --robots 2Run the bridge's test suite:
cd flexsim-digital-twin\bridge
.venv\Scripts\Activate.ps1
pytestrms/ and adapters/ have their own test suite, dependency-free
(stdlib only) and run from the repository root instead:
cd flexsim-digital-twin
pip install pytest
pytestWith the bridge running, see the RMS scheduler make and dispatch one real decision:
cd flexsim-digital-twin
bridge\.venv\Scripts\python.exe examples\live_flexsim_rms_demo.pyflowchart LR
subgraph SIM["Simulation side"]
FX["FlexSim 2027<br/>(DG-FT-01.fsm)<br/>Process Flow loop"]
end
subgraph MID["bridge/ (FastAPI)"]
API["REST API<br/>/api/v1/telemetry<br/>/api/v1/real/telemetry"]
STORE["In-memory state stores<br/>(FlexSim side + real side, kept separate)"]
DASH["Live dashboard<br/>/dashboard"]
API --> STORE --> DASH
end
subgraph REAL["Real-environment side"]
SIMPY["ros2_sim/simulator.py<br/>(mock fleet today)"]
ROS["Future: real ROS2 node<br/>(Phase 2)"]
end
FX -- "HTTP/JSON, every 5s" --> API
SIMPY -- "HTTP/JSON, every 1s" --> API
ROS -. "same API, Phase 2" .-> API
DASH -- "GET /api/v1/real/config" --> SIMPY
- FlexSim side: a Process Flow loop inside the model posts real queue contents, processor utilization, robot position/speed, and throughput counters to the bridge every 5 simulated seconds. No manual steps once it's wired up.
- Bridge: a small FastAPI service that stores the latest telemetry from both sides (FlexSim and the "real" environment) independently, serves it over REST, and renders a live comparison dashboard.
- ros2_sim: a standalone Python simulation of a robot fleet with realistic acceleration and deceleration, a steady tote arrival rate, and a fleet size you can change live from the dashboard, so you can watch backlog grow or drain as robots are added or removed, before any real hardware or ROS2 stack is involved. Phase 2 swaps this for a real ROS2 node behind the same API, with no changes to the bridge.
The actual FlexSim 2027 model (flexsim-model/DG-FT-01.fsm) this project is built against: an Inbound sortation cell feeding an Outbound put-wall/rack area.
The working integration above is the foundation for something bigger: this project is evolving from a FlexSim digital-twin integration into a modular Robot Management System (RMS) for warehouse automation, with mission management, task orchestration, resource scheduling, fleet management, traffic coordination, and workstation integration sitting on top of it.
The design principle stays simple:
FlexSim validates decisions. RMS makes decisions. ROS 2 executes robot-level behavior.
That separation keeps orchestration logic independent of any one simulator, robot vendor, or warehouse system, so the RMS stays useful even if the simulation platform or robot fleet changes underneath it.
flowchart TB
subgraph EXT["Enterprise & external systems"]
WMS["WMS"]
ERP["ERP"]
OMS["OMS"]
MES["MES"]
end
EIM["External interface / API gateway<br/>REST, events, future adapters"]
WMS <--> EIM
ERP <--> EIM
OMS <--> EIM
MES <--> EIM
subgraph RMS["Robot Management System (RMS Core)"]
direction TB
MM["Mission Manager<br/>business-level transport missions"]
TM["Task Manager<br/>mission decomposition & task lifecycle"]
RS["Resource Scheduler<br/>robot / workstation assignment"]
FM["Fleet Manager<br/>availability, capability, battery, state"]
TRM["Traffic Manager<br/>zones, congestion, coordination"]
WM["Workstation Manager<br/>stations, queues, readiness"]
MM --> TM --> RS
RS <--> FM
RS <--> TRM
RS <--> WM
end
EIM <--> MM
subgraph INT["Device & integration layer"]
RAI["Robot Adapter"]
WAI["Workstation / PLC Adapter"]
DTA["Digital Twin Adapter"]
end
FM <--> RAI
TRM <--> RAI
WM <--> WAI
RS <--> DTA
subgraph DT["Digital twin (simulation & validation)"]
FLEX["FlexSim 2027<br/>warehouse model"]
SIM["Scenario & what-if validation"]
FLEX <--> SIM
end
DTA <--> FLEX
subgraph REAL["Real warehouse execution"]
ROS["ROS 2 integration node"]
NAV["Nav2 / robot navigation"]
AMR["AMR fleet"]
PLC["PLC / workstations / automation"]
ROS <--> NAV <--> AMR
end
RAI <--> ROS
WAI <--> PLC
OBS["Dashboard & observability<br/>twin vs. real, KPIs, backlog, fleet state"]
FLEX --> OBS
RMS --> OBS
ROS --> OBS
PLC --> OBS
| Layer | Responsibility |
|---|---|
| WMS / ERP / OMS / MES | Business demand, inventory, orders, warehouse workflows |
| External Interface | Stable boundary between enterprise systems and the RMS |
| Mission Manager | Converts external requests into transport/robotic missions |
| Task Manager | Breaks missions into executable tasks and tracks their lifecycle |
| Resource Scheduler | Picks the best robot/workstation/resource for each task |
| Fleet Manager | Tracks robot capability, availability, battery, and state |
| Traffic Manager | Coordinates shared-space traffic, congestion, zone reservations |
| Workstation Manager | Tracks workstation availability, queues, readiness |
| Device Adapters | Isolate the RMS from robot, PLC, simulator, and vendor protocols |
| FlexSim | Digital twin, capacity analysis, what-if simulation, decision validation |
| ROS 2 / Nav2 | Robot-side execution, navigation, hardware integration |
| Dashboard | Operational observability and digital-twin/real-system comparison |
flowchart TB
BIZ["Business systems"] --> RMSN["RMS<br/>decide & coordinate"]
RMSN --> FS["FlexSim<br/>validate<br/>virtual warehouse"]
RMSN --> R2["ROS 2<br/>execute<br/>real warehouse"]
This lets the RMS keep working even if the simulation platform or robot vendor changes; future adapters can support other simulation or execution environments without rewriting the RMS core.
Today the system closes the observation side of the loop: telemetry flows from the model into the bridge into dashboard state. The RMS extends this into a full loop:
flowchart LR
O["Observe"] --> U["Understand"] --> D["Decide"] --> S["Schedule"] --> DI["Dispatch"] --> E["Execute"] --> O
A future mission might look like:
{
"mission_id": "mission-128",
"type": "move_tote",
"source": "Inbound",
"destination": "Workstation-03",
"priority": 7
}The Resource Scheduler would weigh fleet state (robot availability,
battery, travel cost, current utilization) and assign it, say, to
AMR-02 heading to Workstation-03. The same assignment logic could
then run inside FlexSim for validation, inside the mock environment
during development, or through ROS 2 against a real AMR, without
changing the scheduling code itself.
An initial scheduler score might look like:
score(robot, task) = w1 * travel_cost + w2 * battery_penalty
+ w3 * queue_cost + w4 * utilization_cost
+ w5 * priority_penalty
starting with deterministic heuristics (nearest-available, FIFO, priority-based, battery-aware, congestion-aware) before anything optimization- or learning-based, so results stay understandable and reproducible. FlexSim becomes the environment for comparing these policies under repeatable conditions before any of them touch a real robot.
Phase 1, Digital Twin Foundation, done and working:
- FlexSim 2027 warehouse model talking to a FastAPI bridge over HTTP/JSON.
- Queue, processor, robot, and throughput telemetry.
- Separate real/mock environment telemetry channel.
- Live dashboard, configurable mock robot fleet, command API, automated tests, verified FlexScript integration.
Phase 2, close the simulation control loop, next:
- FlexSim polling and executing commands, with acknowledgment, so the bridge to FlexSim direction is exercised end to end (not just implemented server-side, as it is today).
- Command lifecycle visibility, failure and timeout handling.
Phase 3, RMS Core:
- Domain model, Mission Manager, Task Manager, Resource Scheduler, Fleet Manager, Workstation Manager.
- An initial deterministic scheduling policy and a mission/task state machine, instrumented with scheduler KPIs.
Phase 4, ROS 2 execution:
- A ROS 2 adapter replacing the mock fleet with a ROS 2-connected environment: robot-state ingestion, RMS task dispatch, Nav2 integration, mission feedback, fault and battery/state handling.
Phase 5, traffic management:
- Zone model, route reservations, congestion metrics, deadlock prevention, multi-robot coordination, dynamic replanning signals.
Phase 6, enterprise & workstation integration:
- An external API contract, WMS/OMS/MES adapter patterns, a workstation abstraction, a PLC/OPC UA adapter, inventory/order context.
Phase 7, simulation-validated scheduling:
- Scheduling-policy comparison, fleet-sizing experiments, failure and workstation-outage scenarios, demand-surge and battery/charging scenarios, all evaluated by KPI.
- The RMS owns orchestration. Business-level robot decisions belong in the RMS, not in FlexSim or individual robot controllers.
- FlexSim owns simulation. It models warehouse behavior and validates operational decisions, nothing more.
- ROS 2 owns robot integration. It connects the RMS to navigation, localization, robot drivers, and real hardware.
- Adapters protect the core. The RMS domain shouldn't depend directly on FlexScript, ROS messages, PLC protocols, or WMS-specific schemas.
- Simulation and reality stay distinguishable. Digital-twin state
and real-system state remain independently observable, never merged
(see the note on Reset behavior in
bridge/README.md). - Start deterministic. The first scheduler should be understandable, measurable, and reproducible before any optimization or learning is introduced.
- Every decision should be measurable. Scheduling policies get evaluated against explicit operational KPIs, not intuition.
| Category | Examples |
|---|---|
| Warehouse | Throughput, order cycle time, queue length and wait time, workstation utilization, bottleneck duration |
| Fleet | Robot utilization, idle time, mission completion time, distance traveled, battery/charging time, failed missions |
| Scheduler | Assignment latency, task waiting time, reassignment count, workload balance, congestion impact |
| Digital twin | Simulated vs. real throughput and cycle time, queue deviation, fleet utilization deviation, prediction error |
rms/ and adapters/ already exist, and the first vertical slice
closes end to end against the real bridge/:
FlexSim -> bridge -> FlexSimAdapter.get_robots() -> FleetManager
-> MissionManager.create_mission() -> TaskManager
-> ResourceScheduler -> selected Robot
-> FlexSimAdapter.send_command() -> bridge /api/v1/commands
rms/missions, rms/tasks, rms/fleet, rms/workstations,
rms/scheduler, and rms/services (the orchestrator tying them
together) have working, unit-tested implementations; so does
adapters/flexsim, talking to the real bridge over urllib (no extra
dependency). Run it live yourself, with bridge/ up, via
examples/live_flexsim_rms_demo.py:
python examples\live_flexsim_rms_demo.pyBy default that runs one cycle and exits, matching what most people
want to try first, reading FlexSim's own telemetry channel (needs an
actual FlexSim model running). Pass --loop to keep scheduling
repeatedly (--interval sets the seconds between cycles, default 5),
the same way bridge\start_rms_loop.bat does for the dashboard's
benefit. Pass --source real to schedule against the mock fleet's
telemetry instead, so decisions have something continuously changing
to react to even without FlexSim running:
python examples\live_flexsim_rms_demo.py --loop
python examples\live_flexsim_rms_demo.py --loop --source realThe scheduler's queue_cost and utilization_cost terms are wired in
too now: queue_cost comes from real FlexSim queue backlog via
FlexSimAdapter.get_workstations(), and utilization_cost is the
scheduler's own running per-robot assignment count, for basic load
balancing between otherwise-equal robots. The scheduler also refuses to
dispatch toward a workstation it knows can't accept work right now
(blocked, faulted, starved, or offline): stopping FlexSim marks its
queues offline, and the orchestrator declines instead of sending a
robot toward a queue that isn't moving. rms/traffic now has a real
in-memory zone-reservation implementation too, though it isn't wired
into the scheduler yet. adapters/ (ros2/, plc/, external/) is
still interfaces only.
Every run also shows up on the dashboard: bridge/ has a
POST/GET /api/v1/rms/decision endpoint (read-only observability, the
bridge never acts on it), and the dashboard's "RMS Scheduling Decision"
panel polls it every second, showing the selected robot, score, and
full cost breakdown from the latest posted run. Nothing schedules on a
timer by itself, unlike FlexSim's and the mock fleet's telemetry, so
without --loop (or start_rms_loop.bat) running somewhere, that
panel only updates when you trigger a run yourself.
Unit tests (pytest from the repository root) need nothing running;
one integration test additionally exercises the live path and
self-skips if bridge/ isn't up. See rms/README.md
and adapters/README.md.
flexsim-digital-twin/
├── rms/ RMS core: missions, tasks, scheduler, fleet,
│ traffic, workstations, domain model
├── adapters/ flexsim/, ros2/, plc/, external/
├── bridge/ api/, services/, dashboard/
├── flexsim-model/
├── tests/
├── assets/
└── README.md
adapters/flexsim and rms/services/orchestrator.py are wired into
bridge/ today, over its existing HTTP API (see the vertical slice
above); rms/traffic and the rest of adapters/ are not yet. See
"Repository layout" below for what exists on disk right now.
flexsim-digital-twin/
├── flexsim-model/ FlexSim 2027 model
│ └── DG-FT-01.fsm
│
├── bridge/ Python middleware (FastAPI + dashboard)
│ ├── app/ Application source
│ │ ├── api/ telemetry, real-environment, rms decisions, commands, dashboard
│ │ ├── models/ Pydantic schemas
│ │ ├── services/ in-memory state stores
│ │ └── core/ config, logging
│ ├── flexsim/ FlexSim-side integration docs
│ │ └── verified_scripts/ tested FlexScript + every gotcha found
│ ├── ros2_sim/ mock real-environment robot fleet
│ ├── tests/ pytest suite (25 tests)
│ ├── start.bat double-click to set up and run just the bridge
│ ├── start_ros2_sim.bat double-click to run just the mock fleet
│ ├── start_rms_loop.bat double-click to run the repeating RMS scheduler
│ ├── run.ps1 what start.bat calls under the hood
│ └── README.md full bridge documentation
│
├── rms/ Robot Management System core (see its README)
│ ├── domain/, missions/, tasks/, fleet/, workstations/,
│ │ scheduler/, traffic/, services/
│ └── README.md
├── adapters/ flexsim/ (implemented), ros2/, plc/, external/ (interfaces)
├── examples/
│ └── live_flexsim_rms_demo.py RMS scheduling run(s) against the real bridge (--loop to repeat)
├── tests/ rms/ + adapters/ unit tests (pytest from repo root)
│
├── start_all.bat double-click to start the bridge + mock fleet + one RMS run
├── assets/ Images used in this README
├── LICENSE
└── README.md you are here
The single most useful entry point once everything is running:
http://127.0.0.1:8000/dashboard.
- FlexSim section: queue levels (current and peak), processor state/utilization, entry/exit throughput counters, robot position/speed.
- Digital Twin Comparison section: FlexSim's queues plotted next to the real/ROS2-side environment's queues, a live fleet-size control for the real environment, a backlog metric with a smoothed trend indicator, and the real side's robot table.
- Light/dark theme, a Reset button, auto-refresh every second. No external JS or CSS dependencies; it's a single self-contained HTML page served by FastAPI.
sequenceDiagram
participant FlexSim as FlexSim 2027
participant Bridge as bridge (FastAPI)
participant Sim as ros2_sim/simulator.py
participant User as Dashboard (browser)
loop every 5 simulated seconds
FlexSim->>Bridge: POST /api/v1/telemetry
end
loop every 1 second
Sim->>Bridge: POST /api/v1/real/telemetry
Sim->>Bridge: GET /api/v1/real/config
end
loop every 1 second
User->>Bridge: GET /api/v1/state
User->>Bridge: GET /api/v1/real/state
Bridge-->>User: latest telemetry, both sides
end
User->>Bridge: POST /api/v1/real/config (change fleet size)
Bridge-->>Sim: (picked up on Sim's next poll)
The two telemetry channels never merge. state_store holds FlexSim's
latest snapshot, real_environment_store holds the real/ROS2 side's, and
the dashboard reads both independently to render the comparison.
start.bat (or .\run.ps1) gives you the bridge and the mock
real-environment side; no FlexSim installation is required for that. To
also see live telemetry from the actual model:
- Install FlexSim 2027 and open
flexsim-model\DG-FT-01.fsm. - Follow
bridge/flexsim/verified_scripts/README.md. It has the exact, tested FlexScript (final_telemetry_custom_code.fsc) to paste in, and every gotcha found getting it working: a Windows Firewall rule that silently blocks FlexSim, a case-sensitive API call that fails with no visible error, object paths that aren't where you'd expect. - Run the model. Its queues, processors, and robots start appearing on the dashboard next to the mock real environment.
The value here isn't only the code. It's a trail of concrete, verified findings from getting a real FlexSim 2027 model talking to external software, none of which were obvious from the documentation:
- FlexSim 2027 has a real
Http.Request/Http.ResponseFlexScript API, but the method enum (Http.Method.Post) is case-sensitive in a way that fails silently: a wrong-case value compiles, runs, and quietly sends aGETinstead of aPOST, with no error anywhere in FlexSim. - Windows Firewall can block FlexSim's outbound traffic, even to
127.0.0.1, with zero error surfaced in FlexSim. Model.find("ObjectName")fails silently (it returns an unusable node, not an error) if the object is nested inside a group or plane rather than at the model root. Four of this model's most important queues (Queue1–Queue4) were nested this way, so telemetry silently read 0 for them until we found it by checking the object's real path in FlexSim's status bar.- Model Parameters used as counters or accumulators need
Continuoustype and explicit, wide bounds.Integertype silently rounds fractional values, and the defaultLower Bound = 1silently clamps any attempt to reset a value to0. - FlexSim has no single built-in "items in/out" counter for an arbitrary
point in a line. This project wires it up using existing Photo Eye
objects'
On Covertrigger, incrementing a named Model Parameter.
The full trail, including the exact FlexScript that works and why, is in
bridge/flexsim/verified_scripts/README.md.
Running this integration surfaced a real bottleneck in DG-FT-01.fsm,
not just a software one. Queue1 (Inbound) was observed with 172 totes
in, 35 out, 137 sitting in the queue, and an average wait time over 12
minutes: a genuine capacity mismatch downstream, visible precisely
because the telemetry was flowing correctly. That's the point of a
digital twin: it surfaces real problems, not only simulated ones.
| Layer | Technology |
|---|---|
| Simulation | FlexSim 2027 (FlexScript) |
| API server | Python 3.11+, FastAPI, Uvicorn |
| Validation | Pydantic |
| Dashboard | Vanilla HTML/CSS/JS, <canvas> charts (no frontend framework, no build step) |
| Real-environment simulator | Python, httpx |
| Testing | pytest |
| Future (Phase 2+) | ROS 2, Nav2, an RMS core in Python, OPC UA/PLC adapters |
- Localhost-only; no authentication or TLS on the bridge's API.
- Single FlexSim model instance and single bridge instance: no multi-tenant routing.
- The mock real-environment fleet (
ros2_sim/simulator.py) is a simplified physics model (constant acceleration, no obstacle avoidance or path planning), not a robotics simulator. It's built to answer fleet-sizing questions, not to validate control algorithms. - The command interface (bridge → FlexSim) is implemented and unit-tested but not yet exercised against a real FlexSim consumer polling and acknowledging commands.
- In-memory storage only; state is lost on restart by design (see
bridge/README.mdfor the persistence-ready interface this is built behind).
Phase 1 (this repository, today) is done and working end to end: FlexSim
to bridge over local HTTP/JSON, a live dashboard with FlexSim-vs-real
comparison, a mock real-environment robot fleet with a live-configurable
size, and a command interface (POST /api/v1/commands, poll, ack) that's
implemented and unit-tested but not yet exercised against a real FlexSim
consumer.
Beyond Phase 1, this project is meant to grow into a full Robot Management System, not just a ROS2 swap-in. See Vision: toward a Robot Management System (RMS) above for the complete phase-by-phase roadmap (closing the FlexSim command loop, the RMS core, ROS 2 execution, traffic management, enterprise integration, and simulation-validated scheduling).
Issues and PRs welcome. See CONTRIBUTING.md for setup, test instructions, and project conventions.

