My First ROS2 Robot: Making Turtlesim Move in a Circle

Demo Video: https://drive.google.com/file/d/154eNwBe6CacOM7m-WoXqhVTfeJqjVc1J/view?usp=drive_link
Introduction
Today I built my first ROS2 robot controller.
The goal was simple: create a custom ROS2 node that publishes velocity commands to a robot and makes it move autonomously. Instead of controlling the robot manually with keyboard inputs, I wrote my own ROS2 node that continuously publishes movement commands.
To keep things simple, I used Turtlesim, the standard ROS2 learning simulator.
By the end of this exercise, I learned:
ROS2 Nodes
Topics
Publishers and Subscribers
ROS2 Python Packages
Message Types
The
/cmd_velinterfaceHow robot movement is controlled in ROS2
Environment Setup
Operating System
Ubuntu Linux
ROS2 Workspace
mkdir -p ~/ros2_ws/src
cd ~/ros2_ws
colcon build
source install/setup.bash
Verify ROS2 Installation
ros2 topic list
ros2 node list
ros2 action list
Initially only system topics were available:
/parameter_events
/rosout
Launching Turtlesim
Start the simulator:
ros2 run turtlesim turtlesim_node
Control it manually:
ros2 run turtlesim turtle_teleop_key
This launches a turtle that can be moved using the keyboard.
Exploring ROS2 Topics
List available topics:
ros2 topic list
Output:
/parameter_events
/rosout
/turtle1/cmd_vel
/turtle1/color_sensor
/turtle1/pose
Important topics:
| Topic | Purpose |
|---|---|
/turtle1/cmd_vel |
Movement commands |
/turtle1/pose |
Current position and orientation |
/turtle1/color_sensor |
Color beneath the turtle |
Inspecting Robot State
View live position updates:
ros2 topic echo /turtle1/pose
Example output:
x: 9.40
y: 0.0
theta: -3.13
linear_velocity: 0.0
angular_velocity: 0.0
This provides:
X position
Y position
Orientation (theta)
Current velocity
Understanding Topic Information
Check publisher and subscriber counts:
ros2 topic info /turtle1/pose
Output:
Type: turtlesim/msg/Pose
Publisher count: 1
Subscription count: 0
Meaning:
Turtlesim publishes pose data.
No node is currently subscribing.
Measuring Topic Frequency
Check publishing frequency:
ros2 topic hz /turtle1/pose
Output:
average rate: 62.5 Hz
Interpretation:
The turtle publishes its state approximately 62.5 times per second.
Frequency calculations:
1 / 62.5 = 0.016 seconds
The pose is updated every 16 milliseconds.
This introduced an important robotics concept:
Sensor Frequency (Hz)
Examples:
| Sensor | Typical Frequency |
|---|---|
| Camera | 30 Hz |
| LiDAR | 10 Hz |
| IMU | 200+ Hz |
| Turtlesim Pose | 62.5 Hz |
Creating My First ROS2 Package
Navigate to the workspace source directory:
cd ~/ros2_ws/src
Create a Python package:
ros2 pkg create --build-type ament_python my_first_package
Package structure:
my_first_package/
├── package.xml
├── setup.py
├── setup.cfg
├── resource/
├── test/
└── my_first_package/
Creating My First ROS2 Node
File:
my_first_package/my_first_node.py
Code:
import rclpy
from rclpy.node import Node
class MyFirstNode(Node):
def __init__(self):
super().__init__("my_first_node")
self.get_logger().info("Hello ROS2 developer")
def main(args=None):
rclpy.init(args=args)
node = MyFirstNode()
rclpy.spin(node)
rclpy.shutdown()
if __name__ == "__main__":
main()
Register in setup.py:
entry_points={
'console_scripts': [
'my_first_node = my_first_package.my_first_node:main'
],
}
Build:
cd ~/ros2_ws
colcon build
source install/setup.bash
Run:
ros2 run my_first_package my_first_node
Verify:
ros2 node list
Output:
/my_first_node
Learning Publisher and Subscriber Communication
Publisher Node
Created:
number_publisher.py
Purpose:
Publish messages to:
/robot_news
Output:
Hello robot 0
Hello robot 1
Hello robot 2
...
Run:
ros2 run my_first_package number_publisher
Subscriber Node
Created:
number_subscriber.py
Purpose:
Listen to:
/robot_news
Output:
I heard: Hello robot 0
I heard: Hello robot 1
I heard: Hello robot 2
...
This demonstrated ROS2 communication between nodes.
Architecture:
number_publisher
↓
/robot_news
↓
number_subscriber
Controlling a Robot Using ROS2
The final step was controlling the turtle directly.
Created:
turtle_controller.py
Code:
import rclpy
from rclpy.node import Node
from geometry_msgs.msg import Twist
class TurtleController(Node):
def __init__(self):
super().__init__("turtle_controller")
self.publisher_ = self.create_publisher(
Twist,
"/turtle1/cmd_vel",
10
)
self.timer = self.create_timer(
0.5,
self.move_turtle
)
def move_turtle(self):
msg = Twist()
msg.linear.x = 2.0
msg.angular.z = 1.0
self.publisher_.publish(msg)
def main(args=None):
rclpy.init(args=args)
node = TurtleController()
rclpy.spin(node)
rclpy.shutdown()
if __name__ == "__main__":
main()
Register in setup.py:
'turtle_controller = my_first_package.turtle_controller:main',
Build:
cd ~/ros2_ws
colcon build
source install/setup.bash
Run:
ros2 run my_first_package turtle_controller
Why Did the Turtle Move in a Circle?
The node continuously publishes:
msg.linear.x = 2.0
msg.angular.z = 1.0
Interpretation:
linear.x
→ move forward
angular.z
→ rotate left
The turtle receives both commands simultaneously:
forward motion
+
continuous rotation
=
circular path
Result:
The turtle draws a nearly perfect circle.
What I Learned
By completing this exercise I learned:
✅ ROS2 Workspace Structure
✅ ROS2 Packages
✅ ROS2 Nodes
✅ Topics
✅ Publishers
✅ Subscribers
✅ Message Types
✅ Topic Inspection Tools
✅ Topic Frequencies
✅ /cmd_vel Robot Control
✅ Using Twist Messages
✅ Writing Autonomous Robot Controllers
Connection to Real Robotics
The exact same architecture is used in real robots.
Future project architecture:
Camera Node
↓
YOLO Detection Node
↓
Follower Node
↓
/cmd_vel
↓
Mobile Robot
Today's turtlesim exercise is therefore not just a toy example, it is the foundation of autonomous robot control in ROS2.
Next Steps
Learn ROS2 Services
Learn ROS2 Actions
Learn TF2
Learn URDF Robot Models
Learn Gazebo Simulation
Learn SLAM Toolbox
Learn Nav2 Navigation Stack
Integrate YOLO Object Detection
Build an Autonomous Object-Following Robot
This marks the completion of my first ROS2 robot control project.

