Simulation Quick Starts

Ready to see EMOS in action? This page walks you through launching a full autonomous navigation stack in simulation using either Webots or Gazebo. Each section is self-contained – pick the simulator you prefer and follow along.


Webots Simulator

Launch a full autonomous navigation stack in under 5 minutes.

In this tutorial, we use a single Python script – a “Recipe” – to build a complete point-to-point navigation system. We will use the Webots simulator and a Turtlebot3 to demonstrate how EMOS components link together.

1. Prepare the Environment

To make things easy, we created kompass_sim, a package with ready-to-launch simulation environments.

  1. Build the Simulation: Clone and build the simulator support package in your ROS2 workspace:

    git clone https://github.com/automatika-robotics/kompass-sim.git
    cd .. && rosdep install --from-paths src --ignore-src -r -y
    colcon build --packages-select kompass_sim
    source install/setup.bash
    
  2. Launch Webots: Start the Turtlebot3 simulation world. This will bring up Webots, RViz, and the robot localization nodes:

    ros2 launch kompass_sim webots_turtlebot3.launch.py
    

2. The Navigation Recipe

The power of EMOS lies in its Python API. Instead of complex XML/YAML launch files, you define your navigation logic in a clean script.

Create a file named quick_start.py and paste the following code:

import numpy as np
import os
from ament_index_python.packages import (
    get_package_share_directory,
)

# IMPORT ROBOT CONFIG PRIMITIVES
from kompass.robot import (
    AngularCtrlLimits,
    LinearCtrlLimits,
    RobotGeometry,
    RobotType,
    RobotConfig,
    RobotFrames,
)

# IMPORT EMOS NAVIGATION COMPONENTS
from kompass.components import (
    Controller,
    DriveManager,
    DriveManagerConfig,
    Planner,
    PlannerConfig,
    LocalMapper,
    LocalMapperConfig,
    MapServer,
    MapServerConfig,
    TopicsKeys,
)

# IMPORT ALGORITHMS CONFIG
from kompass.control import ControllersID, MapConfig

# IMPORT ROS PRIMITIVES
from kompass.ros import Topic, Launcher, Event, Action, actions


kompass_sim_dir = get_package_share_directory(package_name="kompass_sim")

# Setup your robot configuration
my_robot = RobotConfig(
    model_type=RobotType.DIFFERENTIAL_DRIVE,
    geometry_type=RobotGeometry.Type.CYLINDER,
    geometry_params=np.array([0.1, 0.3]),
    ctrl_vx_limits=LinearCtrlLimits(max_vel=0.4, max_acc=1.5, max_decel=2.5),
    ctrl_omega_limits=AngularCtrlLimits(
        max_vel=0.4, max_acc=2.0, max_decel=2.0, max_steer=np.pi / 3
    ),
)

# Configure the Global Planner
planner_config = PlannerConfig(loop_rate=1.0)
planner = Planner(component_name="planner", config=planner_config)
planner.run_type = "Timed"

# Configure the motion controller
controller = Controller(component_name="controller")
controller.algorithm = ControllersID.PURE_PURSUIT
controller.direct_sensor = (
    False  # Get local perception from a "map" instead (from the local mapper)
)

# Configure the Drive Manager (Direct commands sending to robot)
driver_config = DriveManagerConfig(
    critical_zone_distance=0.05,
    critical_zone_angle=90.0,
    slowdown_zone_distance=0.3,
)
driver = DriveManager(component_name="drive_manager", config=driver_config)
# Publish Twist or TwistStamped from the DriveManager based on the distribution
if "ROS_DISTRO" in os.environ and (
    os.environ["ROS_DISTRO"] in ["rolling", "jazzy", "kilted"]
):
    cmd_msg_type: str = "TwistStamped"
else:
    cmd_msg_type = "Twist"

driver.outputs(robot_command=Topic(name="/cmd_vel", msg_type=cmd_msg_type))

# Configure a Local Mapper
local_mapper_config = LocalMapperConfig(
    map_params=MapConfig(width=3.0, height=3.0, resolution=0.05)
)
local_mapper = LocalMapper(component_name="mapper", config=local_mapper_config)

# Configure the global Map Server
map_file = os.path.join(kompass_sim_dir, "maps", "turtlebot3_webots.yaml")
map_server_config = MapServerConfig(
    loop_rate=1.0,
    map_file_path=map_file,  # Path to a 2D map yaml file or a point cloud file
    grid_resolution=0.5,
    pc_publish_row=False,
)
map_server = MapServer(component_name="global_map_server", config=map_server_config)

# Setup the launcher
launcher = Launcher()

