4.2. Sample Publisher Nodes

We can look at a two very simply sample nodes to understand how ROS works.

In the Viper Toolkit package, under the scripts/ folder there are two python files: * publisher.py * subscriber.py

In ROS I find it easiest to set everything up as a class… In other notebooks I will try to dissect my classes, but for now lets take a look at these two nodes:

import inspect
import rospy
from viper_toolkit import Dissect

4.2.1. The Example Subscriber Node and Class Definition

from scripts import publisher
publisher_source = inspect.getsource(publisher)
print (publisher_source)
#!/usr/bin/env python3

import rospy
from std_msgs.msg import String

class PublisherNode(object):
    def __init__(self):
        self.setup_ros()
        self.setup_publisher()
        self.main_loop()
        
    def setup_ros(self):
        rospy.init_node('sample_publisher')
 
    def setup_publisher(self):
        self.pub = rospy.Publisher(
            name = 'topic',
            data_class = String,
            queue_size = 1)
    
    def action_loop(self):
        time = rospy.get_time()
        return  time
    
    def main_loop(self):
        rate = rospy.Rate(10)
        while not rospy.is_shutdown():
            time = self.action_loop()
            message = String(f'The time is now {time}')
            self.pub.publish(message)
            rospy.loginfo(f'I published: {message}')
            rate.sleep()

if __name__ == '__main__':
    PublisherNode()

Lets take a look at the different parts of this node!

4.2.2. Detailed Look at Class Attributes, Properties, and Methods

from scripts.publisher import PublisherNode

4.2.2.1. Initialize the Class Object

The init class function executes at the time that the class in instantiated. We are telling the interpreter to first run the setup_ros() function, then run the setup_publisher() function, and finally to execute the main_loop() function.

Dissect('__init__', PublisherNode)
class PublisherNode(object):

    def __init__(self):
        self.setup_ros()
        self.setup_publisher()
        self.main_loop()

4.2.2.2. 2. Setup ROS

We need to tell ROS that we would like to bring this node online; we are registering this kernel as “sample_publisher”. While multiple nodes of the same class can run at once, each node must have a unique name. If a node comes online which has the same name, it will replace the current node and this kernel will be shut down.

Dissect('setup_ros', PublisherNode)
class PublisherNode(object):

    def setup_ros(self):
        rospy.init_node('sample_publisher')

4.2.2.3. 3. Setting up the Publisher

Next we will setup the publisher API which allows a node to publish a “Topic”, or a broadcasted message. This node will be publishing the topic “topic” a message type of “String.” It will only hold on to the last message (queue_size=1).

Dissect('setup_publisher', PublisherNode)
class PublisherNode(object):

    def setup_publisher(self):
        self.pub = rospy.Publisher(
            name = 'topic',
            data_class = String,
            queue_size = 1)

We can take a look at this message type. Messages of the same type need to be serialized so that a reciever is always receiving the same uniform format:

from std_msgs.msg import String
message = String()
print (f"The class structure of String() is:")
print (message)
print ("")
print (f"The class type is: {type(message.data)}")
The class structure of String() is:
data: ''

The class type is: <class 'str'>

This tells us that the message type String contains a single variable, ‘data’, which is of the class “str”. ROS will attempt to convert classes between C++ and Python.

4.2.2.4. 4. The Action Loop

The Action Loop is where the processing of the node occures. In many of my nodes you will see multiple loops chained together, depending on the desired action to be performed. In this example our action loop is getting the time from the rospy module.

Dissect('action_loop', PublisherNode)
class PublisherNode(object):

    def action_loop(self):
        time = rospy.get_time()
        return  time

4.2.2.5. 5. Main Loop

The main loop is the loop which will continually run while the node is online. First, we set the rospy.rate() which is the minimum hz we wish the node to run. In this example, we are asking the node to retrieve the result of the action loop (the time), converting our desired message into the ‘String’ message format, and then publishing our message. After printing a log entry indicating that it published a message it will then wait wait for the Hz timer to finish before it performs this loop again.

Dissect('main_loop', PublisherNode)
class PublisherNode(object):

    def main_loop(self):
        rate = rospy.Rate(10)
        while not rospy.is_shutdown():
            time = self.action_loop()
            message = String(f'The time is now {time}')
            self.pub.publish(message)
            rospy.loginfo(f'I published: {message}')
            rate.sleep()

4.2.3. Final Output

Our resulting output looks like this.

print ('''
if __name__ == '__main__':
    PublisherNode()

[INFO] [1638135063.888976]: I published: data: "The time is now 1638135063.8869355"
[INFO] [1638135063.988257]: I published: data: "The time is now 1638135063.9871924"
[INFO] [1638135064.088094]: I published: data: "The time is now 1638135064.0870602"
[INFO] [1638135064.188143]: I published: data: "The time is now 1638135064.1871343"
''')
if __name__ == '__main__':
    PublisherNode()

[INFO] [1638135063.888976]: I published: data: "The time is now 1638135063.8869355"
[INFO] [1638135063.988257]: I published: data: "The time is now 1638135063.9871924"
[INFO] [1638135064.088094]: I published: data: "The time is now 1638135064.0870602"
[INFO] [1638135064.188143]: I published: data: "The time is now 1638135064.1871343"