All modules
Intermediate25 min read

ROS2 Basics for Robot Builders

Nodes, topics, services, and launch files — the minimum ROS2 knowledge to get a mobile robot navigating. No PhD required.

What ROS2 actually is

ROS2 (Robot Operating System 2) is a middleware for robot software. It solves the messaging problem: how do you get your LIDAR, camera, motor driver, and navigation algorithm to talk to each other without writing custom serial protocols for every connection? ROS2 provides a publish/subscribe message bus, a standardized message format library, and a huge ecosystem of ready-to-use drivers.

On the Room-Nav kit, ROS2 connects: rplidar_ros2 (LIDAR driver) → slam_toolbox (mapping) → nav2 (navigation) → cmd_vel (motor commands) → your motor driver node → Cytron MDD10A. Each component is a “node” publishing and subscribing to “topics”.

Core concepts in 5 minutes

NodeA process that does one thing. LIDAR driver node, camera node, nav2 planner node. Each is independent.
TopicA named message stream. /scan (LIDAR data), /cmd_vel (velocity commands), /odom (odometry). Nodes publish to and subscribe from topics.
Message typeTyped data structure. sensor_msgs/LaserScan for LIDAR, geometry_msgs/Twist for velocity commands. Standard library covers 99% of cases.
ServiceRequest/response instead of stream. Ask a node to do something once and get a reply. Less common than topics.
ActionLong-running task with feedback. nav2 uses actions for “go to this goal pose” — gives progress while navigating.
Launch filePython/XML file that starts multiple nodes with configured parameters. One command launches your whole robot.

Minimal motor driver node (Python)

This is the simplest possible motor driver node — subscribes to /cmd_vel and maps it to PWM signals for the Cytron MDD10A. Based on the Room-Nav kit wiring.

python
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from geometry_msgs.msg import Twist
import RPi.GPIO as GPIO  # or Jetson.GPIO on Orin

PWM_L, PWM_R = 33, 35  # GPIO pin numbers (BOARD mode)
DIR_L, DIR_R = 31, 37

class MotorDriver(Node):
    def __init__(self):
        super().__init__('motor_driver')
        self.sub = self.create_subscription(
            Twist, '/cmd_vel', self.cmd_callback, 10)
        # setup PWM at 1kHz
        GPIO.setmode(GPIO.BOARD)
        for pin in [PWM_L, PWM_R, DIR_L, DIR_R]:
            GPIO.setup(pin, GPIO.OUT)
        self.pwm_l = GPIO.PWM(PWM_L, 1000)
        self.pwm_r = GPIO.PWM(PWM_R, 1000)
        self.pwm_l.start(0)
        self.pwm_r.start(0)

    def cmd_callback(self, msg: Twist):
        linear  = msg.linear.x   # m/s forward
        angular = msg.angular.z  # rad/s rotation
        # differential drive mixing
        left  = (linear - angular * 0.165) / 0.5  # 165mm track, 0.5 m/s max
        right = (linear + angular * 0.165) / 0.5
        self._set_motor(self.pwm_l, DIR_L, left)
        self._set_motor(self.pwm_r, DIR_R, right)

    def _set_motor(self, pwm, dir_pin, val):
        GPIO.output(dir_pin, GPIO.HIGH if val >= 0 else GPIO.LOW)
        pwm.ChangeDutyCycle(min(abs(val) * 100, 100))

def main():
    rclpy.init()
    rclpy.spin(MotorDriver())

Minimal launch file

python (launch file)
# soohoo_robot/launch/bringup.launch.py
from launch import LaunchDescription
from launch_ros.actions import Node

def generate_launch_description():
    return LaunchDescription([
        # LIDAR
        Node(package='rplidar_ros', executable='rplidar_composition',
             parameters=[{'serial_port': '/dev/ttyUSB0', 'frame_id': 'laser'}]),
        # OAK-D (depth + AI)
        Node(package='depthai_ros_driver', executable='camera',
             parameters=[{'i_nn_type': 'mobilenet'}]),
        # Motor driver (custom node above)
        Node(package='soohoo_robot', executable='motor_driver'),
        # nav2 bringup (uses nav2_params.yaml)
        Node(package='nav2_bringup', executable='bringup_launch.py'),
    ])

Run with: ros2 launch soohoo_robot bringup.launch.py

The 5 commands you'll use daily

bash
# List running nodes
ros2 node list

# See what topics are active
ros2 topic list

# Stream a topic (see real sensor data)
ros2 topic echo /scan

# Check topic bandwidth (LIDAR should be ~2kB/s at 2000 samples)
ros2 topic hz /scan

# Send a manual velocity command (test motors without nav2)
ros2 topic pub /cmd_vel geometry_msgs/Twist \
  "{linear: {x: 0.2}, angular: {z: 0.0}}"
SETUP: JetPack 6 + ROS2 Humble
bash
# Install ROS2 Humble on JetPack 6 (Jetson Orin Nano)
# Takes ~20 min from fresh flash
sudo apt update && sudo apt install -y curl gnupg lsb-release
curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key \
  | sudo apt-key add -
echo "deb http://packages.ros.org/ros2/ubuntu $(lsb_release -cs) main" \
  | sudo tee /etc/apt/sources.list.d/ros2.list
sudo apt update
sudo apt install -y ros-humble-desktop ros-humble-nav2-bringup \
  ros-humble-slam-toolbox ros-humble-rplidar-ros
source /opt/ros/humble/setup.bash
READY TO BUILD?

You now have the fundamentals: power budgets, motor control, sensors, and ROS2. The Room-Nav kit is the reference platform this module was written for.

View Room-Nav BOM →