FizzBuzz the ROS way

Table of Contents

1 Overview

In this tutorial you will create a version of the classic FizzBuzz programming exercise using ROS and Python.

This tutorial is meant to be interactive, so the code on this page will not work by itself. You will have to make some changes and complete the parts labeled TODO. If you run into problems or get stuck, a completed version of the code is available at https://github.com/ericboehlke/ros_fizzbuzz

1.1 You will

  1. Create a package in your catkin workspace
  2. Make a Number Publisher Node
  3. Make a FizzBuzz Node
  4. Create your own ROS message type
  5. Create a Launch File to run both of these nodes
  6. Examine the node structure with rqt

1.2 Prerequisites

  • If you haven't already, you will need to install ROS. Here is a link for instructions on how to install ROS: http://wiki.ros.org/ROS/Installation.
  • You will also need a ROS workspace. For more information on setting up a workspace see this tutorial: http://wiki.ros.org/catkin/Tutorials/create_a_workspace/. One additional note is to be sure to run source devel/setup.bash in every terminal you intend to use ROS with. This command will let you use autocompletion and run other ROS commands with the files in your workspace.

2 Creating a Package

For more information regarding ROS packages, see this tutorial: https://wiki.ros.org/ROS/Tutorials/CreatingPackage.

The first step is to create a new package in your catkin workspace. Navigate to the src directory in your catkin workspace and create a package named fizzbuzz with dependencies of std_msgs and rospy.

cd ~/catkin_ws/src
catkin_create_pkg fizzbuzz std_msgs rospy

Then create a directory named scripts for the python files we will make in the following steps.

cd ~/catkin_ws/src/fizzbuzz
mkdir scripts

Finally because we added a new package to the catkin workspace we need to run catkin_make.

cd ~/catkin_ws
catkin_make

3 Number Publisher Node

Now that we have a package, it is time to create a node that publishes numbers that we can use to play the FizzBuzz game.

3.1 Starting off with Simple Publisher

3.1.1 Code

The number publisher node will live in the scripts folder we made in the last section. You can navigate to this folder with the following command since ROS now knows about your new package and where it is located.

roscd fizzbuzz/scripts

If the previous command failed, remember that you have to source the setup file located at devel/setup.bash in your workspace after each time you run catkin_make.

Now we will start creating the number publishing node called number_publisher_node.py. Here is a simple publisher node template that we can use as a starting point.

#!/usr/bin/env python

import rospy

# import the ROS String message type
from std_msgs.msg import String

class SimplePublisherNode:
    def __init__(self):
        rospy.loginfo("Starting simple_publisher")

        # create a publisher object to send data
        self.pub = rospy.Publisher("chatter", String, queue_size=10)

        PUBLISH_RATE = 10.0 # 10 Hz
        # create a timer that calls the timer_callback function at
        # the specified rate
        rospy.Timer(rospy.Duration(1.0/PUBLISH_RATE), self.timer_callback)

    def timer_callback(self, event):
        # this function is called by the timer
        hello_str = "hello there %s" % rospy.get_time()

        # loginfo to print the string to the terminal
        rospy.loginfo(hello_str)

        # publish the string message
        self.pub.publish(hello_str)


if __name__ == "__main__":
    rospy.init_node("simple_publisher")
    node = SimplePublisherNode()
    rospy.spin()

ROS nodes need to be executable. To make a file executable use the following command.

chmod +x [FILE_NAME]

3.1.2 Explaination

This example ROS node is similar to the ROS Python Publisher Tutorial except that the node is written as a class using a timer to create callbacks instead of using a function with a while loop. Using classes sometimes makes the code look cleaner.

