Code for spinning at a specified frequency (C++ and Python)

Requirements for this page:

TL;DR

Of course you did already read everything below, and the following TL;DR summary is just for convenience when you return to remind yourself of a small detail:

Contents for a C++ node spinning at a fixed frequency:

#include "ros/ros.h"
#include <ros/package.h>

// Declare "member" variables
ros::Timer m_timer_for_counting;

// Declare the function prototypes
void timerCallback(const ros::TimerEvent&);

// Implement the timer callback function
void timerCallback(const ros::TimerEvent&)
{
    static uint counter = 0;
    counter++;
    // Display the current counter value to the console
    ROS_INFO_STREAM("[SPINNER CPP NODE] counter = " << counter);
}

int main(int argc, char* argv[])
{
    // Initialise the node
    ros::init(argc, argv, "spinner_cpp_node");
    // Start the node by initialising a node handle
    ros::NodeHandle nh("~");
    // Display the namespace of the node handle
    ROS_INFO_STREAM("SPINNER CPP NODE] namespace of nh = " << nh.getNamespace());
    // Initialise a timer
    float timer_delta_t_in_seconds = 0.5;
    m_timer_for_counting = nh.createTimer(ros::Duration(timer_delta_t_in_seconds), timerCallback, false);
    // Spin as a single-threaded node
    ros::spin();
    // Main has ended, return 0
    return 0;
}

Contents for a Python node spinning at a fixed frequency:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import rospy

class SpinnerPyNode:

  def __init__(self):
      # Initialise a counter
      self.counter = 0
      # Initialise a timer
      timer_delta_t_in_seconds = 0.5;
      rospy.Timer(rospy.Duration(timer_delta_t_in_seconds), self.timerCallback)

  # Respond to timer callback
  def timerCallback(self, event):
      self.counter += 1
      # Display the current counter value to the console
      rospy.loginfo("[SPINNER PY NODE] counter = " + str(self.counter))

if __name__ == '__main__':
    # Initialise the node
    rospy.init_node("spinner_py_node")
    # Display the namespace of the node handle
    rospy.loginfo("SPINNER PY NODE] namespace of node = " + rospy.get_namespace());
    # Start an instance of the class
    spinner_py_node = SpinnerPyNode()
    # Spin as a single-threaded node
    rospy.spin()

Three lines for the CMakeLists.txt, follow the pattern described in Add the C++ node to the C Make List:

add_executable(spinner_cpp_node src/spinner_cpp_node.cpp)
add_dependencies(spinner_cpp_node ${catkin_EXPORTED_TARGETS})
target_link_libraries(spinner_cpp_node ${catkin_LIBRARIES})

A launch file for launching both C++ and Python spinners at the same time.

<launch>
    <!-- START A GROUP WITH A NAMESPACE -->
    <group ns="mrp">
        <!-- LAUNCH A "Spinner C++" NODE -->
        <node
            pkg    = "my_robotics_pkg"
            name   = "spinner_cpp_node"
            output = "screen"
            type   = "spinner_cpp_node"
        />
        <!-- LAUNCH A "Spinner Python" NODE -->
        <node
            pkg    = "my_robotics_pkg"
            name   = "spinner_py_node"
            output = "screen"
            type   = "spinner_py_node.py"
        />
    </group>
</launch>

High-level description

A ROS node spinning a a specified frequency can be achieved by:

  • Initialising a ROS timer variable in the main function of the node.

  • A time delta is specified when initialising the ROS timer, and this registers that the node should repeatedly trigger the specified callback function at that time delta interval.

  • The main function then goes into a ROS spin, which trigger the timer callbacks.

Note

The time delta specified for the timer is respected regardless of how long the callback function takes to complete one execution. In other words, if the callback function takes too long, then the queue of timer callbacks build up and the node’s behaviour becomes be hard to predict.

Note

As stated on the ROS overview timers page: “Timers are not a realtime thread/kernel replacement, rather they are useful for things that do not have hard realtime requirements.

Python only: add a class definition

Starting from the “plain_py_node.py” from Code up a ROS node from scratch (C++ and Python), add a class definition bespoke to this node, and initialise an instance of the class from the main function:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import rospy

class SpinnerPyNode:

  def __init__(self):
      # Initialise a counter
      self.counter = 0

if __name__ == '__main__':
    # Initialise the node
    rospy.init_node("spinner_py_node")
    # Start an instance of the class
    spinner_py_node = SpinnerPyNode()
    # Spin as a single-threaded node
    rospy.spin()

