[Documentation] [TitleIndex] [WordIndex

Writing a Publisher/Subscriber with Dynamic Reconfigure and Parameter Server (Python)

This tutorial will show you how to combine several beginner level tutorials to create publisher and subscriber nodes that are more fully-featured than the previously created nodes. This page will describe how to create a publisher node that:

A subscriber node will be created to work with the publisher node that

It is assumed that all of the beginner tutorials will have been completed before using this one.

How to Run Examples

There are two programs created here, very similar to what is found at the ROS Publisher/Subscriber tutorial. They are even called the same names, talker and listener. If you haven't already checked out the code for this tutorial then you can get it into your home directory (represented as ~ in Unix-based systems) using

svn co http://ibotics.ucsd.edu/svn/stingray/branches/rostingray/node_example ~

Make sure that the directory where you have the example code is in your ~/.bashrc file under ROS_PACKAGE_PATH, similar to what is done when you install ROS initially. Assuming that node_example is checked out to your home directory you will need to add, near the bottom of your ~/.bashrc file, the line

export ROS_PACKAGE_PATH=~/node_example:$ROS_PACKAGE_PATH

Then, make the example nodes using

cd ~/node_example
cmake .
rosmake

The command cmake . will create a Makefile and any auto-generated code. A Makefile has to be used even though we are using Python so that the auto-generated code is built (see here for more details). The auto-generated code is in header files and is created when rosbuild_genmsg() and gencfg() are invoked from CMakeLists.txt. The rest of rosmake will generate the two nodes (binaries) that we will be running, both located in ~/node_example/bin/. Using four terminals (or four tabs in a single terminal -- use <ctrl-shift-t> to start a new tab in an existing terminal, and <alt-#> to easily switch between tabs) run the following four commands.

roscore
rosrun node_example pytalker.py
rosrun node_example pylistener.py
rosrun dynamic_reconfigure reconfigure_gui

You should see output from the terminal running the pylistener.py node. If you change the message variable or either of the integer values in the reconfigure_gui the output of listener should change to match it.

Alternatively, you can run everything using a launch file by running

roslaunch node_example python_node_example.launch

The launch file starts pytalker.py, pylistener.py, reconfigure_gui and rxconsole. The output of pylistener.py will only appear in rxconsole and not in the terminal when using the launch file. See the rxconsole page for more details on how to use that tool.

The following sections go into more detail about how the nodes work.

Creating a Custom Message

This tutorial describes messages in more detail. The custom message for these nodes contains

string message
int32 a
int32 b

After creating that file in the msg/ directory and using rosbuild_genmsg() in CMakeLists.txt nothing else needs to be done for the custom message.

Creating Configuration for Dynamic Reconfigure Server

This tutorial describes dynamic reconfigure in more detail. The variables available for use with the dynamic reconfigure server (that we will create later) are specified in the following file called node_example_params.cfg and located in the cfg/ directory.

   1 #! /usr/bin/env python
   2 
   3 PACKAGE='node_example'
   4 import roslib
   5 roslib.load_manifest(PACKAGE)
   6 
   7 from dynamic_reconfigure.parameter_generator import *
   8 
   9 gen = ParameterGenerator()
  10 #       Name       Type      Level Description     Default Min   Max
  11 gen.add("message", str_t,    0,    "The message.", "hello")
  12 gen.add("a",       int_t,    0,    "First number.", 1,     -100, 100)
  13 gen.add("b",       int_t,    0,    "First number.", 2,     -100, 100)
  14 
  15 exit(gen.generate(PACKAGE, "node_example", "node_example_params"))

This means we will be able to modify a message and two integers. Eventually, we will be able to modify these values and see the results while our nodes are running. Make sure the file node_example_params.cfg is executable by doing

chmod 755 ~/node_example/cfg/node_example_params.cfg

Then, after using gencfg() in CMakeLists.txt we will have everything we need to use a dynamic reconfigure server.

ROS Node Template

There are four files used to create the example nodes. There is one source and one header file that describe the class that is shared by listener and talker. Then, there is one source file to implement each of listener and talker. Note that the code style follows the ROS Python style guide.

The pytalker node

The node_example/src/pytalker.py source file contains

   1 #!/usr/bin/env python
   2 
   3 # Import required Python code.
   4 import roslib
   5 roslib.load_manifest('node_example')
   6 import rospy
   7 import sys
   8 
   9 # Give ourselves the ability to run a dynamic reconfigure server.
  10 from dynamic_reconfigure.server import Server as DynamicReconfigureServer
  11 
  12 # Import custom message data and dynamic reconfigure variables.
  13 from node_example.msg import node_example_data
  14 from node_example.cfg import node_example_paramsConfig as ConfigType
  15 
  16 # Node example class.
  17 class NodeExample():
  18     # Must have __init__(self) function for a class, similar to a C++ class constructor.
  19     def __init__(self):
  20         # Get the ~private namespace parameters from command line or launch file.
  21         init_message = rospy.get_param('~message', 'hello')
  22         rate = float(rospy.get_param('~rate', '1.0'))
  23         topic = rospy.get_param('~topic', 'chatter')
  24         rospy.loginfo('rate = %d', rate)
  25         rospy.loginfo('topic = %s', topic)
  26         # Create a dynamic reconfigure server.
  27         self.server = DynamicReconfigureServer(ConfigType, self.reconfigure)
  28         # Create a publisher for our custom message.
  29         pub = rospy.Publisher(topic, node_example_data)
  30         # Set the message to publish as our custom message.
  31         msg = node_example_data()
  32         # Initialize message variables.
  33         msg.a = 1
  34         msg.b = 2
  35         msg.message = init_message
  36         # Main while loop.
  37         while not rospy.is_shutdown():
  38             # Fill in custom message variables with values from dynamic reconfigure server.
  39             msg.message = self.message
  40             msg.a = self.a
  41             msg.b = self.b
  42             # Publish our custom message.
  43             pub.publish(msg)
  44             # Sleep for a while before publishing new messages. Division is so rate != period.
  45             if rate:
  46                 rospy.sleep(1/rate)
  47             else:
  48                 rospy.sleep(1.0)
  49 
  50     # Create a callback function for the dynamic reconfigure server.
  51     def reconfigure(self, config, level):
  52         # Fill in local variables with values received from dynamic reconfigure clients (typically the GUI).
  53         self.message = config["message"]
  54         self.a = config["a"]
  55         self.b = config["b"]
  56         # Return the new variables.
  57         return config
  58 
  59 # Main function.    
  60 if __name__ == '__main__':
  61     # Initialize the node and name it.
  62     rospy.init_node('pytalker')
  63     # Go to class functions that do all the heavy lifting. Do error checking.
  64     try:
  65         ne = NodeExample()
  66     except rospy.ROSInterruptException: pass

The pylistener node

The node_example/src/pylistener.py source file contains

   1 #!/usr/bin/env python
   2 
   3 # Import required Python code.
   4 import roslib
   5 roslib.load_manifest('node_example')
   6 import rospy
   7 
   8 # Import custom message data.
   9 from node_example.msg import node_example_data
  10 
  11 # Create a callback function for the subscriber.
  12 def callback(data):
  13     # Simply print out values in our custom message.
  14     rospy.loginfo(rospy.get_name() + " I heard %s", data.message)
  15     rospy.loginfo(rospy.get_name() + " a + b = %d", data.a + data.b)
  16 
  17 # This ends up being the main while loop.
  18 def listener():
  19     # Get the ~private namespace parameters from command line or launch file.
  20     topic = rospy.get_param('~topic', 'chatter')
  21     # Create a subscriber with appropriate topic, custom message and name of callback function.
  22     rospy.Subscriber(topic, node_example_data, callback)
  23     # Wait for messages on topic, go to callback function when new messages arrive.
  24     rospy.spin()
  25 
  26 # Main function.
  27 if __name__ == '__main__':
  28     # Initialize the node and name it.
  29     rospy.init_node('pylistener', anonymous = True)
  30     # Go to the main loop.
  31     listener()

Building the code

After modifying the contents of node_example/msg/node_example_data.msg to add, remove or rename variables it is necessary to run

cd ~/node_example
cmake .
rosmake node_example

Invoking CMake again will auto-generate the new header files that contain information about the changes that were made to the message structure.

Using Parameter Server and Dynamic Reconfigure

Parameter Server

There are several ways of setting variables to initial values through the use of the parameter server. One is through a launch file, and another is from the command line.

In node_example/python_node_example.launch the pytalker node is started with four parameters set, message, a, b and rate. To do the same thing from the command line pytalker could be started using

rosrun node_example pytalker.py _message:="Hello world!" _a:=57 _b:=-15 _rate:=1

Note that the ~ has been replaced by an underscore when modifying the private node handle parameters from the command line.

Then run

rosrun node_example pylistener.py

to see the differences.

Note that our nodes use private node handles for the parameter server. This is important because it helps to prevent name collisions when nodes are remapped to have unique node names. For instance, you may want to start two separate instances of a single node. With private node handles the separate nodes can have different values for the same parameter name. This comes in handy when you have multiple cameras of the same type but want to run them at different frame rates (this is just one example out of many for using private node handles).

Dynamic Reconfigure

The dynamic reconfigure tools are awesome, because they allow users to modify variable during runtime, not just at the start of runtime. We have already created a file specifying which variables are available to the dynamic reconfigure server. Note that the file node_example/manifest.xml has to have the line

<depend package="dynamic_reconfigure"/>

Review - What to Change

To use this example code as a new node you have to change several variables.

  1. Edit manifest.xml to depend on any other packages you need.

  2. Edit CMakeLists.txt to change the name of the executable(s) to build, and the source files that are used.

  3. Rename cfg/node_example_params.cfg to match the name of your node.

    • Change the PACKAGE= line to match your node.

    • Change the last line to use the name of your node in the two places where node_example is present.

    • Modify the variables you make available to the dynamic reconfigure server.
  4. Rename msg/node_example_data.msg to match the name of your node.

    • Modify the variables you want in the new message.
  5. Modify src/pytalker.py or src/pylistener.py, rename them if you want.

    • Set up only the parts you want to use from the examples.
    • Import your new custom messages and dynamic reconfigure configurations.
    • It is possible to combine initial configuration parameters, dynamic reconfigure server, publisher(s) and subscriber(s) all into a single node if desired.

2023-10-28 13:11