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.