The self.counter variable is used later on for counting up the number of timer callback.

Add a callback function for when the timer triggers

For both C++ and Python, the callback implementation simply increments a counter and print the current value of the counter to the console.

For C++: add the function prototype towards the top of the file, and the timer callback function wherever you please:

#include "ros/ros.h"
#include <ros/package.h>

// Declare the function prototypes
void timerCallback(const ros::TimerEvent&);

// Implement the timer callback function
void timerCallback(const ros::TimerEvent&)
{
    static uint counter = 0;
    counter++;
    // Display the current counter value to the console
    ROS_INFO_STREAM("[SPINNER CPP NODE] counter = " << counter);
}

int main(int argc, char* argv[])
{
    // Initialise the node
    ros::init(argc, argv, "spinner_cpp_node");
    // Start the node by initialising a node handle
    ros::NodeHandle nh("~");
    // Display the namespace of the node handle
    ROS_INFO_STREAM("SPINNER CPP NODE] namespace of nh = " << nh.getNamespace());
    // Spin as a single-threaded node
    ros::spin();
    // Main has ended, return 0
    return 0;
}

Note

The typical structure for C++ classes (and you can think of each ROS node you write as a separate class), is to put the includes, member variable definitions, and function prototypes into a header. This is not done header for the convenience of display one file instead of two. But as your node grows, you should consider shifting such declarations to a header file, i.e., to a file named spinner_cpp_node.h

Note

The function implementation can be placed above or below the main function, it is a matter of style. And whatever style you choose, remember that consistency of style (i.e., convention) adds tangible value through usability and maintainability.

For Python: add the timer callback function within the class:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import rospy

class SpinnerPyNode:

  def __init__(self):
      # Initialise a counter
      self.counter = 0

  # Respond to timer callback
  def timerCallback(self, event):
      self.counter += 1
      # Display the current counter value to the console
      rospy.loginfo("[SPINNER PY NODE] counter = " + str(self.counter))

if __name__ == '__main__':
    # Initialise the node
    rospy.init_node("spinner_py_node")
    # Start an instance of the class
    spinner_py_node = SpinnerPyNode()
    # Spin as a single-threaded node
    rospy.spin()

Initialise a timer and connect it to the callback

For C++: the timer is initialised in the main function:

#include "ros/ros.h"
#include <ros/package.h>

// Declare "member" variables
ros::Timer m_timer_for_counting;

// Declare the function prototypes
void timerCallback(const ros::TimerEvent&);

// Implement the timer callback function
void timerCallback(const ros::TimerEvent&)
{
    static uint counter = 0;
    counter++;
    // Display the current counter value to the console
    ROS_INFO_STREAM("[SPINNER CPP NODE] counter = " << counter);
}

int main(int argc, char* argv[])
{
    // Initialise the node
    ros::init(argc, argv, "spinner_cpp_node");
    // Start the node by initialising a node handle
    ros::NodeHandle nh("~");
    // Display the namespace of the node handle
    ROS_INFO_STREAM("SPINNER CPP NODE] namespace of nh = " << nh.getNamespace());
    // Initialise a timer
    float timer_delta_t_in_seconds = 0.5;
    bool timer_is_one_shot = false;
    m_timer_for_counting = nh.createTimer(ros::Duration(timer_delta_t_in_seconds), timerCallback, timer_is_one_shot);
    // Spin as a single-threaded node
    ros::spin();
    // Main has ended, return 0
    return 0;
}

For Python: the timer is initialised in the __init__ of the class, which is call from the main function:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import rospy

class SpinnerPyNode:

  def __init__(self):
      # Initialise a counter
      self.counter = 0
      # Initialise a timer
      timer_delta_t_in_seconds = 0.5;
      rospy.Timer(rospy.Duration(timer_delta_t_in_seconds), self.timerCallback, oneshot=False)

  # Respond to timer callback
  def timerCallback(self, event):
      self.counter += 1
      # Display the current counter value to the console
      rospy.loginfo("[SPINNER PY NODE] counter = " + str(self.counter))

if __name__ == '__main__':
    # Initialise the node
    rospy.init_node("spinner_py_node")
    # Start an instance of the class
    spinner_py_node = SpinnerPyNode()
    # Spin as a single-threaded node
    rospy.spin()

Add the C++ node to the C Make List and compile

