Docs Beginners Workspace repo
Docs menu

Hands-On ROS 2 Primer: Package to Rosbag in Five Checkpoints

A hands-on ROS 2 primer for Racerbot members: your first package, node, topic, launch file and rosbag, each with a working checkpoint, ending where the first autonomous lab begins.

This primer teaches ROS 2 by building, not reading. Five skills, five checkpoints, each one a real thing you run inside your workspace container: a package, a node, a topic, a launch file and a rosbag recording. By the end you have done everything Lab 1 asks for, and you know the moving parts Lab 2 builds on.

It is deliberately short. The official ROS 2 Humble tutorials go much deeper; links to the matching official pages sit beside each section for when you want more.

Before you start: inside the container, source the environment in every terminal you open:

source /opt/ros/humble/setup.bash
source /racerbot_ws/install/local_setup.bash

1. Your first package

A package is the unit ROS 2 builds and ships code in. Everything you write lives in one.

cd /racerbot_ws/src
ros2 pkg create primer_pkg --build-type ament_cmake --dependencies rclcpp std_msgs
cd /racerbot_ws
colcon build
source install/local_setup.bash

Checkpoint: ros2 pkg list | grep primer_pkg prints primer_pkg.

More: official “Creating your first ROS 2 package”, colcon tutorial.

2. Your first node

A node is one running process that does one job - reads a sensor, plans a path, drives a motor. Create src/primer_pkg/src/talker.cpp:

#include <chrono>
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"

using namespace std::chrono_literals;

class Talker : public rclcpp::Node {
public:
  Talker() : Node("talker"), count_(0) {
    pub_ = this->create_publisher<std_msgs::msg::String>("/chatter", 10);
    timer_ = this->create_wall_timer(500ms, [this]() {
      auto msg = std_msgs::msg::String();
      msg.data = "lap " + std::to_string(count_++);
      pub_->publish(msg);
    });
  }
private:
  rclcpp::Publisher<std_msgs::msg::String>::SharedPtr pub_;
  rclcpp::TimerBase::SharedPtr timer_;
  size_t count_;
};

int main(int argc, char ** argv) {
  rclcpp::init(argc, argv);
  rclcpp::spin(std::make_shared<Talker>());
  rclcpp::shutdown();
  return 0;
}

Wire it into src/primer_pkg/CMakeLists.txt (add at the end):

add_executable(talker src/talker.cpp)
ament_target_dependencies(talker rclcpp std_msgs)
install(TARGETS talker DESTINATION lib/${PROJECT_NAME})

Rebuild and run:

cd /racerbot_ws && colcon build && source install/local_setup.bash
ros2 run primer_pkg talker

Checkpoint: in a second sourced terminal, ros2 node list shows /talker.

More: official “Writing a simple publisher and subscriber (C++)”, Understanding nodes.

3. Your first topic

Topics are the named channels nodes communicate on. Your talker is already publishing one, /chatter. Listen to it:

ros2 topic echo /chatter

Checkpoint: lap 0, lap 1, … streaming in. Then inspect it: ros2 topic list, ros2 topic info /chatter, ros2 topic hz /chatter (should report about 2 Hz - the 500 ms timer).

The simulator from the quickstart speaks exactly this language: it publishes /scan and /ego_racecar/odom, and listens on /drive. ros2 topic echo /scan against a running sim is worth a look - that wall of numbers is what your Lab 2 code will parse.

More: Understanding topics.

4. Your first launch file

Launch files start several nodes at once with their parameters - how real runs begin. Create src/primer_pkg/launch/primer.launch.py:

from launch import LaunchDescription
from launch_ros.actions import Node

def generate_launch_description():
    return LaunchDescription([
        Node(
            package="primer_pkg",
            executable="talker",
            name="talker",
            output="screen",
        ),
    ])

Tell CMake to install it - in CMakeLists.txt:

install(DIRECTORY launch DESTINATION share/${PROJECT_NAME})

Rebuild, then:

ros2 launch primer_pkg primer.launch.py

Checkpoint: the talker starts under launch (lap messages scrolling, Ctrl-C stops it). Compare with ros2 launch f1tenth_gym_ros gym_bridge_launch.py - the simulator starts the same way, just with more nodes and a YAML parameter file.

More: official “Creating launch files”.

5. Your first rosbag

A rosbag records topics to disk so you can replay a run without rerunning it. The team uses bags constantly: record the car (or sim), debug at your desk.

ros2 bag record /chatter

Let it run ten seconds, Ctrl-C, then replay with the talker stopped:

ros2 bag play rosbag2_*
ros2 topic echo /chatter   # in a second terminal

Checkpoint: the echoed messages replay from the recording, not from a live node. Record the simulator’s /scan the same way and you have captured sensor data you can develop against offline.

More: official “Recording and playing back data”.

Where this leads

You have now done, by hand, everything Lab 1 assesses: workspace, package, publisher, launch, build. The sequence from here:

  1. Lab 1 (Stage 1 onboarding) - the assessed version of what you just did, in the sim.
  2. Lab 2: Automatic Emergency Braking - subscribe to /scan, compute time-to-collision, publish brake commands on /drive. Nodes, topics and launch files doing something that matters.

When the CLI side of this page felt unfamiliar - paths, tabs, tmux, editing files in a terminal - the terminal foundations guide is the catch-up.

Sources