The python file can be thought of in three parts.

  1. The imports at the top of the file
  2. The main logic for the node in the class
  3. And the if statement at the bottom.
  1. Imports

    The two imports here are rospy and the String message type

  2. The SimplePublisherNode Class

    This class has two functions inside.

    The __init__ function runs just once when an instance of the class is created in the if statement at the bottom of the file. It first prints a message to the roslog to say that simple_publisher is starting. This is not necessary to the function of the node and just serves as a helpful way to see when the node starts. It is prefered to use rospy.loginfo instead of print because it works better with other ROS features and will print to the terminal even if the node is started with a launch file (something the print function will not do).

    Next a publisher is created with rospy.Publisher. This function has 3 arguments. The first is the name of the topic that the publisher is publishing to. In this case it is chatter. The second is the message type. In this case the node publishes strings so the std_msgs/String type is used. The final argument is how many messages the publisher keeps around in case the subscribers are not receiving them fast enough. Ten is a good number.

    Finally a timer is created. This timer calls the self.timer_callback function at the rate specified.

    The timer_callback function does the work of publishing the messages. When it is called it creates a string that contains the current time according to ROS. Then it prints this string using roslog and publishes it using self.pub.publish.

  3. The If Statement
    if __name__ == "__main__":
    

    If you have never seen this syntax before it is a way to only run code if the python file is run directly and not if it has been imported into another file.

    In this case it contains the code to initialize the node with the ROS master, make an instance of the class and run rospy.spin() to keep the node running.

3.2 Start Counting!

Right now this simple publisher node publishes strings instead of numbers. We need to customize the example to our use case. First change the class name from SimplePublisherNode to NumberPublisherNode both in the class declaration and in the if statement at the bottom. You should change the name in the rospy.init_node function call as well. This function lets the rest of ROS know what the node's name is. Might as well change the loginfo message to reflect the name change as well.

Right now the message being used is the std_msgs/String message. For the number publisher we want to publish integers. Find the appropriate message type to replace with String. The list of all of the messages in std_msgs can be found here. Make sure to replace the message type both in the import and where the publisher is created. Rename the topic from chatter to something more descriptive such as numbers.

Now the node is nearly all set up to publish integers instead of strings. All that is left is to change the callback function to make that happen. Your mission, should you choose to accept it, is to publish integers starting at 0 and increasing by 1 everytime.

4 Testing the Number Publisher Node

Now it is time to see your hard work in action. Lets test the number publisher node to make sure it is functioning properly.

Step one is to open a bunch of terminal windows. For this test you will need 3 windows. Remember to run source devel/setup.bash in each one. Your desktop will be much more organized if you use a terminal manager such as terminator or tmux. This will allow you to easiy see everything that is going on without windows overlapping or having to switch between tabs.

Step two is to run the following command in one of the terminals to start the ROS master. The ROS master program keeps track of all of the nodes and topics and facilitates the process of nodes publishing and subscribing to topics. If you want to run any ROS nodes at all you will need a ROS master running.

roscore

Step three is to set up a terminal window to listen to a topic and print all of the messages it recieves to the terminal. This way we can make sure that the node is publishing correctly. The following command does exactly that: rostopic echo [topic_name]. Note that you will get a warning saying that the topic has not been published yet. That is to be expected and we will fix that next.

rostopic echo numbers

Finally we need to start the node. For this you can use the rosrun [package_name] [node_name] command. Note that we did not have to run catkin_make after making our Python script.

rosrun fizzbuzz number_publisher_node.py

Now you should see numbers counting up from two of your terminals. The terminal running the node is displaying the numbers via the ROS log and the terminal running the rostopic echo command is subscribing to the messages published by the number_publisher_node.

You can use ^C (Ctrl-C) to stop the processes in all three terminals whenever you like.

5 FizzBuzz Node

Now we are on to the FizzBuzz node. This node will subscribe to the numbers topic that we made in the last section and will publish its own topic named fizzbuzz. This topic will transport custom messages containing the result of the FizzBuzz function for the current number, the ratio of fizz, buzz, and fizzbuzz to the total number of messages received since the node has started, as well as the count of how many messages have been recived by the node.

5.1 Creating a FizzBuzz Message

Our custom fizzbuzz message will look like this.

string fizzbuzz  # the result of the fizzbuzz function
float32 fizz_ratio  # the ratio of fizz results to non fizz results
float32 buzz_ratio  # the ratio of buzz results to non buzz results
float32 fizzbuzz_ratio  # the ratio of fizzbuzz results to non fizzbuzz results
int32 number_total  # the total number of numbers received.

The string fizzbuzz will either be fizz if the number is divisible by 3, buzz if the number is divisible by 5, fizzbuzz if the number is divisible by 15, or an empty string if none of the previous cases are true.