Follow the exact same pattern described in Add the C++ node to the C Make List:

  1. Open the CMakeLists.txt file for editing:

cd ~/my-robotics-system/catkin_ws/src/my_robotics_pkg/
nano CMakeLists.txt
  1. Add the add_executable directive:

    add_executable(spinner_cpp_node src/spinner_cpp_node.cpp)
    
  2. Add the add_dependencies directive:

    add_dependencies(spinner_cpp_node ${catkin_EXPORTED_TARGETS})
    
  3. Add the target_link_libraries directive:

    target_link_libraries(spinner_cpp_node ${catkin_LIBRARIES})
    
  4. Compile

    cd ~/my-robotics-system/catkin_ws/
    catkin_make
    

Make the Python file executable

Add (+) executable (x) permissions to the spinner Python file:

chmod +x ~/my-robotics-system/catkin_ws/src/my_robotics_pkg/src/spinner_py_node.py

Launch and test

Make a launch file for launching both nodes at the same time and test, following the pattern described in Run, launch, and interrogate nodes.

The messages displayed in the console may look something like the following:

process[mrp/spinner_cpp_node-1]: started with pid [20000]
process[mrp/spinner_py_node-2]: started with pid [20001]
[ INFO] [1650283861.482913600]: SPINNER CPP NODE] namespace of nh = /mrp/spinner_cpp_node
[ INFO] [1650283861.989005528]: [SPINNER CPP NODE] counter = 1
[ INFO] [1650283862.487859902]: [SPINNER CPP NODE] counter = 2
[INFO] [1650283862.694406]: [SPINNER PY NODE] counter = 1
[ INFO] [1650283862.987786484]: [SPINNER CPP NODE] counter = 3
[INFO] [1650283863.193749]: [SPINNER PY NODE] counter = 2
[ INFO] [1650283863.487807749]: [SPINNER CPP NODE] counter = 4

Alternative: Fixed frequency spinning in the main

It is possible to spin at a fixed frequency directly within the main function of the node by using ROS loop rate type variable:

  • Pro: this remove the code for a timer, hence if the node performs a small task, spinning in the main can lead to a succinct, clean, and easily maintained code.

  • Con: this put all the functionality of the node into the main, hence if the node perform larger or multiple tasks, spinning in the main can lead to intertwined functionality that is hard to read and hard to maintain.

For C++: spinning in the main uses a ros::Rate type variable, described as a “convenience class which makes a best effort at maintaining a particular rate for a loop”, as follows:

#include "ros/ros.h"
#include <ros/package.h>

int main(int argc, char* argv[])
{
    // Initialise the node
    ros::init(argc, argv, "spinner_cpp_node");
    // Start the node by initialising a node handle
    ros::NodeHandle nh("~");
    // Display the namespace of the node handle
    ROS_INFO_STREAM("SPINNER CPP NODE] namespace of nh = " << nh.getNamespace());

    // Initialise the ROS rate variable
    float loop_frequency_in_hz = 2.0;
    ros::Rate loop_rate(loop_frequency_in_hz);
    // Intialise a counter
    uint counter = 0;
    // Enter a while loop that spins while ROS is ok
    while (ros::ok)
    {
        counter++;
        // Display the current counter value to the console
        ROS_INFO_STREAM("[SPINNER CPP NODE] counter = " << counter);
        // Spin once to service anything that need servicing
        ros::spinOnce();
        // Sleep at the loop rate
        loop_rate.sleep();
    }

    // Main has ended, return 0
    return 0;
}

For Python: spinning in the main uses a rospy::Rate type variable, described as a “convenience class which makes a best effort at maintaining a particular rate for a loop”, as follows:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import rospy

if __name__ == '__main__':
    # Initialise the node
    rospy.init_node("spinner_py_node")

    # Initialise the ROS rate variable
    loop_frequency_in_hz = 2.0;
    loop_rate = rospy.Rate(loop_frequency_in_hz);
    # Intialise a counter
    counter = 0;
    # Enter a while loop that spins while ROS is ok
    while not rospy.is_shutdown():
      counter += 1
      # Display the current counter value to the console
      rospy.loginfo("[SPINNER PY NODE] counter = " + str(counter))
      loop_rate.sleep()

Note

Even though rospy does not have a “spin once” equivalent, this Python sample would still service anything that needs servicing, e.g., timer and subscriber callbacks.

References

The steps detailed on this page are mostly taken from:



Creative Commons License
Paul N. Beuchat, 2023