Skip to content

Latest commit

Β 

History

History
461 lines (345 loc) Β· 14.6 KB

File metadata and controls

461 lines (345 loc) Β· 14.6 KB

QBot β€” ROS 2 Mobile Robot Platform

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.


Table of contents


Overview

This project implements a layered software architecture for a differential-drive mobile robot (QBot). The system covers four layers:

  1. Web interface layer β€” React-based dashboard replacing RViz and teleop_twist_keyboard
  2. Interface contract layer β€” Custom ROS 2 actions, messages, and services
  3. Application behavior layer β€” Navigation server, mapper, odometry, and orchestration nodes
  4. Robot and simulation infrastructure β€” URDF/SDF models, Gazebo, ros2_control, and launch system

System architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚               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           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Repository structure

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

Prerequisites

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

Installation

1. Create workspace and clone

mkdir -p ~/qbot_ws/src
cd ~/qbot_ws/src
git clone https://github.com/your-org/qbot.git .

2. Install ROS 2 dependencies

cd ~/TK_botz_workspace
sudo apt update
rosdep install --from-paths src --ignore-src -r -y

3. Build the workspace

cd ~/TK_botz_workspace
colcon build --symlink-install
source install/setup.bash

Add to your shell profile:

echo "source ~/qbot_ws/install/setup.bash" >> ~/.bashrc

4. Install web interface dependencies

cd ~/qbot_ws/web_interface
npm install

Running the system

Simulation (Gazebo)

Launch the full simulation stack β€” Gazebo, RViz, ros2_control, SLAM, rosbridge, and all application nodes:

ros2 launch qbot_bringup lab_06_simulation_launch.py

This single launch file starts:

  • Gazebo with qbot_world.sdf
  • robot_state_publisher with qbot.urdf
  • ros_gz_bridge for topic bridging between Gazebo and ROS 2
  • ros2_control controller spawners (diff_drive_controller, joint_state_broadcaster)
  • slam_toolbox in mapping mode
  • navigation_server, qbot_controller, mapper_node, odom_node
  • rosbridge_server on port 9090

Start the web interface

In a separate terminal:

cd ~/qbot_ws/web_interface
npm start

Open http://localhost:3000 in your browser.

Real-world deployment

For hardware deployment, launch without Gazebo:

ros2 launch qbot_bringup real_robot_launch.py

Ensure the hardware interface is configured in qbot_controllers.yaml and the robot's serial/USB connection is available.


Web interface

The interface replaces RViz and teleop_twist_keyboard with a unified browser-based dashboard.

Map panel

  • Renders the live 2D occupancy grid from /map (published by slam_toolbox)
  • Overlays robot pose from /odom and /tf
  • Click anywhere on free space to set an autonomous navigation goal
  • Displays the planned path and lidar scan visualization

Control panel

  • D-pad buttons publish geometry_msgs/Twist to /cmd_vel
  • Supports linear velocity (linear.x) and angular velocity (angular.z)
  • Autonomous goal panel shows progress feedback from the Navigation.action server

Status panel

  • 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

Camera feed

  • Subscribes to /camera/image_raw (Kinect or equivalent)
  • Streams compressed image data via rosbridge

ROS 2 interfaces

Navigation.action

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

Position.msg

Robot pose snapshot stored by mapper_node.

float64 x
float64 y
float64 yaw
builtin_interfaces/Time stamp

GetLastPositions.srv

Retrieves historical position log from mapper_node.

int32 count        # number of positions to retrieve
---
Position[] positions

Node reference

navigation_server.py

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

qbot_controller.py

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

mapper_node.py

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

odom_node.py

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


Configuration

Controller configuration β€” qbot_controllers.yaml

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_vel

SLAM toolbox

slam_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.yaml

Communication architecture

Browser (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();

Simulation vs real-world deployment

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

Extending the system

The interface is designed to be modular. Some suggested extensions:

  • Object detection β€” subscribe to a /detections topic 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.srv to replay recorded routes
  • 3D visualization β€” embed a Three.js point cloud renderer subscribed to /scan or /pointcloud

Troubleshooting

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.


License

MIT License. See LICENSE for details.