![]() |
CloudTwin
ROS2 Humble
Digital twin for path and trajectory optimisation
|
This project is a proof-of-concept for a Digital Twin application applied to mobile robotics. The core idea is that budget robots with limited onboard processing power offload heavy computation (trajectory planning, dynamic obstacle avoidance, predictive navigation) to their digital twin running on a cloud server — in our case, the local computer.
The digital twin receives real-time data from the robot and the environment, computes the optimal path, and sends back motion commands. This creates a closed-loop feedback system (boucle de rétroaction) detailed in Documentation/pages/architecture.md.
Since we do not have a physical robot, Gazebo Classic 11 simulates the robot navigating a hospital environment. The simulation represents what would be a real robot connected via a 5G local network to the cloud.
For the full system architecture, feedback loop, and data flow diagrams, see Documentation/pages/architecture.md
For the navigation logic layer (crowd avoidance, room commands, voice control), see Documentation/pages/navigation_logic.md
For the delivery optimizer logic and ML travel-time model, see Documentation/pages/delivery_optimization.md
For the ==Foxglo==ve user interface (layout, panels, topic visualisation), see Documentation/pages/==foxglo==ve.md
For the network map (which IP/port each system opens and connects to), see Documentation/pages/network_connections.md
For known issues and their solutions, see Documentation/pages/troubleshooting.md
The project is organised in the following ROS2 packages:
robot_simulation** : World files (.world), saved maps, and the obstacle spawner that injects dynamic human models into the simulation. In a real deployment, this package would be replaced by the physical robot and its sensors.digital_twin** : The core of the project. Cloud-side intelligence that the robot cannot run onboard. Contains Nav2 configuration, hospital map, launch files, and the navigation logic layer (crowd monitor, room interpreter, speech node).bcr_bot** : The simulated robot. A differential drive robot with 2D lidar, camera, and IMU. Used in Gazebo Classic mode.| Component | Technology | Role |
|---|---|---|
| Framework | ROS2 Humble | Robotics middleware |
| Simulation | Gazebo Classic 11 | Robot and environment simulation |
| Robot | bcr_bot | Differential drive with lidar and camera |
| Navigation | Nav2 (DWB local planner) | Path planning and obstacle avoidance |
| Mapping | slam_toolbox | SLAM for map generation |
| Visualisation | ==Foxglo==ve (web) | Real-time monitoring and user interface |
| Speech | faster-whisper | Local speech-to-text for voice commands |
Note: Gazebo Classic and Gazebo Harmonic (gz-tools2) cannot coexist. If Harmonic is installed, remove it first:
sudo apt remove gz-harmonic gz-tools2 ros-humble-ros-gzharmonic*
If bcr_bot is not already in src/:
Install ROS dependencies:
Xacro needs to be installed for this repo. Check the installation process depending on your system.
Build:
Add to ~/.bashrc if not already there:
Simply open a terminal on your Ubuntu computer and navigate to rge repo of the project. Then you only need to launch the simulation.launch.py file as shown below :
Starts Gazebo with the hospital world, bcr_bot, Nav2 navigation, obstacle spawner, digital twin logic, RViz, cmd_vel relay, initial pose publisher, and ==Foxglo==ve bridge:
Wait approximately 30 seconds for everything to initialise.
The launch argument is still named random_obstacle_scenario for compatibility, but the current human scenarios are deterministic and controlled.
The default obstacle mode remains fixed for backward compatibility.
obstacle_mode:=fixed launches the original obstacle_spawner.py.obstacle_mode:=random launches the controlled moving-human spawner.obstacle_mode:=disabled launches the simulation without obstacles.enable_obstacles:=false also disables obstacle spawning.For controlled moving humans, use:
Available controlled scenarios are:
normal: 10 humans follow predefined patrol loops through the hospital.crowd: 18 humans follow predefined patrol loops with wider coverage.emergency: 8 humans follow fixed emergency routes. After emergency_start_time seconds, they move to a separated gathering area. The return phase begins after emergency_duration seconds, and a new emergency can start after complete dispersal when emergency_loop is enabled.The controlled spawner parameters are in robot_simulation/config/random_obstacles_params.yaml. This file configures the map, wall/robot/person safety distances, Gazebo update rate, spawn timeout, and emergency timing. It no longer generates random trajectories.
The spawner publishes /people_positions for the crowd monitor and /people_markers for RViz. The full simulation remaps the enabled Nav2 MarkerArray display to /people_markers, showing each person as a small coloured circle.
The standalone spawner command, with Gazebo already running, is:
If you only want Gazebo, bcr_bot, Nav2, the obstacle mode selected inhospital.launch.py, and ==Foxglo==ve bridge, launch:
simulation.launch.py already includes this layer. Launch it separately only when using hospital.launch.py directly.
Starts the crowd monitor (dynamic map overlay), room interpreter (text commands), and speech node (voice commands):
To enable the simulation logger (records departure/arrival data per room command to simulation_logs/):
Each simulation run produces a JSON file in simulation_logs/ named simulation_<YYYY-MM-DD_HH-MM-SS>.json. The file contains one record per room command:
Field descriptions:
target_position** — theoretical coordinates from room_registry.yamldeparture.robot_position** — where the robot was when the command was receivedarrival.robot_position** — where the robot actually stoppedduration_s** — simulation time elapsed between departure and arrivalposition_error_m** — Euclidean distance between target_position and arrival.robot_position, representing the navigation accuracy errorstatus** — outcome of the navigation: succeeded, aborted, canceled, interrupted (new command received before arrival), or shutdown (node stopped mid-navigation)The ws_command_bridge node runs a WebSocket server on port 9090 (configurable via ws_port launch argument). Any computer on the network can connect without ROS being installed. To disable the WebSocket bridge or change its port on the server side:
Connection:
On connect, the server immediately sends the current navigation state.
Send the robot to a room:
The room field accepts any name or alias from room_registry.yaml (e.g. "room 101", "cuisine", "pharmacy", "charging station"). The command is validated against the registry before being forwarded to the robot — invalid rooms return an error.
List available rooms:
Query current navigation state:
Acknowledgement (sent to the requesting client after a valid room command):
Navigation status (broadcast to all connected clients on state changes):
Possible state values: idle, navigating, arrived, aborted, canceled.
Feedback (forwarded from the room interpreter):
Room list (response to list_rooms):
Error (invalid room, malformed message, etc.):
Here is an example of a ready to run client:
To run it, type a room name to navigate, rooms to list destinations, status to poll, quit to disconnect. The client prints all status updates as they arrive, so you know when the robot has reached its destination before sending the next command.
ws://<ip>:8765 (use localhost if on the same machine, or the digital twin's IP if remote).foxe file) for voice/text destination commandsYou can also send a goal from the terminal:
The hospital map was generated using slam_toolbox. To recreate it or create a map for a different world:
odom, add /map and /bcr_bot/scan topics.| Version | Details |
|---|---|
| V0.1.0 | Repo initialisation with Doxygen configuration |
| V0.1.1 | Tested Doxygen |
| V1.0.1 | Created test Gazebo world and launch script |
| V1.1.0 | Started building the robot_simulation package |
| V2.1.1 | Created kick_off package for centralised launch |
| V2.1.2 | Renamed kick_off to launch_project |
| V2.2.1 | Created the digital_twin package |
| V2.3.0 | Updated setup.py for the digital_twin package |
| V2.3.1 | Created the visualisation package |
| V2.3.2 | Modified Gazebo world for sun/lighting. Updated launch file for Gazebo server |
| V2.3.3 | Created map using SLAM toolbox |
| V3.0.0 | Migration to Gazebo Harmonic + bcr_bot + small_warehouse. Nav2 integration. ==Foxglo==ve bridge. Removed launch_project |
| V3.1.0 | Removed AMCL startup, increased acceleration and speed |
| V3.2.0 | Custom warehouse for better navigation, applied planning |
| V4.0.0 | Changed to Gazebo Classic from Gazebo Harmonic, hospital world with bcr_bot |
| V4.0.1 | Added goal_pose relay for ==Foxglo==ve timestamp fix |
| V4.1.0 | Added bcr_bot to project tree, first version of people spawner |
| V4.1.1 | Updated human spawner logic, fixed non-moving cylinders |
| V4.1.2 | Changed cylinder SDF to Scrub person model |
| V4.1.3 | Updated Nav2 params for narrow doors |
| V4.1.4 | Added /people_positions publisher to obstacle spawner |
| V5.0.0 | YAML registry files for intersections and rooms |
| V6.0.1 | Smart automatic re-navigation based on crowd affluence data |
| V6.0.2 | ==Foxglo==ve layout V1 saved |
| V6.1.0 | Custom ==Foxglo==ve panel for voice/text room commands |
| V6.1.1 | Released version 1.0.0 of ==Foxglo==ve panel |
| V6.2.1 | hospital.launch.py updated to include obstacle_spawner |
| V6.2.2 | Speech node for voice commands, integrated into logic.launch.py and ==Foxglo==ve panel |
| V6.3.0 | Created websocket to receive command from a no ROS system (do not like the idea) and added support to save a simulation in a JSON format |
| V7.0.0 | Included the LGBM AI model and Laravel UI on the project |
| V7.0.1 | Changed minor issues on the Web App laravel side |
Code/src/delivery_optimization is a web application, not a ROS 2 package. It is the Laravel React Starter Kit, more precisely a Laravel 13 back end with a React 19 + Inertia front end, used as a management / supervision dashboard for the delivery robot. It connects to the running digital twin through the DT_SOCKET_* settings in its .env (the ROS side exposes a WebSocket bridge on port 9090, see the WebSocket section above).
A
COLCON_IGNOREfile is placed in this folder socolcon buildskips it
| Component | Requirement | Notes |
|---|---|---|
| PHP | 8.4+ (required bycomposer.lock) | withmbstring xml curl zip gd sqlite3 bcmath intl |
| Composer | latest | PHP package manager |
| Node.js / npm | Node 20+, npm 10+ | front-end build (Vite) |
| Database | SQLite (default) | file atdatabase/database.sqlite |
| Laravel Reverb | bundled | WebSocket server for real-time UI updates |
Notable PHP packages: inertiajs/inertia-laravel (v3), laravel/fortify,laravel/reverb, laravel/wayfinder, maatwebsite/excel (Excel import/export), smalot/pdfparser (PDF parsing). Front-end: React 19, Inertia, TailwindCSS 4, Radix UI, Recharts, laravel-echo + pusher-js (Reverb client).
You can connect on the different accounts using the test credential below by opening the http://localhost:8000 :
Please note that the Web app communictes with the digital twin via a WebSocket that is opened from ROS2 in order to receive the different comands of delivery in ther order that was decided from the delivery optimizer using the LGBM. The websocket is open by default on the port 9090 of then the IP of the machine running ROS2. If everything is running on the same machine, in that case is localhost.
Two helper scripts at the repository root automate everything described above.
Installs (and skips anything already present):
faster-whisper, sounddevice, numpy, pyyaml, websockets)rosdep dependencies and the bcr_bot package (cloned into Code/src/)ppa:ondrej/php) and Composer.env, SQLite database, and migrationsIt does not install ROS 2 Humble itself. If ROS 2 is missing, follow the installation guide first
Prompts for the obstacle scenario, starts the web app in the background with its proccesses (logs to logs/web.log, stopped automatically on Ctrl+C), then builds the ROS 2 workspace and launches the simulation.