A complete software interface system for a ROS 2-based mobile robot with simulation and real-world deployment capabilities. Includes a custom web UI that replaces RViz and teleop tools, built on React + roslibjs + WebSocket communication via rosbridge_server.
- Overview
- System architecture
- Repository structure
- Prerequisites
- Installation
- Running the system
- Web interface
- ROS 2 interfaces
- Node reference
- Configuration
- Communication architecture
- Simulation vs real-world deployment
- Extending the system
- Troubleshooting
- License
This project implements a layered software architecture for a differential-drive mobile robot (QBot). The system covers four layers:
- Web interface layer — React-based dashboard replacing RViz and teleop_twist_keyboard
- Interface contract layer — Custom ROS 2 actions, messages, and services
- Application behavior layer — Navigation server, mapper, odometry, and orchestration nodes
- Robot and simulation infrastructure — URDF/SDF models, Gazebo, ros2_control, and launch system
┌─────────────────────────────────────────────────────────┐
│ Web Interface (React + roslibjs) │
│ Map panel │ Control panel │ Status panel │ Camera feed │
└────────────────────────┬────────────────────────────────┘
│ WebSocket (JSON)
┌────────────────────────▼────────────────────────────────┐
│ rosbridge_server (port 9090) │
└────────────────────────┬────────────────────────────────┘
│ ROS 2 topics / services / actions
┌────────────────────────▼────────────────────────────────┐
│ Interface contract layer │
│ Navigation.action │ Position.msg │ GetLastPositions.srv │
└────────────────────────┬────────────────────────────────┘
│
┌────────────────────────▼────────────────────────────────┐
│ Application behavior layer │
│ navigation_server │ qbot_controller │ mapper_node │
│ odom_node │ lab_06_simulation_launch │
└────────────────────────┬────────────────────────────────┘
│
┌────────────────────────▼────────────────────────────────┐
│ Robot and simulation infrastructure │
│ qbot.urdf │ qbot.sdf │ qbot_world.sdf │ ros_gz_bridge │
│ robot_state_publisher │ ros2_control │ Gazebo │
└─────────────────────────────────────────────────────────┘
TK_botz_workspace/
├── src/
│ ├── qbot_interfaces/ # Interface contract layer
│ │ ├── action/
│ │ │ └── Navigation.action
│ │ ├── msg/
│ │ │ └── Position.msg
│ │ ├── srv/
│ │ │ └── GetLastPositions.srv
│ │ ├── CMakeLists.txt
│ │ └── package.xml
│ │
│ ├── qbot_description/ # Robot models
│ │ ├── urdf/
│ │ │ └── qbot.urdf
│ │ ├── sdf/
│ │ │ ├── qbot.sdf
│ │ │ └── qbot_world.sdf
│ │ └── config/
│ │ └── qbot_controllers.yaml
│ │
│ ├── qbot_bringup/ # Launch and orchestration
│ │ ├── launch/
│ │ │ └── lab_06_simulation_launch.py
│ │ └── package.xml
│ │
│ └── qbot_nodes/ # Application behavior
│ ├── qbot_nodes/
│ │ ├── navigation_server.py
│ │ ├── qbot_controller.py
│ │ ├── mapper_node.py
│ │ └── odom_node.py
│ ├── setup.py
│ └── package.xml
│
└── web_interface/ # Custom web UI
├── public/
├── src/
│ ├── components/
│ │ ├── MapPanel.jsx
│ │ ├── ControlPanel.jsx
│ │ ├── StatusPanel.jsx
│ │ └── CameraFeed.jsx
│ ├── ros/
│ │ └── rosbridge.js # roslibjs connection + topic helpers
│ └── App.jsx
├── package.json
└── README.md
| Dependency | Version | Notes |
|---|---|---|
| Ubuntu | 22.04 LTS | Recommended |
| ROS 2 | Jazzy Jalisco | ros-jazzy-desktop |
| Gazebo | Harmonic | Matches your ros_gz_bridge version |
| ros2_control | ros-jazzy-ros2-control |
|
| slam_toolbox | ros-jazzy-slam-toolbox |
|
| rosbridge_suite | ros-jazzy-rosbridge-server |
|
| ros_gz_bridge | ros-jazzy-ros-gz-bridge |
|
| Node.js | 18+ | For web interface |
| Python | 3.10+ | Node scripts |
mkdir -p ~/qbot_ws/src
cd ~/qbot_ws/src
git clone https://github.com/your-org/qbot.git .cd ~/TK_botz_workspace
sudo apt update
rosdep install --from-paths src --ignore-src -r -ycd ~/TK_botz_workspace
colcon build --symlink-install
source install/setup.bashAdd to your shell profile:
echo "source ~/qbot_ws/install/setup.bash" >> ~/.bashrccd ~/qbot_ws/web_interface
npm installLaunch the full simulation stack — Gazebo, RViz, ros2_control, SLAM, rosbridge, and all application nodes:
ros2 launch qbot_bringup lab_06_simulation_launch.pyThis single launch file starts:
- Gazebo with
qbot_world.sdf robot_state_publisherwithqbot.urdfros_gz_bridgefor topic bridging between Gazebo and ROS 2ros2_controlcontroller spawners (diff_drive_controller, joint_state_broadcaster)slam_toolboxin mapping modenavigation_server,qbot_controller,mapper_node,odom_noderosbridge_serveron port 9090
In a separate terminal:
cd ~/qbot_ws/web_interface
npm startOpen http://localhost:3000 in your browser.
For hardware deployment, launch without Gazebo:
ros2 launch qbot_bringup real_robot_launch.pyEnsure the hardware interface is configured in qbot_controllers.yaml and the robot's serial/USB connection is available.
The interface replaces RViz and teleop_twist_keyboard with a unified browser-based dashboard.
- Renders the live 2D occupancy grid from
/map(published byslam_toolbox) - Overlays robot pose from
/odomand/tf - Click anywhere on free space to set an autonomous navigation goal
- Displays the planned path and lidar scan visualization
- D-pad buttons publish
geometry_msgs/Twistto/cmd_vel - Supports linear velocity (linear.x) and angular velocity (angular.z)
- Autonomous goal panel shows progress feedback from the
Navigation.actionserver
- Robot state badge: Idle / Moving / Goal Reached / Error
- Live topic Hz monitor:
/map,/odom,/scan,/tf,/cmd_vel,/camera/image_raw - ROS console log showing node output
- Subscribes to
/camera/image_raw(Kinect or equivalent) - Streams compressed image data via rosbridge
Goal-based movement interface used by navigation_server.py.
# Goal
float64 target_x
float64 target_y
float64 target_yaw
---
# Result
bool success
string message
---
# Feedback
float64 distance_remaining
float64 progress_percent
string status
Robot pose snapshot stored by mapper_node.
float64 x
float64 y
float64 yaw
builtin_interfaces/Time stamp
Retrieves historical position log from mapper_node.
int32 count # number of positions to retrieve
---
Position[] positions
ROS 2 action server that accepts Navigation.action goals and drives the robot to the target pose. Publishes velocity commands via qbot_controller and streams feedback to the action client.
Topics subscribed: /odom, /tf
Action server: /navigate
Action client that connects to navigation_server. Also acts as a direct velocity publisher for manual teleop commands received from the web interface.
Topics published: /cmd_vel
Action client: /navigate
Stores robot pose snapshots at configurable intervals and serves them via the GetLastPositions service. Integrates with slam_toolbox to correlate positions with map updates.
Topics subscribed: /odom
Services: /get_last_positions
Optional odometry emulation node for simulation environments where hardware encoders are replaced by Gazebo ground-truth pose. Publishes nav_msgs/Odometry to /odom.
Topics published: /odom
controller_manager:
ros__parameters:
update_rate: 100
diff_drive_controller:
ros__parameters:
left_wheel_names: ["left_wheel_joint"]
right_wheel_names: ["right_wheel_joint"]
wheel_separation: 0.35
wheel_radius: 0.05
publish_rate: 50.0
odom_frame_id: odom
base_frame_id: base_link
cmd_vel_topic: /cmd_velslam_toolbox runs in online_async mode by default. To save a map:
ros2 service call /slam_toolbox/save_map slam_toolbox/srv/SaveMap "{name: {data: 'my_map'}}"To switch to localization mode with a saved map, update the launch argument:
ros2 launch qbot_bringup lab_06_simulation_launch.py slam_mode:=localization map:=/path/to/my_map.yamlBrowser (React)
│
│ WebSocket ws://localhost:9090
▼
rosbridge_server
│
├── Subscribe /map → occupancy grid → MapPanel
├── Subscribe /odom → pose → robot marker
├── Subscribe /tf → transform tree
├── Subscribe /camera/image_raw → camera feed
├── Publish /cmd_vel → manual control
├── Service /get_last_positions
└── Action /navigate → goal + feedback + result
The frontend uses roslibjs for all ROS communication:
import ROSLIB from 'roslib';
const ros = new ROSLIB.Ros({ url: 'ws://localhost:9090' });
// Subscribe to map
const mapTopic = new ROSLIB.Topic({
ros, name: '/map', messageType: 'nav_msgs/OccupancyGrid'
});
mapTopic.subscribe(msg => renderMap(msg));
// Publish cmd_vel
const cmdVel = new ROSLIB.Topic({
ros, name: '/cmd_vel', messageType: 'geometry_msgs/Twist'
});
cmdVel.publish(new ROSLIB.Message({ linear: {x: 0.3}, angular: {z: 0.0} }));
// Send navigation goal
const navClient = new ROSLIB.ActionClient({
ros, serverName: '/navigate', actionName: 'qbot_interfaces/action/Navigation'
});
const goal = new ROSLIB.Goal({ actionClient: navClient, goalMessage: { target_x: 1.0, target_y: 2.0, target_yaw: 0.0 } });
goal.on('feedback', fb => console.log(fb.progress_percent));
goal.send();| Feature | Simulation | Real world |
|---|---|---|
| Robot model | qbot.sdf (Gazebo) |
qbot.urdf (hardware) |
| Odometry | odom_node.py (ground truth) |
Hardware encoders via ros2_control |
| Sensors | Gazebo plugins (lidar, camera) | Physical Kinect / RPLidar |
| Bridge | ros_gz_bridge |
Not needed |
| Launch file | lab_06_simulation_launch.py |
real_robot_launch.py |
The interface is designed to be modular. Some suggested extensions:
- Object detection — subscribe to a
/detectionstopic and overlay bounding boxes on the map panel - Dynamic obstacles — visualize costmap inflation layers from
/global_costmap/costmap - Multi-robot support — namespace each robot (
/robot1/cmd_vel,/robot2/cmd_vel) and add a robot selector in the UI - Path recording — use
GetLastPositions.srvto replay recorded routes - 3D visualization — embed a Three.js point cloud renderer subscribed to
/scanor/pointcloud
rosbridge not connecting
Check that rosbridge_server is running: ros2 node list | grep rosbridge. Verify port 9090 is not blocked by a firewall.
Map not rendering
Confirm slam_toolbox is publishing: ros2 topic hz /map. The first map message may take a few seconds after launch.
Robot not moving
Check that the diff_drive_controller is active: ros2 control list_controllers. Ensure /cmd_vel is being received: ros2 topic echo /cmd_vel.
Navigation goal not accepted
Verify navigation_server is running: ros2 node list | grep navigation_server. Check action server is available: ros2 action list.
Gazebo and ROS 2 topics not bridged
Confirm ros_gz_bridge is running and the bridge config matches your topic names and message types.
MIT License. See LICENSE for details.