Skip to content

Part 1 Publisher Node

tom-howard edited this page Jul 2, 2021 · 2 revisions

The Code

Copy all of the code below into your publisher.py file. Then, review the explainer (below) to understand how this all works.

#!/usr/bin/env python
# A simple ROS publisher node in Python

import rospy
from std_msgs.msg import String

class Publisher:
    
    def __init__(self):
        self.pub = rospy.Publisher('chatter', String, queue_size=10)
        rospy.init_node('publisher_node', anonymous=True)
        self.rate = rospy.Rate(10) # hz
                
        self.ctrl_c = False
        rospy.on_shutdown(self.shutdownhook) 
        
        rospy.loginfo("publisher node is active...")

    def shutdownhook(self):
        self.shutdown_function()
        self.ctrl_c = True

    def shutdown_function(self):
        print("stopping publisher node at: {}".format(rospy.get_time()))

    def main_loop(self):
        while not self.ctrl_c:
            publisher_message = "rospy time is: {}".format(rospy.get_time())
            self.pub.publish(publisher_message)
            self.rate.sleep()

if __name__ == '__main__':
    publisher_instance = Publisher()
    try:
        publisher_instance.main_loop()
    except rospy.ROSInterruptException:
        pass

The Code Explained:

First things first, the very first line in this file looks like a comment, but it is actually a very crucial part of the script:

#!/usr/bin/env python

This is called the Shebang and it tells the operating system which interpreter to use to execute the code. In our case here, it tells the operating system where the Python interpreter is installed and then uses this interpreter to actually run the code. Make sure that this exists as the first line in all the Python nodes that you create in ROS!

Then on to the actual python code. rospy is the Python client library for ROS and we need to import this in order to write a ROS Node in Python. We also need to import the String message type from the std_msgs.msg library for publishing our messages:

import rospy
from std_msgs.msg import String

Next, we create a Publisher class to contain all the functionality of our node:

class Publisher:

The __init__ method is called as soon as an instance of the Publisher class is created:

def __init__(self):
    self.pub = rospy.Publisher('chatter', String, queue_size=10)
    rospy.init_node('publisher_node', anonymous=True)

In the first part of this method we create a publisher object within our class, define the chatter topic on which to publish messages, and specify that we will be publishing String type messages. Then, we initialise our Python node and give it a name (publisher_node, in this case). Finally, we set anonymous=True to ensure that the node has a unique name by adding random numbers to the end of it.

Then, we create a Rate object and set the frequency to 10 Hz, so that our publisher will publish messages at this frequency.

self.rate = rospy.Rate(10) # hz

The next section of the initialisation block illustrates a nice way to shutdown a ROS node effectively:

self.ctrl_c = False
rospy.on_shutdown(self.shutdownhook)

First, we create a ctrl_c variable within the parent class and initialise it to False. Then, we use the rospy.on_shutdown() method to register a shutdownhook function, which will be called when rospy begins shutdown (i.e. the user has entered Ctrl + C in the terminal, or rospy has stopped for another reason). The function must take no arguments.

Finally, we issue a message to indicate that our node is active (this will appear in the terminal that we run the node in):

rospy.loginfo("publisher node is active...")

Next, we define the shutdownhook function:

def shutdownhook(self):
    self.shutdown_function()
    self.ctrl_c = True

Here, we call the actual shutdown process (defined later in the code) and set the ctrl_c variable to True to stop the main_loop() method (also defined later).

Then, we define the actual shutdown process, which can take arguments (but doesn't in the case of this example). We can use this to apply any stop conditions (i.e. to make sure a robot stops moving when the node is shut down, for example):

def shutdown_function(self):
    print("stopping publisher node at: {}".format(rospy.get_time()))

Then, we define the functionality of the main loop, which will run continuously until the node is shut down:

def main_loop(self):
    while not self.ctrl_c:
        publisher_message = "rospy time is: {}".format(rospy.get_time())
        self.pub.publish(publisher_message)
        self.rate.sleep()

Here, we create a publisher message (a simple string in this case), publish it using the pub object we created in the initialisation stage, and then use the rate object (also created earlier) to make the node "sleep" for as long as required to satisfy the frequency setpoint that we defined earlier.

Finally, the code is actually executed by first performing the standard python __name__ check, to ensure that the script being run is the main executable (i.e. it has been executed directly, and hasn't been called by another script):

if __name__ == '__main__':
    publisher_instance = Publisher()
    try:
        publisher_instance.main_loop()
    except rospy.ROSInterruptException:
        pass

Then, we create an instance of the Publisher class that we created above (at which point the __init__ method will be called). Following this, we call the main_loop() method to execute the core functionality of our class until further notice. We use a try-except-pass statement to look for a rospy.ROSInterruptException error, which can be output by rospy when the user presses Ctrl + C or the node is shutdown in some other way.