# Node Creation

Now that you've created a package, you're ready to get into actual development. The first step to actual development is creating a node, and to go through that process, we will use a template node to illustrate how we structure our ROS code.

# First Steps

The first thing you should do before using anything in this doc is actually creating a python file. You can do this on linux by executing

touch [node_name].py

where [node_name] is the name of your node. Then you can copy the template and use it in your node's code.

# Template Node

# Code

#!/usr/bin/env python3

import rospy
from std_msgs.msg import String


class base_node():
  def __init__(self):
    self.sub = rospy.Subscriber('/subscribed_topic', MessageType self.callback)
    self.pub = rospy.Publisher('/published_topic', MessageType, queue_size=2)

  def callback(self, msg):
    self.pub.publish(msg)

if __name__ == '__main__':
  try:
    rospy.init_node('base_node', anonymous=True) 
    base_node() 
    rospy.spin()
  except RuntimeError as e:
    pass

# Breakdown

As you can see, a minimum functioning node does not require a ton of code, but we will still break it down into parts so your understanding is correct.

# Shebang

#!/usr/bin/env python3

First up, we have the shebang. This tells your underlying Linux OS how to interpret this file. As you are creating a python node, this will always be #!/usr/bin/env python3.

# Imports

import rospy
from std_msgs.msg import String

Next up is your imports. You can add any python packages present on the system here, but there are a few important ones that you shouldn't miss. If you recall your reading of Package Creation, you will remember that you declared some dependencies, such as std_msgs, rospy, and the likes. These will need to be imported here to be used in your python script, and likewise they will need to be included in your package dependencies. One important thing to note is that you should import message types as needed rather than doing import std_msgs.msg.

# Class

class base_node():
  def __init__(self):
    self.sub = rospy.Subscriber('/subscribed_topic', MessageType, self.callback)
    self.pub = rospy.Publisher('/published_topic', MessageType, queue_size=2)

  def callback(self, msg):
    self.pub.publish(msg)

Now we get to the meat and bones of the operation, an object oriented ROS node. There are two ROS specific parts to pay attention to here: the publisher/subscriber initialization, and the callback function.

def __init__(self):
    self.sub = rospy.Subscriber('/subscribed_topic', MessageType, self.callback)
    self.pub = rospy.Publisher('/published_topic', MessageType, queue_size=2)

First up is the initialization function. In here, we define the inputs and outputs of your rosnode in terms of rostopics. For your subscriber, there's a minimum of three arguments:

  • /subscribed_topic
    • This is the topic name you want the node to receive data from
  • MessageType
    • This is the message type of the topic you want to subscribe to. As covered in the imports section, you will import message types from a .msg import, and then they will go here. If you are having trouble determining the message type of the topic you want, then run rostopic info /topic_name!
  • self.callback
    • This will be the callback function that is run everytime a new message is received. For your publisher, the arguments are:
  • /published_topic
    • This is the topic name you want the node to publish data on. You choose this name yourself, so make it descriptive
  • MessageType
    • This is the message type of the topic you want to publish. As covered in the imports section, you will import message types from a .msg import, and then they will go here.
  • queue_size
    • This determines how many messages will be queued before ros clears them from memory

A couple of notes:

  • You can have multiple publishers and subscribers in one node, but there are some things you will have to watch out for:
    • Each subscriber must have their own callback (If you want a single callback with synced messages, use message_filters)
    • Each publisher should only be published once, and you need to use distinct topic names for each publisher
  • To publish a message of ANY type, you need to fill out the fields of each message manually. Messages are just instances of a python class, and you will fill out each instance of a message like you would member variables of a python class. For example, a sensor_msgs/PointCloud2 is filled out like below (these are not real values):
    pcl_message = PointCloud2()
    pcl_message.header.stamp = rospy.Time.now()
    pcl_message.header.frame_id = "world"
    pcl_message.height = 1
    pcl_message.width = 680
    pcl_message.fields = []
    pcl_message.is_bigendian = true
    pcl_message.point_step = 4
    pcl_message.row_step = 64
    pcl_message.data = points[1:64]
    pcl_message.is_dense = false

# Main Function

Lastly, we have the main function:

    if __name__ == '__main__':
      try:
        rospy.init_node('base_node', anonymous=True) 
        base_node() 
        rospy.spin()
      except RuntimeError as e:
        pass

This code block spins up the node and ensures that it is executing properly. Some notable parts:

if __name__ == '__main__':

This line is the actual function call for main() in python.

rospy.init_node('base_node', anonymous=True)

This initializes the node with the name 'base_node'. Like every other name in this template, it should be changed. This one should change to match the functionality of the node.

base_node() 

This initializes the node, and subsequently initializes the publishers and subscribers, meaning that the node will begin to receive data.

rospy.spin()

This puts the node into a cycle of waiting for the next message and then executing.