ROS API Basics
This article will review the ROS robotics middleware API basic design and usage. There are many resources online to allow more detailed or specific uses. There are also numerous packages and repositories of publicly available code with varying degrees of ROS API usage.
Frequently Asked Questions
I have a library that I have used before and it allows me to do everything I need to in order to create my application, why should I use ROS?
The simple answer is that ROS is not essential for creating robotics applications. After completing a prototype robotics application, and looking towards the design of a more complete solution, many common design challenges will arise. ROS is designed to solve these common challenges or provide common tools across a broad variety of problems. By using the ROS eco-system, developers have access to tested and reusable solutions to solve numerous robotics design problems rapidly and repeatably. The core ROS design leverages many best practices for problem-solving and software development and as a result, allows the creation of high-quality prototypes.
ROS is not commonly used in industry today, why should I learn how to use it if I won’t actually use it?
ROS is becoming increasingly popular as a tool for research, prototyping, and production. Despite this, it is not the only framework that solves robotics software problems. After some time understanding the challenges that commonly arise in robotics software design, it will become apparent that there are a handful of effective general strategies that are re-used across many robotics frameworks. An example of this can be seen in the commercial drone sector with onboard flight control operating systems and components opting to use messaging protocols such as Mavlink to manage sensory data flow, and communications. This is characteristically similar to the basic approach used in ROS and as such, it is still beneficial to learn ROS and the core concepts due to the inherent similarities in design approaches used in research and industry.
How do I solve ‘x’ with ROS?
There is no strict pattern to solving problems with ROS, rather there are a series of tools and best practices that once leveraged provide complex system traits such as failure tolerance, generality, or measurability. These tools and best practices are documented and used in numerous publicly available examples. As new problems are solved, new tools and best practices are proposed and refined. The best way to solve design challenges using ROS is to fully understand the features of the middleware and how they are used. Another important consideration is that not all problems will benefit from the use of ROS as middleware.
Instructions
The following components are essential to almost all ROS-based applications:
- Become familiar with the ROS middleware and the problems that it aims to solve.
- Go to the ROS wiki and review ALL the tutorials as it will be important to understand both basic and advanced API usage to create complete applications
- Do not use while loops for the ‘main’ execution for each process, use Timers instead
- Apply the concept of one-task-per-process strictly
Avoid copying examples
The ROS examples and tutorials are designed to allow people to get started with ROS. There are some examples that intentionally choose not to use best practice designs. These choices should be avoided in a real-use context.
Core Concepts
Nodes
A ROS application consists of many nodes (processes) that use a communication protocol to transport data as messages. a complete robotic system is launched by declaring nodes, their parameters, and connections in a launch file. The nodes, parameters, and messages form the computation graph.
Design Considerations
There are two important considerations for creating and working with nodes. These are as follows:
- Design nodes using the principal of separation of concerns as in - a single process performs a single definite responsibility in the network. This ensures that ROS networks are fault-tolerant, testable, re-usable and re-configurable. Another feature of this design style is that it can assist in avoiding race conditions on data and implicitly allows for data immutability.
- Design abstract inputs and outputs that are relevant to the node’s responsibility. This allows the node to be re-configured or used in different scenarios and networks. This is achieved by using relative namespaces, and generic names for nodes, parameters, topics, services, actions, and message types.
Anit-Patterns
There are some design strategies that the ROS API allows but should be avoided. Some common design issues encountered early in the process of creating a ROS application include: Monoliths
A single node is responsible for many tasks.
Fuzzy service boundaries Two or more nodes are responsible for the same tasks. Two or more nodes publish the same data. A node publishes data to itself.
Under-abstraction or over-specialisation
A node can only be used in one circumstance with specific inputs and configuration. A node defines or allocates global namespaces. The node does not use any parameters or remaps.
Duplication
A core ROS feature or best practice is not used but rather duplicated. For example a node uses it’s own logging library.
Time sensitive dependencies
A node may terminate if some condition is not met within a time window. An example is one node waiting for another node to start during launch, or one node assumes the existence of another node in order to function.
Busy waiting
A process stays in a while-loop waiting for the occurrence of some event. This locks the CPU up and is generally not good for responsive multi-tasking or concurrent programs.
Topics
An intuitive way to understand topics is to imagine a message board like below:
Each post-it note and polaroid is a topic, and nodes can discover these and then read them. As time goes on, the messages change or get updated. Sometimes, new topics are added. Each node can add topics to the board and read other topics of interest.
Communications
Communications in ROS are based on data flow. Data will be created by sources, and data will travel through a network towards sinks. Intermediate data will be transformed as it travels through a network.
An example in ROS1:
- node 2 asks roscore ‘Is there a /image topic?’
- roscore replies ‘yes, node 1 publishes to /image, here is the connection details’
- node 2 connects to node 1 using the connection details
- node 2 receives an image from node 1
- node 2 draws a rectangle on the image
See another example
Since ROS is designed as a distributed network, there is a necessary process for finding and collecting data. A simple overview of this process is as follows.
- Search for resources through a discovery protocol
- Agree on connection details and connect to the resource provider
- collect data from resource providers as it is created
- transform the data and provide new resources for discovery using a discoverable protocol
ROS Service Discovery
-
In ROS1, the discovery process is orchestrated by the roscore server. This is the major criticism of the ROS1 design as this creates a single point of failure which results in the potential for severe system failure.
-
In ROS2 this issue was addressed by using the industrial communications protocol called DDS - Data Distribution Service which is a distributed protocol - preventing a single point of failure, but increasing network usage during discovery.
Namespaces
Namespaces are used to segment ROS data and networks using logical relationships - thereby avoiding resource collisions in a distributed network. Namespaces are a common strategy used in messaging protocols. More information can be found here.
The key to using namespaces is to ALWAYS use a private-first namespace.
The diagram below shows the ideal name conventions used to design any node. If a node is set up with this convention, it can be reconfigured and remapped to fit into most computation graphs.
Code Implementation
Most ROS development will take place in the creation of nodes. A typical node will leverage all of the available computation graph tools to operate effectively in varying scenarios. A Python example is given below:
# an example of a pythonic ros node
# import the ros python client library
import rospy
# import data types used by this node
from sensor_msgs.msg import Image # for example an image message might be used
from std_msgs.msg import String
# first a class is used to encapsulate the graph resources associated with the node
# this is an example of applying the relative-first namespaces by collecting all the
# node's resources together into a common, relative namespace
class Node():
# it is simplest to define all of the node resources in the constructor
def __init__(self):
# define ROS parameters
# the parameters are ALWAYS private(~) and can be remapped later
# set a default value to ensure logical operation if it is not configured elsewhere
self.param1 = rospy.get_param("~param1", 1.0) # the default value here is 1.0
self.param2 = rospy.get_param("~string_param1", "string_value")
# define publishers
# the publishers will post data to the network on the specified topics
# the topics here are private(~) and can be remapped later
# the queue size informs the ros network of the size of the buffer used for this
# topic by this node
self.image1_pub = rospy.Publisher("~image_topic_name", Image, queue_size=1)
# define subscribers
self.string1_sub = rospy.Subscriber("~string_topic", String, self.string_cb)
# define services
# define actions
# define timers (for execution loops, instead of while/spin loops)
self.do_something_timer = rospy.Timer(period1, self.do_cb)
self.do_another_thing_timer = rospy.Timer(period2, self.do_another_cb)
# define all of the necessary callbacks for receiving data
# define all of the necessary callbacks for timers
# the main function broadcasts the node's existence to the network
# instantiates an object of the node class
# and spins until the execution is cancelled
if __name__ == "__main__":
# establish communication with the ros network for this node
# the node name is generic (relative) here and will be remapped later
rospy.init("node_type")
# this allocates memory for the node class and calls the constructor
node = Node()
# run until cancelled
# the spin function checks the network for new data and executes any callbacks
# needed to receive data
rospy.spin()
Network Services
The ROS middleware provides features that improve the development experience and the overall finish of a design.
Logging Via /rosout
Logging is handled via the ROS middleware by communicating to rosout. More information can be found here.
# an example of logging in rospy - python client api
rospy.logdebug("most of this will just be logged to perform triage, not that interesting")
rospy.loginfo("sometimes it's nice to know things that are happening")
rospy.logwarn("there might be problem, consider reviewing this")
rospy.logerr("something went wrong that needs to be addressed")
rospy.logfatal("an unresolvable issue has occurred and this node will probably be stopping")
Transforms: /tf
, /tf_static
tf2 is the second generation of the transform library, which lets the user keep track of multiple coordinate frames over time. tf2 maintains the relationship between coordinate frames in a tree structure buffered in time, and lets the user transform points, vectors, etc between any two coordinate frames at any desired point in time.
More about here.
Diagnostics: /diagnostics
The diagnostics system is designed to collect information from hardware drivers and robot hardware to users and operators for analysis, troubleshooting, and logging. The diagnostics stack contains tools for collecting, publishing, analyzing and viewing diagnostics data.
The diagnostics toolchain is built around the /diagnostics topic. On this topic, hardware drivers and devices publish diagnostic_msgs/DiagnosticArray messages with the device names, status and specific data points.
The diagnostic_updater and self_test packages allow nodes to collect and publish diagnostics data. The diagnostic_aggregator can categorize and analyze diagnostics at runtime. Operators and developers can view the diagnostics data using the rqt_robot_monitor package. The diagnostic_analysis package can convert diagnostics logs to CSV files for examination and after-the-fact analysis.
ROSBags
The data storage format of a ROS system. A ROSBag stores data in a format that makes sense for robotics. This data can be shared and used to develop new systems.
Robot Description
A robot is often tangible and interacts with the physical world. This concept needs a representation in software. URDFs (universal robot description format), and SDFs (scene description format) are used to abstract the physical world in software urdf - ROS Wiki.