ROS 2
COMPLETE LEARNING NOTES
From Zero to Building Real Projects
Everything you need to know:
✅ Setting up your ROS 2 Workspace
✅ Understanding Nodes, Topics, Publishers & Subscribers
✅ Creating Packages and Writing Python Nodes
✅ Custom Messages (.msg files)
✅ Real Projects: Publisher/Subscriber + Student Data System
✅ All Errors Explained + How to Fix Them
Written by: mrnobody | ROS 2 (Humble) | Ubuntu 22.04
CHAPTER 1: What is ROS 2?
Before touching a single line of code, let's understand what ROS is and WHY we even need it.
This chapter is for complete beginners — even a 10-year-old should understand this.
1.1 The Robot Problem
Imagine you want to build a robot that:
• Reads data from a camera
• Processes that camera image to detect objects
• Moves motors based on what it sees
• At the same time, reads sensors to avoid walls
Without ROS, you would have to write ALL of this in one giant messy program. That's a
nightmare. 😩
🤔 The Problem Without ROS:
One huge file doing everything
Camera code tangled with motor code
Sensor code mixed with AI code
If one part crashes → everything crashes
Very hard to test or fix individual parts
1.2 ROS to the Rescue!
ROS (Robot Operating System) solves this by letting you break your robot brain into small
programs called NODES. Each node does ONE job.
✅ With ROS:
Camera Node → reads camera
Detector Node → finds objects in image
Motor Node → controls wheels
Sensor Node → reads distance sensors
Each node is separate, small, and easy to fix.
They talk to each other through TOPICS.
1.3 Simple Real-Life Analogy
Think of a restaurant:
Real Life ROS
Restaurant Robot (ROS)
Waiter takes your order Publisher node sends data
Kitchen receives the order Topic carries the message
Chef cooks the food Subscriber node processes data
Multiple waiters & chefs Multiple nodes working together
The restaurant building Your ROS Workspace
1.4 ROS 1 vs ROS 2
You are learning ROS 2 (specifically the version called Humble). Here is why ROS 2 is better:
ROS 1 (Old) ROS 2 (You are learning this)
ROS 1 ROS 2
Old version New, modern version
Works only on Linux Works on Linux, Windows, Mac
Not very secure Secure communication
No real-time support Real-time robot support
Requires a 'ROS Master' No master needed
1.5 Key ROS 2 Vocabulary
Learn these words — you will see them everywhere:
Word Simple Meaning
Word Simple Meaning
Node One small Python program doing one job
Topic A pipe/channel where data flows
Publisher A node that SENDS data
Subscriber A node that RECEIVES data
Message The actual data being sent (like text,
numbers)
Package A folder containing your project code
Workspace Your main project folder (ros2_ws)
Launch File A file that starts multiple nodes at once
1.6 How Nodes Communicate — The Big Picture
Here is the most important concept in ROS. Read this slowly:
Publisher Node Subscriber Node
────────────── ───────────────
│ Sends data │ ──► /topic ──► │ Receives data │
────────────── ───────────────
Example:
You type 'Rohan'
│
▼
Publisher ──► /name_topic ──► Subscriber
│
▼
Prints: 'Hi Rohan'
🧠 Remember This Forever:
Node = one program
Topic = the phone line
Publisher = the caller (sends)
Subscriber = the receiver (listens)
Many publishers can publish to same topic.
Many subscribers can listen to same topic.
CHAPTER 2: Setting Up Your ROS 2 Workspace
This is the very first thing you do when starting any ROS project. Think of the workspace as
your 'office' where all your robot code lives.
2.1 What is a Workspace?
A workspace is just a special folder structure that ROS understands. You always work inside it.
📁 Your Workspace Structure:
ros2_ws/ ← The main workspace folder
├── src/ ← Your packages (code) go here
├── build/ ← ROS builds things here (auto-created)
├── install/ ← Final runnable files go here (auto-created)
└── log/ ← Logs go here (auto-created)
You only work inside src/
ROS manages build/, install/, log/ automatically
2.2 Why Do We Need a Workspace?
ROS does NOT work with random folders. It only recognizes your code if it is inside a proper
workspace. This is like:
📚 Analogy: College Assignment
Teacher: 'Submit in the correct format only'
ROS: 'Put your code inside ros2_ws/src only'
If you put code in a random folder → ROS ignores it!
2.3 Creating Your Workspace — Step by Step
This is what YOU did. Let's understand each command:
Step 1: Create the workspace folder
mkdir -p ~/ros2_ws/src
🔍 What this means:
mkdir = make directory (create folder)
-p = also create parent folders if they don't exist
~/ = your home folder (/home/mrnobody/)
ros2_ws = the workspace folder name
/src = the src folder inside it
Result: /home/mrnobody/ros2_ws/src/ is created
Step 2: Go inside your workspace
cd ~/ros2_ws
🔍 What this means:
cd = change directory (go into a folder)
~/ros2_ws = go to your workspace
⚠️YOUR MISTAKE: You once typed cd ~/ros_ws (missing '2')
Always double-check spelling!
Step 3: Build the workspace
colcon build
🔍 What colcon build does:
colcon = ROS build tool (like a compiler for your packages)
build = compile everything inside src/
After this, you will see:
build/ ← intermediate files
install/ ← runnable files
log/ ← build logs
Every time you change code → run colcon build again
Step 4: Source the workspace
source install/[Link]
🔍 What 'source' does:
source = 'activate' your workspace for the current terminal
Without sourcing → ROS cannot find your nodes!
You must run this every time you open a new terminal.
TIP: Add this to ~/.bashrc to run automatically:
echo 'source ~/ros2_ws/install/[Link]' >> ~/.bashrc
2.4 Useful Terminal Commands (Linux Basics)
These are the Linux commands you used — memorize them!
Command What it does
Command What it does
ls List all files in current folder
cd folder_name Go into a folder
cd .. Go back one folder
cd ~ Go to home folder
pwd Show where you currently are
mkdir folder Create a new folder
touch [Link] Create an empty file
nano [Link] Open file in text editor
rm file Delete a file
rm -r folder Delete a folder and everything inside
chmod +x [Link] Make file executable (runnable)
mv source dest Move file/folder to new location
2.5 The Mistake You Made (Important!)
❌ What Went Wrong:
You ran: ros2 pkg create my_comm_pkg
BUT you were NOT inside ~/ros2_ws/src/
So the package was created in your HOME folder:
/home/mrnobody/my_comm_pkg ← WRONG location
✅ The Fix:
mv ~/my_comm_pkg ~/ros2_ws/src/
This moves the package to the correct location.
📌 Golden Rule: ALWAYS do cd ~/ros2_ws/src FIRST!
Then create your package.
# CORRECT ORDER — do this every time:
cd ~/ros2_ws/src # 1. Go to src first
ros2 pkg create my_pkg --build-type ament_python # 2. Create package
cd ~/ros2_ws # 3. Go to workspace root
colcon build # 4. Build
source install/[Link] # 5. Source
CHAPTER 3: ROS 2 Packages
A package is like a mini-project inside your workspace. Everything you build in ROS lives inside
a package.
3.1 What is a Package?
Think of your workspace like a school bag. Inside the bag you have different notebooks for
different subjects. Each notebook = one package.
School Bag ROS
School Bag Analogy ROS
Your school bag ros2_ws (workspace)
Maths notebook my_comm_pkg (package 1)
Science notebook student_nodes (package 2)
History notebook tutorial_config (package 3)
3.2 Creating a Package
# Always run this from inside ~/ros2_ws/src
cd ~/ros2_ws/src
ros2 pkg create my_package --build-type ament_python
🔍 Breaking down the command:
ros2 pkg create = 'create a new package'
my_package = the name you choose
--build-type = how it will be built
ament_python = we are using Python (not C++)
For C++ you would use: --build-type ament_cmake
3.3 What Gets Created Inside a Package
my_package/
├── [Link] ← ID card of the package
├── [Link] ← tells ROS which files to run
├── [Link] ← extra config (don't touch)
├── resource/
│ └── my_package ← marker file (don't touch)
├── test/ ← for testing (ignore for now)
└── my_package/ ← YOUR CODE GOES HERE
└── __init__.py ← makes it a Python module
3.4 Understanding [Link]
This is like the ID card of your package. ROS reads this to know your package exists.
<?xml version='1.0'?>
<package format='3'>
<name>my_comm_pkg</name> ← package name
<version>0.0.0</version> ← version
<description>My first package</description>
<maintainer email='you@[Link]'>your_name</maintainer>
<license>MIT</license>
<buildtool_depend>ament_python</buildtool_depend>
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
<export>
<build_type>ament_python</build_type>
</export>
</package>
Tag Meaning
Tag Meaning
<package format='3'> Using format version 3 — just leave it, don't
change
<name> Your package's name
<version> Package version — start with 0.0.0
<description> Short description of what it does
<maintainer> Who made it (your name + email)
<license> Legal license — type MIT or Apache-2.0
<buildtool_depend> What tool builds this package
<depend> Other packages this package needs
3.5 Understanding [Link]
This is the MOST IMPORTANT file for running your nodes. It tells ROS which Python files are
runnable commands.
from setuptools import setup
package_name = 'my_package'
setup(
name=package_name,
version='0.0.0',
packages=[package_name],
data_files=[...],
install_requires=['setuptools'],
zip_safe=True,
maintainer='your_name',
entry_points={
'console_scripts': [
'my_node = my_package.my_node:main',
],
},
)
🔍 The entry_points part — MOST IMPORTANT:
'my_node = my_package.my_node:main'
────── ────────── ─────── ────
│ │ │ │
│ │ │ └─ function to run (main)
│ │ └───────── file name (my_node.py)
│ └─────────────────── package folder name
└─────────────────────────────── command name you type
When you type: ros2 run my_package my_node
ROS goes to: my_package/my_node.py → runs main()
⚠️If you forget [Link] entry:
ros2 run my_package my_node
→ Error: Package 'my_package' not found
Always add your node to [Link] before building!
3.6 The Complete Workflow for Any New Package
Every single time you start a new project, follow these exact steps:
# STEP 1: Navigate to src
cd ~/ros2_ws/src
# STEP 2: Create package
ros2 pkg create my_package --build-type ament_python
# STEP 3: Go to your code folder
cd my_package/my_package
# STEP 4: Create your node file
touch my_node.py
chmod +x my_node.py
# STEP 5: Write your code
nano my_node.py
# STEP 6: Edit [Link]
cd .. (go up one level to my_package/)
nano [Link] (add your node to console_scripts)
# STEP 7: Build
cd ~/ros2_ws
colcon build
# STEP 8: Source
source install/[Link]
# STEP 9: Run!
ros2 run my_package my_node
CHAPTER 4: Nodes — The Heart of ROS
A node is just one Python file doing one specific job. All the actual logic of your robot lives in
nodes.
4.1 What is a Node?
🧠 Simple Definition:
Node = One Python program doing ONE job
Examples:
• camera_node.py → reads camera
• motor_node.py → controls motors
• sensor_node.py → reads distance sensor
• publisher_node.py → sends data
• subscriber_node.py → receives data
4.2 Structure of Every ROS Node
Every ROS 2 Python node follows this exact structure. Memorize it!
#!/usr/bin/env python3 # 1. Tell Linux: use Python 3
import rclpy # 2. Import ROS Python library
from [Link] import Node # 3. Import the Node class
class MyNode(Node): # 4. Create your node class
def __init__(self): # 5. Constructor (runs at start)
super().__init__('my_node') # 6. Give node a name
# 7. Your setup code here...
# 8. Your other functions here...
def main(args=None): # 9. Entry point
[Link](args=args) # 10. Start ROS
node = MyNode() # 11. Create node
[Link](node) # 12. Keep node running
node.destroy_node() # 13. Clean up
[Link]() # 14. Stop ROS
if __name__ == '__main__': # 15. Run if executed directly
main()
4.3 Line-by-Line Explanation of Every Part
Line 1: #!/usr/bin/env python3
This is called a 'shebang' line.
It tells Linux: 'Run this file using Python 3'
Without it, Linux won't know which language to use.
Line 2: import rclpy
rclpy = ROS Client Library for Python
It is the main ROS 2 Python package.
Without it, you cannot use ANY ROS features.
Think of it like: 'import the entire ROS toolbox'
Line 3: from [Link] import Node
Node is a class (blueprint) for making ROS programs.
We import it so we can make our own node.
Your class will EXTEND this Node class.
Line 4: class MyNode(Node):
This creates your node class.
MyNode is your custom name — you can call it anything.
(Node) means: inherit everything from the Node class.
So your class gets all of ROS's built-in features.
Line 5 & 6: __init__ and super().__init__
__init__ runs automatically when you create the node.
It is the constructor — 'start-up code'.
super().__init__('my_node')
→ Calls the parent Node's constructor
→ Registers your node in ROS with the name 'my_node'
→ This name shows up in ros2 node list
Line 10: [Link](args=args)
Starts the ROS 2 communication system.
Must be called FIRST before creating any node.
Think of it as: 'Turn ON ROS'
Line 12: [Link](node)
Keeps your node alive and running forever.
Without this → the node starts and immediately stops.
With this → the node keeps listening/processing.
It's like a while True loop, but ROS-managed.
⚠️Publishers that use input() DON'T need spin.
Subscribers ALWAYS need spin.
Lines 13 & 14: destroy_node and shutdown
node.destroy_node() → cleanly stop the node
[Link]() → turn OFF ROS
Always include these at the end for clean exit.
Line 15: if __name__ == '__main__'
This says: 'Only run main() if this file is run directly'
It prevents main() from running if this file is imported
by another file.
Always include this at the bottom of every node file.
4.4 Useful Node Commands
# List all running nodes:
ros2 node list
# Get info about a specific node:
ros2 node info /my_node
# Run a node:
ros2 run my_package my_node
CHAPTER 5: Publisher and Subscriber Nodes
This is the most fundamental communication pattern in ROS. Everything in robotics uses this
idea.
5.1 The Big Idea
📡 Publisher → Topic → Subscriber
Publisher = Sends data (like a radio station broadcasting)
Topic = The channel/frequency (like a radio frequency)
Subscriber = Receives data (like a radio receiver)
Many publishers can publish to ONE topic.
Many subscribers can listen to ONE topic.
They DON'T need to know about each other — just the topic name!
5.2 PROJECT 1: Hi [Name] System
This is the first real project you built! Let's understand every detail.
🎯 Goal:
• You type a name (e.g., 'Rohan') in the terminal
• Publisher sends the name through a topic
• Subscriber receives it and prints: 'Hi Rohan 👋'
The Publisher Node — publisher_node.py
#!/usr/bin/env python3
# Line 1: Tell Linux → use Python 3
import rclpy
# Line 2: Import ROS Python library
from [Link] import Node
# Line 3: We will create a Node
from std_msgs.msg import String
# Line 4: We will send TEXT data (String type)
class MyPublisher(Node):
# Line 5: Create our publisher node class
def __init__(self):
# Line 6: Constructor — runs at startup
super().__init__('my_publisher')
# Line 7: Register this node in ROS as 'my_publisher'
self.publisher_ = self.create_publisher(String, 'name_topic', 10)
# Line 8: CREATE THE PUBLISHER
# String → type of data (text)
# 'name_topic' → topic name (the channel)
# 10 → queue size (store 10 messages max)
self.get_logger().info('Type a name:')
# Line 9: Print a message in the terminal
while True:
# Line 10: Loop forever — keep asking for input
name = input('Enter name: ')
# Line 11: Ask user to type something
msg = String()
# Line 12: Create an empty message object (like a blank box)
[Link] = name
# Line 13: Put the user's input inside the message box
self.publisher_.publish(msg)
# Line 14: SEND the message to the topic!
self.get_logger().info(f'Publishing: {name}')
# Line 15: Print confirmation in terminal
def main(args=None):
[Link](args=args) # Start ROS
node = MyPublisher() # Create the node (this runs __init__)
node.destroy_node() # After input() loop ends, clean up
[Link]() # Stop ROS
if __name__ == '__main__':
main()
The Subscriber Node — subscriber_node.py
#!/usr/bin/env python3
import rclpy
from [Link] import Node
from std_msgs.msg import String
class MySubscriber(Node):
def __init__(self):
super().__init__('my_subscriber')
# Register as 'my_subscriber'
[Link] = self.create_subscription(
String, # type of message
'name_topic', # which topic to listen to
self.listener_callback, # function to call when data arrives
10 # queue size
)
# This sets up the subscription.
# Whenever a message comes to 'name_topic',
# ROS will automatically call listener_callback()
def listener_callback(self, msg):
# This function runs automatically when data arrives
name = [Link]
# Extract the text from the message
self.get_logger().info(f'Hi {name} 👋')
# Print: Hi Rohan 👋
def main(args=None):
[Link](args=args)
node = MySubscriber()
[Link](node) # ← IMPORTANT: keeps subscriber alive forever
node.destroy_node()
[Link]()
if __name__ == '__main__':
main()
5.3 How to Run Both Nodes
⚠️You need TWO terminals!
Because both nodes run at the same time.
# Terminal 1 — Start Subscriber FIRST:
cd ~/ros2_ws
source install/[Link]
ros2 run my_comm_pkg subscriber_node
# Terminal 2 — Start Publisher:
cd ~/ros2_ws
source install/[Link]
ros2 run my_comm_pkg publisher_node
# Now type in Terminal 2:
Enter name: Rohan
# You will see in Terminal 1:
[INFO] Hi Rohan 👋
5.4 [Link] for This Package
entry_points={
'console_scripts': [
'publisher_node = my_comm_pkg.publisher_node:main',
'subscriber_node = my_comm_pkg.subscriber_node:main',
],
},
5.5 Understanding Topics
🔍 Topics are like group chats:
Topic Name = Group chat name
Publisher = Someone sending a message in the group
Subscriber = Someone reading messages from the group
You don't need to know WHO is sending.
You just subscribe to the topic name you care about.
In our project: topic name = 'name_topic'
Both publisher and subscriber use the SAME topic name.
That's how they connect!
# Check all active topics:
ros2 topic list
# See what data is on a topic:
ros2 topic echo /name_topic
# Publish directly from terminal (without a publisher node):
ros2 topic pub /name_topic std_msgs/String "data: 'Rohan'"
5.6 std_msgs — Standard Message Types
std_msgs is a library of pre-built message types for common data:
Message Type What data it carries
Message Type What data it carries
std_msgs/String Text (words, sentences)
std_msgs/Int32 Integer numbers (1, 2, -5)
std_msgs/Float32 Decimal numbers (3.14, -0.5)
std_msgs/Bool True or False
std_msgs/Float64 More precise decimals
# How to use different message types:
from std_msgs.msg import String # for text
from std_msgs.msg import Int32 # for integers
from std_msgs.msg import Float32 # for decimals
from std_msgs.msg import Bool # for true/false
CHAPTER 6: Custom Messages (.msg files)
Standard messages work for simple data. But what if you want to send a student's name, roll
number AND marks all together in one message? You need a CUSTOM MESSAGE.
6.1 Why Custom Messages?
📦 Problem with standard messages:
If you want to send 3 things (name, roll, marks),
you would need 3 separate topics. Very messy!
Solution: Create ONE custom message that holds all 3.
StudentData message:
string name
int32 roll_no
float32 marks
Now you send ONE message with all data inside!
6.2 Creating a Custom Message Package
Custom messages need a SEPARATE CMake package (not Python). This is what
tutorial_config is.
cd ~/ros2_ws/src
ros2 pkg create tutorial_config --build-type ament_cmake
⚠️Notice: ament_cmake (not ament_python)
Custom messages use CMake because they need to be compiled
into code that multiple languages (Python, C++) can use.
6.3 Creating the .msg File
# Create the msg folder inside the package:
mkdir ~/ros2_ws/src/tutorial_config/msg
# Create the message file:
nano ~/ros2_ws/src/tutorial_config/msg/[Link]
Inside [Link], write:
string name
int32 roll_no
float32 marks
📝 .msg file format:
data_type field_name
Available types:
string → text
int32 → whole numbers
float32 → decimal numbers
bool → true/false
int64 → big whole numbers
float64 → very precise decimals
6.4 Setting Up [Link]
This file tells CMake how to build your custom message:
cmake_minimum_required(VERSION 3.8)
project(tutorial_config)
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES 'Clang')
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
find_package(ament_cmake REQUIRED)
find_package(rosidl_default_generators REQUIRED)
# ↑ This finds the tool that generates message code
rosidl_generate_interfaces(${PROJECT_NAME}
# ↑ NOTE: 'interfaces' with an 's' — common mistake!
'msg/[Link]'
# ↑ Path to your .msg file
)
ament_export_dependencies(rosidl_default_runtime)
ament_package()
❌ ERRORS YOU MADE (learn from them!):
1. rosidl_generate_interface ← WRONG (missing 's')
rosidl_generate_interfaces ← CORRECT
2. 'ms/[Link]' ← WRONG (typo)
'msg/[Link]' ← CORRECT
6.5 Setting Up [Link] for Custom Messages
<?xml version='1.0'?>
<package format='3'>
<name>tutorial_config</name>
<version>0.0.0</version>
<description>Custom messages</description>
<maintainer email='you@[Link]'>your_name</maintainer>
<license>MIT</license>
<buildtool_depend>ament_cmake</buildtool_depend>
<buildtool_depend>rosidl_default_generators</buildtool_depend>
<!-- ↑ The tool that generates message code -->
<depend>rosidl_default_runtime</depend>
<!-- ↑ Needed at runtime -->
<member_of_group>rosidl_interface_packages</member_of_group>
<!-- ↑ NOTE: 'interface' NOT 'interfaces' — common mistake! -->
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
<export>
<build_type>ament_cmake</build_type>
</export>
</package>
❌ Your mistake in [Link]:
rosidl_interfaces_packages ← WRONG (extra 's')
rosidl_interface_packages ← CORRECT
6.6 Building the Custom Message
cd ~/ros2_ws
# Build ONLY the message package first:
colcon build --packages-select tutorial_config
source install/[Link]
# Verify message was created:
ros2 interface show tutorial_config/msg/StudentData
🔥 VERY IMPORTANT:
If you change the .msg file AFTER building,
you MUST clean and rebuild:
cd ~/ros2_ws
rm -rf build install log
colcon build
source install/[Link]
Old compiled message definitions get cached!
Cleaning forces a fresh build.
CHAPTER 7: Project 2 — Student Data System
This is the assignment you built! A complete system using custom messages, publisher,
subscriber, and logic. Let's understand every part.
7.1 What the System Does
🎯 System Goal:
1. User types student's name, roll number, marks
2. Publisher puts data into StudentData message
3. Sends it through topic 'student_topic'
4. Subscriber receives it
5. Prints all the data
6. If marks < 40 → prints FAIL ❌
If marks >= 40 → prints PASS ✅
7.2 Package Structure
ros2_ws/
└── src/
├── tutorial_config/ ← custom message package
│ ├── [Link]
│ ├── [Link]
│ └── msg/
│ └── [Link]
│
└── student_nodes/ ← Python nodes package
├── [Link]
├── [Link]
└── student_nodes/
├── __init__.py
├── student_publisher.py
└── student_subscriber.py
7.3 [Link]
# File: tutorial_config/msg/[Link]
string name ← student's name (text)
int32 roll_no ← roll number (whole number)
float32 marks ← marks (decimal allowed: 98.5)
7.4 The Publisher — student_publisher.py
#!/usr/bin/env python3
import rclpy
from [Link] import Node
from tutorial_config.msg import StudentData
# ↑ Import YOUR custom message (not std_msgs)
class StudentPublisher(Node):
def __init__(self):
super().__init__('student_publisher')
self.publisher_ = self.create_publisher(
StudentData, # ← our custom message type
'student_topic', # ← topic name
10
)
while True:
# Get input from user
name = input('Enter name: ')
roll = int(input('Enter roll number: '))
# ↑ int() converts typed text to integer number
marks = float(input('Enter marks: '))
# ↑ float() converts typed text to decimal number
# Create message and fill all fields
msg = StudentData()
[Link] = name
msg.roll_no = roll
[Link] = marks
# Send the message
self.publisher_.publish(msg)
self.get_logger().info(f'Sent: {name}, {roll}, {marks}')
def main(args=None):
[Link](args=args)
node = StudentPublisher()
node.destroy_node()
[Link]()
if __name__ == '__main__':
main()
7.5 The Subscriber — student_subscriber.py
#!/usr/bin/env python3
import rclpy
from [Link] import Node
from tutorial_config.msg import StudentData
class StudentSubscriber(Node):
def __init__(self):
super().__init__('student_subscriber')
[Link] = self.create_subscription(
StudentData, # ← custom message type
'student_topic', # ← same topic as publisher!
[Link], # ← function to run on data
10
)
def callback(self, msg):
# This runs automatically when data arrives
# Print all the data
self.get_logger().info(
f'Name: {[Link]}, Roll: {msg.roll_no}, Marks: {[Link]}'
)
# ─── PASS/FAIL LOGIC ───
if [Link] < 40:
self.get_logger().info('FAIL ❌')
else:
self.get_logger().info('PASS ✅')
def main(args=None):
[Link](args=args)
node = StudentSubscriber()
[Link](node) # ← keep subscriber running
node.destroy_node()
[Link]()
if __name__ == '__main__':
main()
7.6 [Link] for student_nodes
entry_points={
'console_scripts': [
'student_pub = student_nodes.student_publisher:main',
'student_sub = student_nodes.student_subscriber:main',
],
},
7.7 [Link] for student_nodes
Because student_nodes uses the tutorial_config message, it must depend on it:
<depend>rclpy</depend>
<depend>tutorial_config</depend>
<!-- ↑ This tells ROS: I need tutorial_config's messages -->
7.8 Building and Running
cd ~/ros2_ws
# Build message package first:
colcon build --packages-select tutorial_config
source install/[Link]
# Then build everything:
colcon build
source install/[Link]
# Terminal 1 — Subscriber:
ros2 run student_nodes student_sub
# Terminal 2 — Publisher:
ros2 run student_nodes student_pub
7.9 Sample Output
# Publisher terminal:
Enter name: Rohan
Enter roll number: 12
Enter marks: 35
[INFO] Sent: Rohan, 12, 35.0
# Subscriber terminal:
[INFO] Name: Rohan, Roll: 12, Marks: 35.0
[INFO] FAIL ❌
# ───────────────────────
# Publisher terminal:
Enter name: Priya
Enter roll number: 7
Enter marks: 85
[INFO] Sent: Priya, 7, 85.0
# Subscriber terminal:
[INFO] Name: Priya, Roll: 7, Marks: 85.0
[INFO] PASS ✅
CHAPTER 8: Errors You Faced & How to Fix Them
Understanding your own errors is the best way to learn. Here is every error you faced,
explained clearly.
Error 1: Pasting Python Code in Terminal
❌ What Happened:
You pasted Python code directly into the terminal.
Terminal ran it as Linux commands → errors everywhere.
Error: 'import' not found
Error: 'from' not found
Error: syntax error near unexpected token '('
✅ Fix:
NEVER paste code in terminal.
Always open the file first: nano my_file.py
Then paste inside the editor.
📌 Rule: Terminal = Linux commands. .py file = Python code.
Error 2: mkdir nano subscriber_node.py
❌ What Happened:
You typed: mkdir nano subscriber_node.py
This created TWO folders instead of files:
- a folder named 'nano'
- a folder named 'subscriber_node.py'
✅ Fix:
rm -r nano
rm -r subscriber_node.py
touch subscriber_node.py (creates the FILE correctly)
📌 Rule:
mkdir = make FOLDER
touch = make FILE
nano = OPEN file (not make folder!)
Error 3: Package created in wrong location
❌ What Happened:
You ran: ros2 pkg create my_comm_pkg
But ~/ros2_ws/src/ did not exist yet.
So package was created in ~ (home folder). WRONG!
✅ Fix:
mkdir -p ~/ros2_ws/src
mv ~/my_comm_pkg ~/ros2_ws/src/
📌 Rule: ALWAYS cd ~/ros2_ws/src FIRST, then create package.
Error 4: module has no attribute 'main'
❌ Error Message:
AttributeError: module 'my_comm_pkg.publisher_node'
has no attribute 'main'
❌ What Happened:
[Link] said: 'run main() in publisher_node.py'
But publisher_node.py had no main() function.
The file was empty or incomplete.
✅ Fix:
Open the file: nano publisher_node.py
Make sure it has: def main(args=None):
Rebuild: colcon build
📌 Rule: Every node file MUST have a def main() function.
Error 5: rosidl_generate_interface (missing 's')
❌ Error Message:
Unknown CMake command 'rosidl_generate_interface'
❌ What Happened:
You wrote: rosidl_generate_interface
Correct is: rosidl_generate_interfaces (with 's' at end)
✅ Fix:
Open [Link]: nano [Link]
Change: rosidl_generate_interface
To: rosidl_generate_interfaces
📌 This is one of the most common ROS mistakes!
Error 6: 'StudentData' has no attribute 'roll_no'
❌ Error Message:
AttributeError: 'StudentData' object has no attribute 'roll_no'
❌ What Happened:
Your .msg file had a different field name.
e.g., the .msg had 'rollno' but code used 'roll_no'.
They must match EXACTLY.
✅ Fix:
Step 1: Check .msg file: nano .../msg/[Link]
Make sure it says exactly: int32 roll_no
Step 2: Clean and rebuild:
cd ~/ros2_ws
rm -rf build install log
colcon build
source install/[Link]
📌 After changing .msg → always clean rebuild!
Error 7: Package 'sudent_nodes' not found
❌ Error Message:
Package 'sudent_nodes' not found
❌ What Happened:
Simple typo: 'sudent_nodes' instead of 'student_nodes'
You missed the 't' in 'student'.
✅ Fix:
ros2 run student_nodes student_sub ← correct spelling
📌 Always check spelling carefully!
8.1 General Debugging Checklist
When something doesn't work, go through this list:
✅ 1. Did you source the workspace?
source ~/ros2_ws/install/[Link]
✅ 2. Did you colcon build after any changes?
cd ~/ros2_ws && colcon build
✅ 3. Is your node in [Link]?
Check the console_scripts section.
✅ 4. Are you in the right folder?
pwd (shows current folder)
ls (shows files)
✅ 5. Does your node file have def main()?
✅ 6. Are field names in .msg and .py code EXACTLY same?
✅ 7. If .msg changed → rm -rf build install log then rebuild
CHAPTER 9: Quick Reference — Commands Cheat
Sheet
Everything you need in one place. Print this page!
9.1 Workspace Commands
mkdir -p ~/ros2_ws/src # Create workspace
cd ~/ros2_ws # Go to workspace
colcon build # Build everything
colcon build --packages-select pkg_name # Build one package
source install/[Link] # Activate workspace
rm -rf build install log # Clean build (full reset)
9.2 Package Commands
# Create Python package:
ros2 pkg create my_pkg --build-type ament_python
# Create CMake package (for custom messages):
ros2 pkg create my_pkg --build-type ament_cmake
# List all packages:
ros2 pkg list
9.3 Running Nodes
ros2 run package_name node_name # Run a node
ros2 node list # List running nodes
ros2 node info /node_name # Info about a node
9.4 Topic Commands
ros2 topic list # List all topics
ros2 topic echo /topic_name # Print topic data
ros2 topic info /topic_name # Topic details
ros2 topic pub /topic std_msgs/String "data: 'hello'"
9.5 Message Commands
ros2 interface list # List all messages
ros2 interface show std_msgs/msg/String # See message structure
ros2 interface show tutorial_config/msg/StudentData
9.6 Every New Project Checklist
□ cd ~/ros2_ws/src
□ ros2 pkg create pkg_name --build-type ament_python
□ cd pkg_name/pkg_name
□ touch node_name.py
□ chmod +x node_name.py
□ nano node_name.py (write code)
□ cd .. (go to package root)
□ nano [Link] (add node to console_scripts)
□ cd ~/ros2_ws
□ colcon build
□ source install/[Link]
□ ros2 run pkg_name node_name
9.7 Publisher Node Template
#!/usr/bin/env python3
import rclpy
from [Link] import Node
from std_msgs.msg import String # change message type as needed
class MyPublisher(Node):
def __init__(self):
super().__init__('my_publisher')
[Link] = self.create_publisher(String, 'my_topic', 10)
# --- your publishing code here ---
def main(args=None):
[Link](args=args)
node = MyPublisher()
node.destroy_node()
[Link]()
if __name__ == '__main__':
main()
9.8 Subscriber Node Template
#!/usr/bin/env python3
import rclpy
from [Link] import Node
from std_msgs.msg import String # change message type as needed
class MySubscriber(Node):
def __init__(self):
super().__init__('my_subscriber')
[Link] = self.create_subscription(
String, 'my_topic', [Link], 10
)
def callback(self, msg):
# This runs when data arrives
self.get_logger().info(f'Received: {[Link]}')
def main(args=None):
[Link](args=args)
node = MySubscriber()
[Link](node) # ← always spin for subscribers
node.destroy_node()
[Link]()
if __name__ == '__main__':
main()
9.9 Summary Table — Everything in One View
Concept One-Line Explanation
Concept One-Line Explanation
Workspace (ros2_ws) Main folder holding all your packages
Package One project folder with all its code
Node One Python file doing one job
Topic Named channel that carries data between
nodes
Publisher Node that sends data to a topic
Subscriber Node that receives data from a topic
Message The data format sent through topics
std_msgs Built-in message types (String, Int32, etc.)
Custom msg (.msg) Your own data type with multiple fields
colcon build Compile all packages in workspace
source [Link] Activate workspace in current terminal
[Link]() Start the ROS 2 system
[Link]() Keep subscriber running forever
[Link]() Stop the ROS 2 system
create_publisher() Create a channel to send data
create_subscription() Create a listener on a topic
publish(msg) Send a message to topic
[Link] ID card of the package
[Link] Tells ROS which files are runnable
[Link] Build instructions for CMake packages
9.10 The Journey You Completed
🏆 What you learned and built:
✅ Set up ROS 2 workspace from scratch
✅ Created Python packages
✅ Wrote Publisher and Subscriber nodes
✅ Built the Hi [Name] communication system
✅ Created custom message (.msg file)
✅ Built the Student Data System with pass/fail logic
✅ Debugged real errors (the best way to learn!)
✅ Understood every line of ROS Python code
You now understand the CORE of ROS 2.
Everything else (sensors, motors, cameras, AI)
is built on top of exactly what you just learned.
Keep building. Keep breaking things. Keep learning. 🚀