To create this new message type in ROS, we will have to make a file named FizzBuzz.msg in a new folder named msg within the fizzbuzz package.

mkdir -p ~/catkin_ws/src/fizzbuzz/msg
cd ~/catkin_ws/src/fizzbuzz/msg
touch FizzBuzz.msg

Now that you have a new message file you need to tell catkin where it is and that you want it compiled. To do this you will have to edit the file CMakeLists.txt as well as package.xml in the fizzbuzz package. By default there are comments in the CMakeLists.txt file that will guide you through adding a new message.

The uncommented parts of your CMakeLists.txt and package.xml files should look something like this.

cmake_minimum_required(VERSION 3.0.2)
project(fizzbuzz)

find_package(catkin REQUIRED COMPONENTS
  rospy
  std_msgs
  message_generation
)

add_message_files(
  FILES
  FizzBuzz.msg
)

generate_messages(
  DEPENDENCIES
  std_msgs
)

catkin_package(
  CATKIN_DEPENDS rospy std_msgs message_runtime
)

include_directories(
  ${catkin_INCLUDE_DIRS}
)
<?xml version="1.0"?>
<package format="2">
  <name>fizzbuzz</name>
  <version>1.0.0</version>
  <description>The fizzbuzz package</description>
  <maintainer email="eric@ericboehlke.com">Eric Boehlke</maintainer>
  <license>MIT</license>

  <buildtool_depend>catkin</buildtool_depend>
  <build_depend>rospy</build_depend>
  <build_depend>std_msgs</build_depend>
  <build_depend>message_generation</build_depend>
  <build_export_depend>rospy</build_export_depend>
  <build_export_depend>std_msgs</build_export_depend>
  <exec_depend>rospy</exec_depend>
  <exec_depend>std_msgs</exec_depend>
  <exec_depend>message_runtime</exec_depend>

  <export>
  </export>
</package>

After you have updated those two files, you will need to run catkin_make to build the message.

cd ~/catkin_ws
catkin_make

If everything worked correctly, you should be able to use rosmsg info [message_name] to see the contents of your new FizzBuzz message.

rosmsg info FizzBuzz

5.2 FizzBuzz Node

5.2.1 Code

The FizzBuzz node will also live in the scripts folder. Create a new file named fizzbuzz_node.py. Here is some code to get you started.

#!/usr/bin/env python

import rospy

# import our new fizzbuzz message type
from fizzbuzz.msg import FizzBuzz 

# TODO import the number message used for the numbers topic

class FizzBuzzNode:
    def __init__(self):
        rospy.loginfo("TODO: add appropriate log message for when node starts")

        self.total_numbers = 0
        self.total_fizz = 0
        self.total_buzz = 0
        self.total_fizzbuzz = 0

        # create a publisher object to send data
        self.fizzbuzz_pub = rospy.Publisher("fizzbuzz_stats", FizzBuzz, queue_size=10)

        # TODO fill in the TOPIC_NAME and MESSAGE_TYPE
        rospy.Subscriber("TOPIC_NAME", MESSAGE_TYPE, self.number_callback)

    def number_callback(self, msg):
        # this function is called whenever a number is recived.

        number = msg.data 

        fizzbuzz_str = self.fizzbuzz(number)
        # loginfo to print the string to the terminal
        rospy.loginfo(fizzbuzz_str)

        fizzbuzz_msg = FizzBuzz()
        fizzbuzz_msg.fizzbuzz = fizzbuzz_str
        fizzbuzz_msg.fizz_ratio = 0 # TODO fill in this value
        fizzbuzz_msg.buzz_ratio = 0 # TODO fill in this value
        fizzbuzz_msg.fizzbuzz_ratio = 0 # TODO fill in this value
        fizzbuzz_msg.number_total = 0 # TODO fill in this value

        # publish the message
        self.fizzbuzz_pub.publish(fizzbuzz_msg)

    def fizzbuzz(self, number):
        # TODO complete this function
        # This should return a string equal to:
        #      "fizz" if number divisible my 3
        #      "buzz" if number divisible my 5
        #      "fizzbuzz" if number divisible my 15
        #      a string containing the number otherwise
        return ""


