πŸ“Speak Clearly!

gayatri kumargayatri kumar
10 min read

πŸ™οΈ 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 text

  • geometry_msgs/Twist: Speeds and directions

  • sensor_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

TagPurposeWhen it's needed
<build_depend>rosidl_default_generators</build_depend>To build custom messagesAt compile time
<exec_depend>rosidl_default_runtime</exec_depend>To run nodes using custom messagesAt 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

StepAction
βœ… 1Created a new package mood_bot
βœ… 2Defined a custom message Mood.msg
βœ… 3Updated package.xml to support custom messages
βœ… 4Edited 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, your MoodBot crafts a custom Mood 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 πŸ’–πŸ€–

10
Subscribe to my newsletter

Read articles from gayatri kumar directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

gayatri kumar
gayatri kumar