Replacements for each slide in Hands_on_TurtleBot3.pdf, targeting ROS 2 Jazzy Jalisco on Ubuntu 24.04 with Gazebo Harmonic.
| Area | Humble | Jazzy |
|---|---|---|
| Distro path | /opt/ros/humble |
/opt/ros/jazzy |
| Git branch | -b humble |
-b jazzy |
| Simulator | Gazebo Classic 11 (gazebo_ros) |
Gazebo Harmonic (ros_gz) |
/cmd_vel type |
geometry_msgs/Twist |
geometry_msgs/TwistStamped |
| RViz config | hand-built path into turtlebot3_gazebo |
turtlebot3_bringup rviz2.launch.py |
| TF tool | view_frames.py |
view_frames |
ros2 pkg create |
license optional | warns unless --license given |
Launch file names are unchanged (turtlebot3_world.launch.py, navigation2.launch.py), so most of your ros2 launch lines survive as-is. The three that need real edits are Step 0, the controller script, and the RViz2 slide.
Assume ROS 2 Jazzy + Gazebo Harmonic are already installed (Ubuntu 24.04).
mkdir -p ~/ros2_ws/src
cd ~/ros2_ws/srcSource Jazzy:
source /opt/ros/jazzy/setup.bashInstall the simulation and navigation dependencies:
sudo apt update
sudo apt install -y \
ros-jazzy-ros-gz \
ros-jazzy-navigation2 ros-jazzy-nav2-bringup \
ros-jazzy-cartographer-ros \
python3-colcon-common-extensions python3-rosdepros-jazzy-ros-gz pulls in Gazebo Harmonic. Gazebo Classic is not available on Ubuntu 24.04 — it reached end of life in January 2025, which is why the simulator stack changed.
cd ~/ros2_ws/src
# Core TurtleBot3 packages (Jazzy branch)
git clone -b jazzy \
https://github.com/ROBOTIS-GIT/turtlebot3.git
git clone -b jazzy \
https://github.com/ROBOTIS-GIT/turtlebot3_msgs.git# Gazebo Harmonic simulation for TurtleBot3
git clone -b jazzy \
https://github.com/ROBOTIS-GIT/turtlebot3_simulations.gitgit clone -b jazzy \
https://github.com/ROBOTIS-GIT/DynamixelSDK.gitResult: turtlebot3, turtlebot3_msgs, and turtlebot3_simulations are now in ~/ros2_ws/src.
Shortcut for a lab full of machines: Jazzy binaries exist, so
sudo apt install ros-jazzy-turtlebot3 ros-jazzy-turtlebot3-msgs ros-jazzy-turtlebot3-gazeboskips the clone-and-build entirely. Worth checking withapt search ros-jazzy-turtlebot3before the session — it saves students a 10-minute build.
cd ~/ros2_ws
rosdep install --from-paths src --ignore-src -r -ycd ~/ros2_ws
colcon build --symlink-installsource ~/ros2_ws/install/setup.bash- Repeat the
sourcecommand in every new terminal (or add it to~/.bashrc). - Set the model once in
~/.bashrcso students stop forgetting it:echo 'export TURTLEBOT3_MODEL=burger' >> ~/.bashrc
No changes needed. LaunchDescription, launch_ros.actions.Node, generate_launch_description(), and the setup.py data_files glob all behave identically in Jazzy.
One small addition for the setup.py slide (slide 13) — Jazzy is stricter about package metadata, so make sure setup.py also carries a license field:
# Add to imports:
from glob import glob
import os
# Add to data_files in setup():
(os.path.join('share', package_name, 'launch'), glob('launch/*.py')),
# And in setup():
license='Apache-2.0',# Choose the robot model
export TURTLEBOT3_MODEL=burger
# Launch TurtleBot3 World in Gazebo Harmonic
ros2 launch turtlebot3_gazebo turtlebot3_world.launch.pyCommand is unchanged, but the window that opens is the new Gazebo (Harmonic), not Gazebo Classic. First launch takes longer while resources are fetched.
Other worlds:
ros2 launch turtlebot3_gazebo empty_world.launch.py
ros2 launch turtlebot3_gazebo turtlebot3_house.launch.pyros2 topic listExpected topics are the same, but the type of /cmd_vel has changed:
/cmd_vel— Velocity commands (input) → nowgeometry_msgs/msg/TwistStamped/odom— Odometry data (output)/scan— Laser scan data/imu— Inertial measurement unit data/joint_states— Robot joint states/tfand/tf_static— Transform data
Good live demo to add here:
ros2 topic type /cmd_vel
ros2 interface show geometry_msgs/msg/TwistStampedros2 run turtlebot3_teleop teleop_keyboardCommand and key bindings (W X A D S) are unchanged. Internally the node now publishes TwistStamped.
ros2 topic echo /cmd_vel
ros2 topic echo /odomrqt_graph
rqtUnchanged. Note that ros2 topic echo /cmd_vel output now shows a header block above twist — a natural place to point out the message change.
The old ros2 pkg prefix turtlebot3_gazebo/.rviz path is fragile on Jazzy. Use the bringup launch file instead:
# Launch RViz2 with the TurtleBot3 configuration
ros2 launch turtlebot3_bringup rviz2.launch.py
# Or launch bare and add displays manually
ros2 run rviz2 rviz2Key displays to add (unchanged): TF, LaserScan (/scan), RobotModel, Odometry, PointCloud2.
Unchanged, with one rename: the goal tool in the Jazzy toolbar is labelled "2D Goal Pose" (the Nav2 plugin also exposes it as "Nav2 Goal"), not "2D Nav Goal".
- Global Options → Fixed Frame:
odom - LaserScan → Topic
/scan, Size 0.1 m - Interactive Markers → 2D Pose Estimate, 2D Goal Pose
# View all running nodes
ros2 node list
# View node information
ros2 node info /teleop_keyboard
# View topic message structure
ros2 interface show sensor_msgs/msg/LaserScan
# Monitor specific topic rate
ros2 topic hz /scan
# View TF tree (note: no .py suffix in Jazzy)
ros2 run tf2_tools view_framescd ~/ros2_ws/src
ros2 pkg create turtlebot3_controller \
--build-type ament_python \
--license Apache-2.0 \
--dependencies rclpy geometry_msgs--license is new in practice: Jazzy's ros2 pkg create emits a warning without it, and colcon test complains later.
Create turtlebot3_controller/circle_controller.py.
Three things changed from the Humble version:
Twist→TwistStamped(velocity now lives undermsg.twist, plus aheader).- Publishing once inside
__init__never worked reliably — the publisher has not finished discovery yet, so the single message is dropped. Use a timer. linear.x = 1.0exceeds the burger's maximum of 0.22 m/s; it gets clamped and the circle is not the radius you advertise.
import rclpy
from rclpy.node import Node
from geometry_msgs.msg import TwistStamped
class Circle(Node):
def __init__(self):
super().__init__('turtlebot3_controller')
self.get_logger().info('Node started with turtlebot3_circle')
self.vel_pub = self.create_publisher(TwistStamped, '/cmd_vel', 10)
self.linear_x = 0.15 # m/s (burger max: 0.22 m/s)
self.angular_z = 0.5 # rad/s (burger max: 2.84 rad/s)
radius = self.linear_x / self.angular_z
self.get_logger().info(f'Radius = {radius:.2f} m')
self.timer = self.create_timer(0.1, self.publish_velocity) # 10 Hz def publish_velocity(self):
msg = TwistStamped()
msg.header.stamp = self.get_clock().now().to_msg()
msg.header.frame_id = 'base_link'
msg.twist.linear.x = self.linear_x
msg.twist.angular.z = self.angular_z
self.vel_pub.publish(msg)
def stop(self):
msg = TwistStamped()
msg.header.stamp = self.get_clock().now().to_msg()
msg.header.frame_id = 'base_link'
self.vel_pub.publish(msg)
def main(args=None):
rclpy.init(args=args)
node = Circle()
try:
rclpy.spin(node)
except KeyboardInterrupt:
pass
finally:
node.stop()
node.destroy_node()
if rclpy.ok():
rclpy.shutdown()
if __name__ == '__main__':
main()- Publisher: publishes
TwistStampedvelocity commands to/cmd_vel. - Velocity setup:
twist.linear.xcontrols forward speed,twist.angular.zthe rotation rate. - Radius: r = v / ω, so 0.15 / 0.5 = 0.3 m.
- Stopping on
Ctrl+Cmatters — otherwise the robot keeps circling after the node dies.
The original deck never shows this, but ros2 run turtlebot3_controller circle_controller fails without it:
entry_points={
'console_scripts': [
'circle_controller = turtlebot3_controller.circle_controller:main',
],
},cd ~/ros2_ws
colcon build --symlink-install --packages-select turtlebot3_controllersource ~/ros2_ws/install/setup.bashUnchanged.
export TURTLEBOT3_MODEL=burger
ros2 launch turtlebot3_gazebo turtlebot3_world.launch.pyros2 run turtlebot3_controller circle_controllerResult: The TurtleBot3 follows a circular path of radius ≈ 0.3 m in Gazebo Harmonic.
# Launch Gazebo Harmonic simulation
export TURTLEBOT3_MODEL=burger
ros2 launch turtlebot3_gazebo turtlebot3_world.launch.py# Launch navigation with pre-built map
ros2 launch nav2_bringup bringup_launch.py \
use_sim_time:=false \
map:=$HOME/tb3_world_map.yaml- Simulation:
use_sim_time:=Truefor the Gazebo clock - Real robot:
use_sim_time:=falsefor the system clock - Map path: ensure the path to your saved map file is correct
You need a map before this step. Build one with:
ros2 launch turtlebot3_cartographer cartographer.launch.py use_sim_time:=True ros2 run nav2_map_server map_saver_cli -f ~/map
export TURTLEBOT3_MODEL=burger
ros2 launch turtlebot3_navigation2 navigation2.launch.py \
use_sim_time:=True \
map:=$HOME/map.yamlComponents launched (unchanged): AMCL localization, global + local planners, DWB controller, behavior trees.
Add this note to the slide: Nav2 on Jazzy publishes TwistStamped on /cmd_vel by default. If you are mixing in a node that expects plain Twist, set enable_stamped_cmd_vel: false in the parameter files of both turtlebot3_bringup and turtlebot3_navigation2. Keeping the whole stack on one convention avoids a silent "robot never moves" failure.
Substantively unchanged. Only the toolbar label for the goal tool differs:
- 2D Pose Estimate — same name, same behaviour
- 2D Goal Pose — this was "2D Nav Goal" in the Humble-era RViz2
Troubleshooting notes on both slides still apply.
Unchanged. rqt tools (Topic Monitor, Node Graph, Bag Recording, Plotting, Console, Robot Steering) and the RViz2 displays (Map, Path, LaserScan, TF, Costmaps) all work the same.
One caveat for Robot Steering in rqt: it publishes plain Twist, so on Jazzy it will not drive the robot unless you have switched the stack to enable_stamped_cmd_vel: false. Either mention it or drop it from the list.