if __name__ == "__main__":
    rospy.init_node("fizzbuzz_node")
    node = FizzBuzzNode()
    rospy.spin()

5.2.2 Explaination

At the top of the file we import the FizzBuzz message we just created.

The __init__ function first initializes a couple variables that might be helpful for completing the node. Then, it creates a publisher object that publishes FizzBuzz messages to a topic named fizzbuzz_stats.

Next it subscribes to a topic. It is your job to fill in both the topic name and the message type. When a new message is recived, it calls the number_callback function. In this way, it is similar to how the timer called the timer_callback function.

The number_callback function takes in the message recieved by the subscriber and publishes a FizzBuzz message with the correct information. To accomplish this it uses a function called self.fizzbuzz that returns a string containing the correct response for that number.

5.2.3 Complete the Node

If you complete the code in the places marked by TODO , you will have a functioning FizzBuzz Node. Remember to make the python script executable as well.

6 Testing the FizzBuzz Node

Let's check to make sure the FizzBuzz node is working correctly. Open four terminals and run the following commands.

This command starts the ROS master.

roscore

This starts up the FizzBuzz node we just made. You should see the result of fizzbuzz printed to the terminal whenever this node recives a message from the numbers topic.

rosrun fizzbuzz fizzbuzz_node.py

This command displays the messages published to the fizzbuzz_stats topic. You should see the result of FizzBuzz as well as the ratio and total count in this terminal after you start the number publisher.

rostopic echo fizzbuzz_stats

When you run this command the number_publisher_node will start publishing numbers and will kick off the whole game of FizzBuzz.

rosrun fizzbuzz number_publisher_node.py

7 Making a Launch File

That was a lot of terminals we needed to play FizzBuzz! It would be great if there was a way to start multiple ROS nodes with one command. That is where launch files come in.

Launch files are files that contain instructions to run a group of ROS nodes. We will now make a launch file to start both the number publisher and the fizzbuzz node. Here is an example launch file that runs the number_publisher_node.py. You can copy the format to make it launch the fizzbuzz node as well. Launch files go in a directory called launch in the package to which they belong. You can name this one fizzbuzz.launch and put it in a new folder at fizzbuzz/launch.

<launch>
    <node pkg="fizzbuzz" name="number_publisher_node" type="number_publisher_node.py" output="screen"/>
</launch>

As you can see, we need to specify the package of the node, the name of the node, and the filename of the node in order to properly start the node from a launch file. The output attribute specifies where the messages from the ROS log command get sent. By specifying the value screen they will appear in the terminal we use to start the launch file.

For more information about the format of a ROS launch file, see this page: http://wiki.ros.org/roslaunch/XML.

Now if we want to play FizzBuzz again we can simply open two terminals and run the following commands.

The roslaunch command will run the launch file. It will also start the ROS master if one is not already running, so this launch file saved us two terminals!

roslaunch fizzbuzz number_publisher_node.py

To see the messages on the fizzbuzz_stats topic, we will still have to use the rostopic echo command.

rostopic echo fizzbuzz_stats

8 RQT

RQT is a useful too to see what nodes are running and how messages are being transfered between them.

Use your launch file to begin a game of FizzBuzz and then open RQT using the following command in a new terminal.

rqt

This command will open up an empty window. Navigate in the top menu to Plugins, Introspection, Node Graph. This will show you all of the currently running ROS nodes as circles and the topics they communicate on as arrows. For FizzBuzz this graph is only two nodes with a single topic between them, but RQT is very helpful for visualizing the connections in larger projects. RQT has many other helpful tools for inspecting messages and graphing data that you might want to explore.

ROS has many built in tools for seeing what topics being used (rostopic list), what nodes are being run (rosnode list), recording and playing back messages (rosbag), diagnosing problems while ROS is running (roswtf), and many more.

9 Keep Exploring!

This tutorial has just scratched the surface of what ROS can do. To learn more check out the ROS Wiki here: http://wiki.ros.org/. Hopefully you found this tutorial helpful on your journey of learning ROS.

Author: Eric Boehlke

Created: 2021-02-21 Sun 16:44

Validate