πSpeak Clearly!

Table of contents
- ποΈ The Problem in Robot Town...
- π§ What Is a ROS2 Message?
- π Folder Recap So Far
- π οΈ Step 1: Create the mood_bot Package
- π¬ Step 2: Create a msg/ Directory
- ποΈ Step 3: Edit package.xml to Add Message Dependencies
- βοΈ Step 4: Update setup.py to Tell ROS2 About the Message
- π§ Recap So Far
- π§± Visual: Flowchart of Message Sharing
- π¦ Step 5: Add Python Script That Uses the Custom Message
- π Step 6: Tell setup.py About Your Script
- π§ Step 7: Update CMakeLists.txt
- π§± Build Time!
- π‘ Launch MoodBot!
- π§© Mini Project: MoodBot + UmbrellaBot
- ποΈ Folder Recap: Your Current Robot Town Layout
- π§ Whatβs Next: Behind the Scenes in Robot Town β Understanding CMake, Ament, and XML

ποΈ The Problem in Robot Town...
So far, bots like WeatherBot
and UmbrellaBot
have been talking to each other, but only using plain text messages (std_msgs/String
). Thatβs like sending someone a sticky note with just one word: βRainy.β
But what if we want more structured information?
π Something like:
"Mood: π, Battery: 80%, Task: Cleaning"
To do that, robots need custom messages β little letters with specific fields.
π§ What Is a ROS2 Message?
Think of a message like a template letter robots use to communicate.
π¦ Built-in Messages:
ROS2 gives us common ones out of the box:
std_msgs/String
: Just textgeometry_msgs/Twist
: Speeds and directionssensor_msgs/Image
: Camera images
βοΈ Custom Messages:
When basic letters arenβt enough, we write our own message types!
Analogy: It's like designing a form with blanks:
makefileCopyEditMood: π Battery: ___ % Task: ___________
π Folder Recap So Far
Hereβs how your Robot Town workspace should look right now:
robot_town_ws/
βββ install/
βββ build/
βββ log/
βββ src/
βββ mayor_bot/
βββ door_bot/
βββ weather_bot/
βββ umbrella_bot/
βββ mood_bot/ <-- (New!)
Weβll now create a new bot: MoodBot
, who sends and shares moods via custom messages.
π οΈ Step 1: Create the mood_bot
Package
Letβs create a fresh ROS2 Python package that will contain our custom message.
cd ~/robot_town_ws/src
ros2 pkg create mood_bot --build-type ament_python --dependencies rclpy std_msgs
Update the folder layout:
mood_bot/
βββ mood_bot/
β βββ __init__.py
βββ package.xml
βββ setup.py
βββ setup.cfg
Now letβs add the messages to this bot.
π¬ Step 2: Create a msg/
Directory
Inside the mood_bot
folder, we create a folder to hold our custom message definition:
mkdir -p ~/robot_town_ws/src/mood_bot/msg
And add a new file:
cd ~/robot_town_ws/src/mood_bot/msg
nano Mood.msg
Inside Mood.msg
, write:
string mood # π§ The botβs current mood (e.g., "happy", "tired", "angry")
int32 battery # π Battery percentage level (e.g., 95, 60, 10)
string task # π The current task the bot is working on (e.g., "cleaning", "charging")
This is your custom Mood message β a robot-readable letter with 3 fields.
π‘ ROS2 will generate Python classes based on this file so you can use them in your code!
ποΈ Step 3: Edit package.xml
to Add Message Dependencies
In mood_bot/package.xml
, add these lines inside <build_depend>
and <exec_depend>
:
π§± package.xml
dependencies for custom messages
<build_depend>rosidl_default_generators</build_depend>
π§ Build Dependency
This tells ROS 2:
βI want to generate custom interfaces like
.msg
,.srv
, or.action
files.βIt pulls in tools like
rosidl_generator_py
,rosidl_generator_cpp
, etc.Required during the build process so your
.msg
files are converted into code.
<exec_depend>rosidl_default_runtime</exec_depend>
π Execution Dependency
This tells ROS 2:
βWhen my package is run, make sure the generated message code is available.β
It's used at runtime, not during the build.
Ensures your nodes can import and use the generated Python (or C++) message classes.
π¦ <member_of_group>rosidl_interface_packages</member_of_group>
This line is used in your package.xml
when youβre creating a pure interface package β that is, a package that contains only .msg
, .srv
, or .action
files and no actual nodes.
π§ What it does
<member_of_group>rosidl_interface_packages</member_of_group>
This tells the ROS 2 build system:
ββ οΈ Hey! This package is an interface-only package β treat it accordingly!β
It's used by tools like rosidl_default_generators
to:
Identify which packages define interfaces (like messages).
Automatically include all interface packages when needed.
β When to use it
You should include this line in your package.xml
if:
Your package is dedicated solely to defining messages, services, or actions.
You want it to be picked up by generators automatically (e.g., when another package says
rosidl_default_generators
as a dependency, it knows to include yours).
β TL;DR
Tag | Purpose | When it's needed |
<build_depend>rosidl_default_generators</build_depend> | To build custom messages | At compile time |
<exec_depend>rosidl_default_runtime</exec_depend> | To run nodes using custom messages | At runtime |
<member_of_group>rosidl_interface_packages</member_of_group> | Declares this package as a message-only interface package so generators and tools treat it appropriately. |
βοΈ Step 4: Update setup.py
to Tell ROS2 About the Message
Open mood_bot/
setup.py
, and find the setup()
function.
Add:
from setuptools import setup # Imports the setup() function used to define the Python package metadata and installation behavior
import os # Provides functions for interacting with the operating system, like file paths and environment variables
from glob import glob # Used to find all the file names matching a specific pattern, e.g., to include all launch files or resources
Then inside setup(...)
, add this to the end:
data_files=[
# Install the package.xml file into the share/package_name directory
('share/' + package_name, ['package.xml']),
# Install all message definition files (*.msg) found in the msg folder
# into the share/package_name/msg directory using os.path.join for cross-platform paths
(os.path.join('share', package_name, 'msg'), glob('msg/*.msg')),
],
Also add the following in the setup arguments:
'zip_safe': True, # Indicates the package can be safely installed as a zip file (no file access assumptions)
'install_requires': ['setuptools'], # Specifies dependencies required to install this package, here setuptools itself
'packages': [package_name], # Lists the Python packages included in this distribution, usually your package name
'package_dir': {'': ''}, # Maps package directories: here the root '' maps to the current directory ''
π§ Recap So Far
Step | Action |
β 1 | Created a new package mood_bot |
β 2 | Defined a custom message Mood.msg |
β 3 | Updated package.xml to support custom messages |
β 4 | Edited setup.py to include the msg/ folder |
Weβve now laid the foundation for structured robot communication in Robot Town!
π§± Visual: Flowchart of Message Sharing
[ MoodBot (Publisher) ]
|
| Publishes Mood.msg
V
[ Other Bots (Subscribers) ]
This shows that one bot sends rich info (emoji, battery, task), and others react based on what they hear.
π¦ Step 5: Add Python Script That Uses the Custom Message
Now that weβve defined our custom Mood.msg
, letβs create a Python node that sends moods around town.
π Inside your package folder:
cd ~/robot_town_ws/src/mood_bot/mood_bot
touch mood_publisher.py
chmod +x mood_publisher.py
Then edit mood_
publisher.py
:
import rclpy
from rclpy.node import Node
from mood_bot.msg import Mood # Import the custom message type 'Mood' defined in mood_bot/msg/Mood.msg
import random # For generating random moods, battery levels, and tasks
class MoodPublisher(Node):
def __init__(self):
super().__init__('mood_publisher')
# Initialize the node with the name 'mood_publisher'
# Create a publisher that publishes messages of type 'Mood' to the topic 'mood_updates'
# Parameters:
# - Mood: the message type being published (custom message with mood, battery, task fields)
# - 'mood_updates': the topic name where messages are sent
# - 1: the queue size (max number of messages to store if subscribers are slow)
self.publisher_ = self.create_publisher(Mood, 'mood_updates', 1)
# Create a timer that calls self.publish_mood every 2 seconds
# Parameters:
# - 2.0: time interval in seconds
# - self.publish_mood: callback function to invoke each timer tick
self.timer = self.create_timer(2.0, self.publish_mood)
# Log an informational message using the node's logger to announce that the node is active
# self.get_logger() returns the Logger object tied to this node
# .info() logs a message with INFO severity level
self.get_logger().info("MoodBot is now broadcasting feelings!")
def publish_mood(self):
msg = Mood() # Instantiate a new Mood message object
# Assign random values to the message fields:
# 'mood' is a string representing emoji mood states
msg.mood = random.choice(['π', 'π', 'π’', 'π '])
# 'battery' is an integer representing battery percentage between 10 and 100
msg.battery = random.randint(10, 100)
# 'task' is a string representing the current task the bot is performing
msg.task = random.choice(['Cleaning', 'Charging', 'Exploring', 'Idle'])
# Publish the message to the 'mood_updates' topic using the publisher created earlier
self.publisher_.publish(msg)
# Log the published message details so you can see whatβs sent
self.get_logger().info(
f"Broadcasting Mood -> {msg.mood}, Battery: {msg.battery}%, Task: {msg.task}"
)
def main(args=None):
rclpy.init(args=args) # Initialize ROS2 communication
node = MoodPublisher() # Create an instance of the MoodPublisher node
rclpy.spin(node) # Keep the node alive and processing callbacks
node.destroy_node() # Cleanly destroy the node when done
rclpy.shutdown() # Shutdown the ROS2 client library
π§ Whatβs Happening?
We import our custom message:
from mood_bot.msg import Mood
Every 2 seconds, the bot sends a new Mood update
It's random for fun β like emotional radio waves!
π Step 6: Tell setup.py
About Your Script
Back in setup.py
, add this to the entry_points
section:
entry_points={
'console_scripts': [
'mood_publisher = mood_bot.mood_publisher:main'
],
},
π§ Step 7: Update CMakeLists.txt
Navigate to:
cd ~/robot_town_ws/src/mood_bot
Open CMakeLists.txt
and add these lines:
β
Add after find_package(...)
section:
find_package(rosidl_default_generators REQUIRED)
# This command tells CMake to locate the rosidl_default_generators package,
# which provides default generators to create ROS2 interface code from
# message (.msg), service (.srv), and action (.action) definition files.
# The REQUIRED keyword makes the build fail if this package is not found,
# ensuring that the necessary message/service generation tools are available.
β
Below ament_package()
, add this block to build the message:
rosidl_generate_interfaces(${PROJECT_NAME}
"msg/Mood.msg"
)
# This command tells ROS2's build system to generate interface code for
# the custom message defined in "msg/Mood.msg" within the current project.
# ${PROJECT_NAME} is a variable holding the name of your package.
# It generates source code in various languages (e.g., C++, Python)
# so your package can use the custom message type 'Mood' in ROS2 nodes.
β
Add this to the ament_package()
block:
ament_export_dependencies(rosidl_default_runtime)
# This line declares that your package depends on the 'rosidl_default_runtime' package,
# and exports this dependency to any other package that depends on yours.
# 'rosidl_default_runtime' provides the runtime support libraries for
# ROS2 interface types (messages, services, actions),
# so your package and its dependents know to link against these runtime libraries.
Final structure of CMakeLists.txt
will now include:
find_package(ament_cmake REQUIRED)
find_package(rclpy REQUIRED)
find_package(std_msgs REQUIRED)
find_package(rosidl_default_generators REQUIRED)
...
rosidl_generate_interfaces(${PROJECT_NAME}
"msg/Mood.msg"
)
...
ament_export_dependencies(rosidl_default_runtime)
ament_package()
π§± Build Time!
Now letβs build the workspace and generate Python classes for our custom Mood
message.
cd ~/robot_town_ws
colcon build --packages-select mood_bot
π‘ What is colcon build
doing again?
Think of it like a construction crew going through every robot house (package) and setting up wiring, plumbing, and code generation β especially turning
.msg
files into usable Python code!
Once done, source the workspace:
source install/setup.bash
Or permanently add it to your .bashrc
:
echo "source ~/robot_town_ws/install/setup.bash" >> ~/.bashrc
source ~/.bashrc
π‘ Launch MoodBot!
Run the emoji-broadcasting bot:
ros2 run mood_bot mood_publisher
You should see output like:
[INFO] [mood_publisher]: Broadcasting Mood -> π, Battery: 87%, Task: Cleaning
[INFO] [mood_publisher]: Broadcasting Mood -> π, Battery: 45%, Task: Charging
π§ Whatβs happening?
Every 2 seconds, yourMoodBot
crafts a customMood
message and sends it out β like a radio signal carrying rich emotion and intent!
π§© Mini Project: MoodBot + UmbrellaBot
You already have UmbrellaBot
from the article βListening Earsβ. Now, try making it listen to mood_updates
instead of weather
and react to mood!
Ideas:
If mood is π’ or π β Say βSending comfort messageβ¦β
If battery < 20% β Say βRecommend charging soon!β
You can use this custom message anywhere, and even across packages!
ποΈ Folder Recap: Your Current Robot Town Layout
robot_town_ws/
βββ install/
βββ build/
βββ src/
β βββ mayor_bot/
β βββ door_bot/
β βββ weather_bot/
β βββ umbrella_bot/
β βββ mood_bot/
β βββ mood_bot/
β β βββ mood_publisher.py
β βββ msg/
β β βββ Mood.msg
β βββ setup.py
β βββ package.xml
β βββ CMakeLists.txt
π§ Whatβs Next: Behind the Scenes in Robot Town β Understanding CMake, Ament, and XML
Ever wonder how Robot Townβs robots get built under the hood? In the next article, weβll dive deep into the magic behind the scenes β exploring CMake, Ament, and XML.
Get ready to uncover the secrets of how your code transforms into running nodes, and how Robot Townβs build system keeps everything running smoothly. ποΈβ¨
Robot Town is becoming more expressive every day.
Now the bots donβt just talk β they share feelings ππ€
Subscribe to my newsletter
Read articles from gayatri kumar directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by