In this guide, we’ll build a simple publisher node using Python in ROS.
The node will periodically publish a string message on a topic.
We’ll explain every step clearly and line-by-line, so even if you’re new to ROS you can follow along.
In ROS (Robot Operating System), software is organized into small, independent processes called nodes.
Nodes communicate by publishing and subscribing to topics — named channels that carry specific types of data.
For example:
A camera node might publish images on /camera/image_raw
A motor controller might subscribe to /cmd_vel
to receive movement commands.
Nodes and topics decouple your software, so different parts of your robot can evolve independently and work together via message passing.
We’ll:
Create a new Python package inside an existing ROS workspace
Write a publisher node in Python
Build and run it
Monitor its output via ROS command-line tools
The node will:
Publish a string message on a topic named /chatter
Run in a loop at a fixed frequency
Display a counter alongside the message
If you haven’t created a ROS workspace yet, follow this clear tutorial first:
How to Create a ROS2 Workspace
Once your workspace is ready and sourced, continue from here.
Inside your workspace’s src
folder:
cd ~/catkin_ws/src
catkin_create_pkg tutorial_publisher std_msgs rospy
This creates a new package with dependencies on:
std_msgs
for message types like String
rospy
the Python client library for ROS
Create a scripts/
directory and the Python script inside:
cd tutorial_publisher
mkdir scripts
touch scripts/simple_publisher.py
chmod +x scripts/simple_publisher.py
Now open simple_publisher.py
and write:
#!/usr/bin/env python
import rospy
from std_msgs.msg import String
def publisher():
pub = rospy.Publisher('chatter', String, queue_size=10)
rospy.init_node('simple_publisher_py', anonymous=True)
rate = rospy.Rate(10) # 10Hz
counter = 0
while not rospy.is_shutdown():
msg_str = "Hello world %d" % counter
rospy.loginfo(msg_str)
pub.publish(msg_str)
rate.sleep()
counter += 1
if __name__ == '__main__':
try:
publisher()
except rospy.ROSInterruptException:
pass
Let’s carefully go through this code line by line.
Shebang
#!/usr/bin/env python
Tells Linux to execute the file using the Python interpreter.
Imports
import rospy
from std_msgs.msg import String
rospy
is the ROS 1 Python client library.
String
is a message type from std_msgs
.
Publisher Function
def publisher():
Defines the main function for the publisher node.
Create Publisher
pub = rospy.Publisher('chatter', String, queue_size=10)
Creates a publisher that sends messages of type String
to the topic 'chatter'
.
queue_size
buffers up to 10 messages if subscribers can’t keep up.
Initialize Node
rospy.init_node('simple_publisher_py', anonymous=True)
Initializes a new ROS node called simple_publisher_py
.
anonymous=True
ensures unique node names in case multiple instances run.
Set Publish Rate
rate = rospy.Rate(10) # 10 Hz
Defines a Rate
object to control the loop frequency (10 times per second).
Message Counter
counter = 0
Simple counter to number messages.
Main Loop
while not rospy.is_shutdown():
Loops until ROS is shut down (via Ctrl+C
or shutdown signals).
Publish a Message
msg_str = "Hello world %d" % counter
rospy.loginfo(msg_str)
pub.publish(msg_str)
Formats a message string with the current counter.
Logs it to the terminal.
Publishes it to the topic.
Sleep
rate.sleep()
Sleeps just enough to maintain the desired publish rate (10 Hz).
Increment Counter
counter += 1
Increases message number for next iteration.
Exception Handling
if __name__ == '__main__':
try:
publisher()
except rospy.ROSInterruptException:
pass
Runs the publisher()
function.
Cleanly handles ROS shutdown events.
From your workspace root:
cd ~/ros_cpp_ws
catkin_make
source devel/setup.bash
First, start the ROS master:
roscore
Then, in a new terminal:
source ~/catkin_ws/devel/setup.bash
rosrun tutorial_publisher simple_publisher.py
In another terminal:
rostopic list
Then, in a new terminal:
/chatter
/rosout
/rosout_agg
Check the messages being published:
rostopic echo /chatter
You’ll see:
data: "Hello world 0"
data: "Hello world 1"
...
Check topic information:
rostopic info /chatter
Check message frequency:
rostopic hz /chatter
It should report a rate around 10Hz.