# Add navigation components
launcher.kompass(
    components=[map_server, controller, planner, driver, local_mapper],
    multiprocessing=True,
)

# Get odom from localizer filtered odom for all components
odom_topic = Topic(name="/odometry/filtered", msg_type="Odometry")
launcher.inputs(location=odom_topic)

# Set the robot config for all components
launcher.robot = my_robot
launcher.frames = RobotFrames(world="map", odom="map", scan="LDS-01")

# Enable the UI
# Outputs: Static Map, Global Plan, Robot Odometry
launcher.enable_ui(
    outputs=[
        map_server.get_out_topic(TopicsKeys.GLOBAL_MAP),
        odom_topic,
        planner.get_out_topic(TopicsKeys.GLOBAL_PLAN),
    ],
)

# Run the Recipe
launcher.bringup()

3. Run and Navigate

Open a new terminal and run your recipe:

python3 quick_start.py

You will see the components starting up in the terminal. Once ready, you have two ways to control the robot.

Option A: The EMOS Web UI

The recipe includes launcher.enable_ui(...), which automatically spins up a lightweight web interface for monitoring and control.

  1. Check Terminal: Look for a log message indicating the UI URL: http://0.0.0.0:5001.

  2. Open Browser: Navigate to that URL.

  3. Send Goal: You will see the map and the robot’s live position. Simply click the publish point button and click anywhere on the map to trigger the Planner and send the robot to that location.

Option B: RViz

If you prefer the standard ROS tools:

  1. Go to the RViz window launched in Step 1.

  2. Select the Publish Point tool (sometimes called Clicked Point) from the top toolbar.

  3. Click anywhere on the map grid.

  4. The robot will plan a path (Blue Line) and immediately start driving.

What just happened?

  • Components: You configured your robot and the navigation components directly in your Python recipe.

  • Launcher: Automatically managed the lifecycle of 5 ROS2 nodes in multi-processing.

  • Web UI: Visualized the map, plan, and odometry topics instantly without installing extra frontend tools.


Gazebo Simulator

Launch a full autonomous navigation stack in under 5 minutes.

In this tutorial, we use a single Python script – a “Recipe” – to build a complete point-to-point navigation system. We will use the Gazebo simulator and a Turtlebot3 Waffle Pi to demonstrate how EMOS components link together.

1. Install Gazebo

If you haven’t already, install the default Gazebo version for your ROS distribution (replace ${ROS_DISTRO} with humble, jazzy, or rolling):

sudo apt-get install ros-${ROS_DISTRO}-ros-gz

2. Prepare the Environment

To make things easy, we created kompass_sim, a package with ready-to-launch simulation environments.

  1. Build the Simulation: Clone and build the simulator support package in your ROS2 workspace:

git clone https://github.com/automatika-robotics/kompass-sim.git
cd .. && rosdep install --from-paths src --ignore-src -r -y
colcon build --packages-select kompass_sim
source install/setup.bash
  1. Set the Model: Tell the simulation to use the “Waffle Pi” model:

export TURTLEBOT3_MODEL=waffle_pi
  1. Launch Gazebo: Start the Turtlebot3 house simulation. This will bring up Gazebo, RViz, and the localization nodes:

ros2 launch kompass_sim gazebo_turtlebot3_house.launch.py

3. The Navigation Recipe

The power of EMOS lies in its Python API. Instead of complex XML/YAML launch files, you define your navigation logic in a clean script.

Create a file named quick_start_gz.py and paste the following code:

import numpy as np
import os
from ament_index_python.packages import get_package_share_directory

# IMPORT ROBOT CONFIG PRIMITIVES
from kompass.robot import (
    AngularCtrlLimits,
    LinearCtrlLimits,
    RobotGeometry,
    RobotType,
    RobotConfig,
    RobotFrames,
)

# IMPORT EMOS NAVIGATION COMPONENTS
from kompass.components import (
    Controller,
    DriveManager,
    DriveManagerConfig,
    Planner,
    PlannerConfig,
    LocalMapper,
    LocalMapperConfig,
    MapServer,
    MapServerConfig,
    TopicsKeys,
)

# IMPORT ALGORITHMS CONFIG
from kompass.control import ControllersID, MapConfig

# IMPORT ROS PRIMITIVES
from kompass.ros import Topic, Launcher, Event, Action, actions


kompass_sim_dir = get_package_share_directory(package_name="kompass_sim")

