ROS2 Humble
Hands-On Workshop
FS-AI | March 2026
What is ROS2?
Robot Operating System 2 is a middleware framework: a set of tools, libraries, and conventions for building robot software.
- Standardized communication between processes
- Language-agnostic (Python, C++, and more)
- Built on DDS (Data Distribution Service)
- Real-time capable, production-ready
Why ROS2 for FS?
- Team standard: Nearly all FS-AI teams use ROS2
- Modular architecture: Swap out different perception, planning, or control modules without rewriting the pipeline
- Simulation ready: Integrates directly with IPG CarMaker
- Ready-made piping: Built-in pub/sub, services, and actions: no need to build your own IPC from scratch
Why Humble Hawksbill?
- LTS release: Long Term Support until May 2027; stability matters for a multi-year FS project
- Ubuntu 22.04 locked: Each ROS2 release targets a specific Ubuntu version; Humble requires Jammy (22.04)
- IPG CarMaker compatibility: CarMaker's ROS2 interface is validated on Ubuntu 22.04, matching Humble's target
- Ecosystem maturity: Most ROS2 packages and drivers have stable Humble branches
Our Stack
- Ubuntu 22.04 Jammy
- ROS2 Humble Hawksbill
- IPG CarMaker 2024+
Nodes
A node is a single process that performs one specific task. Each node is independent, runs in its own process, and communicates with others.
FS-AI Example
cone_detector: finds bounding boxes using YOLO inferenceslam: does simultaneous localization and mappingpath_planner: plans the path trajectorycontrol: calculates steering and gas/brake commands
import rclpy
from rclpy.node import Node
class MyNode(Node):
def __init__(self):
super().__init__('my_node')
self.get_logger().info('Started!')
def main():
rclpy.init()
node = MyNode()
rclpy.spin(node)
rclpy.shutdown()
Topics
A topic is a named bus for messages. Nodes publish to or subscribe to topics. It's a one-to-many or many-to-many communication pattern.
Key Properties
- Asynchronous: publisher doesn't wait
- Decoupled: pub/sub don't know each other
- Typed: each topic has a message type
- Named: e.g.
/camera/image_raw
Node
Node
One publisher, multiple topics, one subscriber
Node
One publisher, one topic, many subscribers
Publisher & Subscriber
self.pub = self.create_publisher(
String, # msg type
'my_topic', # topic name
10) # queue size
# Publish on timer (1 Hz)
self.timer = self.create_timer(
1.0, self.timer_cb)
def timer_cb(self):
msg = String()
msg.data = 'Hello ROS2!'
self.pub.publish(msg)
self.sub = self.create_subscription(
String, # msg type
'my_topic', # topic name
self.cb, # callback
10) # queue size
def cb(self, msg):
self.get_logger().info(
f'Got: {msg.data}')
Messages
Messages are typed data structures that flow through topics. ROS2 provides built-in types and you can define custom ones.
Common Built-in Types
std_msgs/String: text datastd_msgs/Int32: integer valuesstd_msgs/Float64: floating-point valuessensor_msgs/Image: camera
string color # "blue", "yellow"
float64 x # meters
float64 y # meters
# msg/ConeArray.msg
std_msgs/Header header
ozu_msgs/Cone[] cones
Custom messages are defined in .msg files and auto-generated into Python/C++ code.
Services
Services are request/response communication. Unlike topics (continuous stream), services are for one-time calls: like asking a question and getting an answer.
Topics vs Services
- Topic: "Here's sensor data" (streaming)
- Service: "Switch to manual mode" (one-shot)
string mode # request
---
bool success # response
string message
ros2 service call /set_mode \
ozu_msgs/srv/SetMode \
"{mode: 'autonomous'}"
Service Server & Client
self.srv = self.create_service(
SetMode,
'set_mode',
self.handle_set_mode)
def handle_set_mode(self, req, res):
self.mode = req.mode
res.success = True
res.message = f'Now: {req.mode}'
return res
self.cli = self.create_client(
SetMode, 'set_mode')
# Wait for service
self.cli.wait_for_service()
# Send request
req = SetMode.Request()
req.mode = 'autonomous'
future = self.cli.call_async(req)
Parameters
Parameters are per-node configuration values that can be set at launch and changed at runtime. No need to recompile!
FS-AI Use Cases
model_path: YOLO .pt model pathconfidence_threshold: YOLO detection thresholduse_sim_time: use simulation timemax_speed: speed limit
self.declare_parameter(
'noise_level', 0.1)
# Read parameter
noise = self.get_parameter(
'noise_level'
).get_parameter_value()
.double_value
ros2 param set /sensor_sim \
noise_level 0.5
# List all params
ros2 param list
Quality of Service (QoS)
QoS profiles control how messages are delivered. Choose the right profile for your data type.
| Policy | Options | When to use |
|---|---|---|
| Reliability | RELIABLE / BEST_EFFORT | Reliable for commands; best-effort for sensors |
| Durability | VOLATILE / TRANSIENT_LOCAL | Transient for late-joining subscribers |
| History | KEEP_LAST(N) / KEEP_ALL | Keep last for sensors; keep all for logs |
| Depth | Queue size (1, 5, 10...) | Small for real-time; larger for buffering |
qos = QoSProfile(depth=10, reliability=ReliabilityPolicy.RELIABLE,
durability=DurabilityPolicy.VOLATILE)
Workspace & Packages
A workspace is your project directory. It contains one or more packages: the unit of organization in ROS2.
src/
ozu_msgs/ # CMake pkg
CMakeLists.txt
package.xml
msg/ srv/
ozu_racing_demo/ # Python pkg
setup.py
package.xml
ozu_racing_demo/
launch/
build/ install/ log/
CMake Package (ament_cmake)
For C++ code and message/service definitions. Uses CMakeLists.txt.
Python Package (ament_python)
For Python code. Uses setup.py instead of CMake.
Build & Source
colcon build: builds everythingsource install/setup.bash: loads built packages
package.xml
Every package has a package.xml: the package manifest. It declares metadata and dependencies.
<package format="3">
<name>ozu_msgs</name>
<version>0.1.0</version>
<description>Custom messages for OzU Racing</description>
<!-- Build type: cmake or python -->
<buildtool_depend>ament_cmake</buildtool_depend>
<buildtool_depend>rosidl_default_generators</buildtool_depend>
<!-- Dependencies: what this package needs -->
<depend>std_msgs</depend>
<depend>builtin_interfaces</depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<member_of_group>rosidl_interface_packages</member_of_group>
</package>
CMakeLists.txt
The build recipe for CMake packages. For ozu_msgs, it generates code from .msg and .srv files.
project(ozu_msgs)
# Find dependencies
find_package(ament_cmake REQUIRED)
find_package(rosidl_default_generators REQUIRED)
find_package(std_msgs REQUIRED)
find_package(builtin_interfaces REQUIRED)
# Generate message & service interfaces
rosidl_generate_interfaces(${PROJECT_NAME}
"msg/Cone.msg"
"msg/ConeArray.msg"
"msg/VehicleCommand.msg"
"srv/SetMode.srv"
DEPENDENCIES std_msgs builtin_interfaces
)
ament_package()
setup.py (Python Package)
Python packages use setup.py instead of CMakeLists. The key part: entry_points: this is how ROS2 discovers your nodes.
setup(
name='ozu_racing_demo',
packages=['ozu_racing_demo'],
data_files=[
# Install launch files
('share/.../launch', ['launch/demo_launch.py']),
],
# THIS registers your nodes as executables
entry_points={
'console_scripts': [
'sensor_simulator = ozu_racing_demo.sensor_simulator:main',
'cone_detector = ozu_racing_demo.cone_detector:main',
'path_planner = ozu_racing_demo.path_planner:main',
],
},
)
Launch Files
Launch files start multiple nodes at once with configured parameters. Written in Python for ROS2.
What launch files do
- Start multiple nodes together
- Set parameters per node
- Define launch arguments (CLI overrides)
- Set up remappings and namespaces
from launch_ros.actions import Node
def generate_launch_description():
return LaunchDescription([
Node(
package='ozu_racing_demo',
executable='sensor_simulator',
parameters=[{
'noise_level': 0.1
}],
),
])
ros2 launch ozu_racing_demo \
demo_launch.py noise_level:=0.2
Our Demo Architecture
A simplified autonomous driving pipeline for Formula Student: sensor to steering.
Simulator
Detector
Planner
Controller
Pre-Built
Sensor Simulator
Cone Detector
Live Coding
Path Planner
Vehicle Controller
Service
/set_mode
autonomous / manual
ROS2 CLI Cheat Sheet
ros2 node info /sensor_simulator
ros2 topic echo /raw_cones
ros2 topic hz /raw_cones
ros2 topic info /raw_cones
ros2 service call /set_mode \
ozu_msgs/srv/SetMode \
"{mode: 'manual'}"
ros2 param get /sensor_sim noise_level
ros2 param set /sensor_sim noise_level 0.5
Debugging & Visualization
rqt_graph
Visualize node connections and topic flow as a graph.
rqt_console
View all log messages from all nodes in one place.
ros2 bag
Record and replay topic data for testing and debugging.
ros2 bag play my_bag/
Pro tip: Run rqt_graph while our demo is running to see the full pipeline visualized as a node graph.
Let's Build
The Planner
Open the guidebook and follow along!
Recap & Resources
What We Covered
- Nodes: independent processes
- Topics: pub/sub data streams
- Messages: typed data structures
- Services: request/response calls
- Parameters: runtime configuration
- QoS: delivery guarantees
- Launch files: multi-node startup
- Workspace & build system
Official Docs
docs.ros.org: Tutorials, API reference
Navigation2
nav2.org: Full autonomous nav stack
Autoware
autoware.org: Open-source self-driving
Questions?
Guidebook, demo code, and install instructions
are all in the ros_lecture/ folder.
Let's build something fast.