Meet rclpy
You write ROS 2 nodes in Python using a library called rclpy (ROS Client Library for Python). The pattern for almost every node is the same four steps:
- Create a class that inherits from
Node. - In its constructor, set up publishers, subscribers, and timers.
- Write the logic (a timer callback that publishes, or a subscriber callback that reacts).
- A
main()that starts ROS, spins the node, and cleans up.
Write the publisher
Inside your package from Lesson 2, create a new file. It publishes a counting message to the topic /robot_status twice per second.
~/ros2_ws/src/my_robot/my_robot/status_publisher.py
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
class StatusPublisher(Node):
def __init__(self):
super().__init__('status_publisher') # the node's name
# create_publisher(message_type, topic_name, queue_size)
self.publisher = self.create_publisher(String, 'robot_status', 10)
self.count = 0
# call self.tick() every 0.5 seconds
self.timer = self.create_timer(0.5, self.tick)
self.get_logger().info('status_publisher has started')
def tick(self):
msg = String()
msg.data = f'Robot OK — heartbeat {self.count}'
self.publisher.publish(msg)
self.get_logger().info(f'Publishing: "{msg.data}"')
self.count += 1
def main(args=None):
rclpy.init(args=args) # start up ROS 2
node = StatusPublisher()
rclpy.spin(node) # keep running, firing callbacks
node.destroy_node()
rclpy.shutdown() # clean up
if __name__ == '__main__':
main()
create_publisher(String, 'robot_status', 10) says "I will send String messages on the robot_status topic, and buffer up to 10 if the network is slow." create_timer(0.5, self.tick) runs tick() twice a second. spin() is what keeps the node alive and firing those callbacks.
Write the subscriber
Now a second node that listens on the same topic and reacts to every message.
~/ros2_ws/src/my_robot/my_robot/status_listener.py
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
class StatusListener(Node):
def __init__(self):
super().__init__('status_listener')
# create_subscription(type, topic, callback, queue_size)
self.subscription = self.create_subscription(
String, 'robot_status', self.on_message, 10)
self.get_logger().info('status_listener is waiting for messages')
def on_message(self, msg):
# this runs every time a message arrives on robot_status
self.get_logger().info(f'Received: "{msg.data}"')
def main(args=None):
rclpy.init(args=args)
node = StatusListener()
rclpy.spin(node)
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
Notice the symmetry: the publisher used a timer callback to send; the subscriber uses a message callback to receive. Both must use the same message type and topic name to connect — String on robot_status.
Tell ROS 2 how to run them
ROS 2 needs to know these files are runnable programs. Open setup.py and add both to the entry_points section:
~/ros2_ws/src/my_robot/setup.py (excerpt)
entry_points={
'console_scripts': [
'status_publisher = my_robot.status_publisher:main',
'status_listener = my_robot.status_listener:main',
],
},
Each line reads: <command_name> = <package>.<file>:<function>. Also confirm std_msgs and rclpy are listed as dependencies in package.xml:
<depend>rclpy</depend>
<depend>std_msgs</depend>
Build and run
Terminal — build once
cd ~/ros2_ws
colcon build
source install/setup.bash
Now open two terminals (source install/setup.bash in each) and run one node in each:
Terminal 1 — publisher
ros2 run my_robot status_publisher
Terminal 2 — subscriber
ros2 run my_robot status_listener
The publisher logs Publishing: "Robot OK — heartbeat 3" and the listener logs Received: "Robot OK — heartbeat 3". You wrote both sides of a robot conversation. Open a third terminal and run rqt_graph to see /status_publisher → /robot_status → /status_listener.
- Change the publisher's timer to fire 5 times per second. What number goes in
create_timer? - Make the listener print
ALERTwhenever the heartbeat number is a multiple of 10. (Hint: you'll need to send a number — try switching to thestd_msgs/msg/Int32message type, whose field isdataas an integer.) - Run a second copy of the listener in a new terminal. Do both receive every message? Why? (Re-read the one-to-many idea from Lesson 1.)
Key takeaways
- ROS 2 Python nodes are classes that inherit from
rclpy'sNode. - A publisher sends with
create_publisher+publish(); a subscriber reacts withcreate_subscription+ a callback. - Two nodes connect only if they share the same topic name and message type.
- Register runnable nodes in
setup.py'sentry_points, thencolcon buildandros2 run.