# Setup your robot configuration (Turtlebot3 Waffle Pi)
my_robot = RobotConfig(
    model_type=RobotType.DIFFERENTIAL_DRIVE,
    geometry_type=RobotGeometry.Type.BOX, # Waffle Pi is rectangular
    geometry_params=np.array([0.3, 0.3, 0.2]), # Length, Width, Height
    ctrl_vx_limits=LinearCtrlLimits(max_vel=0.26, max_acc=1.0, max_decel=1.0),
    ctrl_omega_limits=AngularCtrlLimits(
        max_vel=1.8, max_acc=2.0, max_decel=2.0, max_steer=np.pi / 3
    ),
)

# Configure the Global Planner
planner_config = PlannerConfig(loop_rate=1.0)
planner = Planner(component_name="planner", config=planner_config)
planner.run_type = "Timed"

# Configure the motion controller
controller = Controller(component_name="controller")
controller.algorithm = ControllersID.PURE_PURSUIT
controller.direct_sensor = (
    False  # Get local perception from a "map" instead (from the local mapper)
)

# Configure the Drive Manager (Direct commands sending to robot)
driver_config = DriveManagerConfig(
    critical_zone_distance=0.05,
    critical_zone_angle=90.0,
    slowdown_zone_distance=0.3,
)
driver = DriveManager(component_name="drive_manager", config=driver_config)

# Handle Twist/TwistStamped compatibility
if "ROS_DISTRO" in os.environ and (
    os.environ["ROS_DISTRO"] in ["rolling", "jazzy", "kilted"]
):
    cmd_msg_type: str = "TwistStamped"
else:
    cmd_msg_type = "Twist"

driver.outputs(robot_command=Topic(name="/cmd_vel", msg_type=cmd_msg_type))

# Configure a Local Mapper
local_mapper_config = LocalMapperConfig(
    map_params=MapConfig(width=3.0, height=3.0, resolution=0.05)
)
local_mapper = LocalMapper(component_name="mapper", config=local_mapper_config)

# Configure the global Map Server
# Note: We use the 'house' map to match the Gazebo world
map_file = os.path.join(kompass_sim_dir, "maps", "turtlebot3_gazebo_house.yaml")
map_server_config = MapServerConfig(
    loop_rate=1.0,
    map_file_path=map_file,
    grid_resolution=0.5,
    pc_publish_row=False,
)
map_server = MapServer(component_name="global_map_server", config=map_server_config)

# Setup the launcher
launcher = Launcher()

# Add navigation components
launcher.kompass(
    components=[map_server, controller, planner, driver, local_mapper],
    multiprocessing=True,
)

# Get odom from localizer filtered odom for all components
odom_topic = Topic(name="/odometry/filtered", msg_type="Odometry")
launcher.inputs(location=odom_topic)

# Set the robot config and frames
launcher.robot = my_robot
# Standard Gazebo TB3 frames: world=map, odom=odom, scan=base_scan
launcher.frames = RobotFrames(world="map", odom="odom", scan="base_scan")

# Enable the UI
# Outputs: Static Map, Global Plan, Robot Odometry
launcher.enable_ui(
    outputs=[
        map_server.get_out_topic(TopicsKeys.GLOBAL_MAP),
        odom_topic,
        planner.get_out_topic(TopicsKeys.GLOBAL_PLAN),
    ],
)

# Run the Recipe
launcher.bringup()

4. Run and Navigate

Open a new terminal and run your recipe:

python3 quick_start_gz.py

You will see the components starting up in the terminal. Once ready, you have two ways to control the robot.

Option A: The EMOS Web UI

The recipe includes launcher.enable_ui(...), which automatically spins up a lightweight web interface for monitoring and control.

  1. Check Terminal: Look for a log message indicating the UI URL: http://0.0.0.0:5001.

  2. Open Browser: Navigate to that URL.

  3. Send Goal: You will see the map and the robot’s live position. Simply click the publish point button and click anywhere on the map to trigger the Planner and send the robot to that location.

Option B: RViz

If you prefer the standard ROS tools:

  1. Go to the RViz window launched in Step 1.

  2. Select the Publish Point tool (sometimes called Clicked Point) from the top toolbar.

  3. Click anywhere on the map grid.

  4. The robot will plan a path (Blue Line) and immediately start driving.

What just happened?

  • Customization: We adapted the robot configuration (RobotConfig) to match the Waffle Pi’s rectangular geometry and adjusted the RobotFrames to match Gazebo’s standard output (base_scan).

  • Launcher: Managed the lifecycle of the entire stack.

  • Perception: The Local Mapper is processing the Gazebo laser scan to provide obstacle avoidance data to the Controller.


Next Steps

Tip

Check the Point Navigation recipe for a deep dive into these recipes.