ROS Basics
The goal of this tutorial is to set up a workspace for all the resources required to install and develop as part of our journey to control robots. We will work with ROS (the most widely used open middleware for robotics) on a Linux-Ubuntu computer, which is the best-supported operating system for ROS.
This tutorial assumes you are using an Ubuntu-like 22.04 computer with configured Python and C++ APIs. It is based on ROS2 Iron.
The wiki.ros.org
website primarily refers to ROS1. While ROS1 and ROS2 share similarities, there are significant differences between them. Be cautious when interpreting search results on the internet, as they might refer to ROS1 instead of ROS2.
Nodes and Topics
Robots are complex cybernetic systems that require complex software to handle complex missions. That for, the main feature proposed by ROS is to develop programs in a modular way. Pieces of programs are dedicated to atomic functionalities and the overall control program is composed of several interconnected pieces.
A piece of program is called node and nodes exchange data by communicating through topics.
This tutorial part relies directly on ROS documentation docs.ros.org.
- Configuring environment: You should well understand how to configure your Linux environment
- First contact: Then, you can play with the hello world example on ROS2. Notice that
sudo
commands suppose you have administrator privilege on the computer - Understanding nodes
- Understanding topics
At this point you should be comfortable with the core component of ROS, nodes and topics. You realized that you will interact a lot with your software architecture throuh your terminal. So a good practice consists of turning your terminal prompt to clearly state your ROS configuration at any time...
Add the following 2 lines into your ~/.bashrc
file.
The bash run command file (~/.bashrc
) is a good tool to configure all your terminal with common environment variables and configurations.
# Tunned prompt:
PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:($ROS_AUTOMATIC_DISCOVERY_RANGE::$ROS_DOMAIN_ID)\[\033[01;34m\]\w\[\033[00m\].\n\$ '
Python API
ROS include more elements, components like services and actions, features like parameters or tools to debug (rqt_console), visualize (rviz2), launch configurations of nodes (launch) or recording topics (bag). But let see the Python API (Application Programming Interface) on nodes and topics first, before to visit other ROS elements.
Notice that the purpose of this lecture and its tutorials is not to cover all ROS elements.
Furthermore, the tutorials are designed with ament_cmake
build tool, mostly python
node and yaml
launch files.
Other solutions are provided by ROS2.
Setup a Dev. Environment
First, you should work on a dedicated directory to regroup the ROS things you need and you develop, let say your ros_space
or ros_ws
.
The things
would generally be packages
but we will see that later.
In this workspace, we will work for now on a playground
directory and mode specifically on ROS talker
and listener
python script.
To set up our environment in a terminal:
cd # Go to the home directory of the current user.
mkdir ros_space # Create a directory
cd ros_space # Enter it
mkdir playground # Create a directory
touch playground/talker.py # Create a file (if it does not exist)
touch playground/listener.py # Create a file (if it does not exist)
We recommend working with a complete extensible IDE, typically Visual Studio Code.
And open the entire ros_space
as a project, plus a terminal inside the IDE.
At this point, your IDE should have \(3\) areas: the explorer on the left side, a text editor on top of a terminal.
The explorer should contain the playground
directory with your \(2\) python scripts inside.
Talker: a simple topic publisher
The goal of our first program is to send a message on a topic.
For that, we need the ROS client Python package (rclpy
) for initializing ROS and creating nodes, and all the packages defining all the types of messages the program should manipulate (only simple string message on our example).
At the end the first hello world
program will look like:
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
def oneTalk():
# Initialize ROS client
rclpy.init()
# Create a node
aNode= Node( "simpleTalker" )
# Attach a publisher to the node, with a specific type, the name of the topic, a history depth
aPublisher= aNode.create_publisher( String, 'testTopic', 10 )
# Create a message to send
msg = String()
msg.data = 'Hello World'
# Add the message to the list of messages to publish
aPublisher.publish(msg)
# Activate the ROS client with the node
# (that will publish the message on testTopic topic)
rclpy.spin_once(aNode, timeout_sec= 10.0)
# Clean everything and switch the light off
aNode.destroy_node()
rclpy.shutdown()
# Execute the function.
if __name__ == "__main__":
oneTalk()
Run the solution on a first terminal.
python3 playground/talker.py
Listen to the topic in another terminal (you have to run again the talker to see a message...).
ros2 topic echo testTopic
The talker node need to be active when echo testTopic
is started otherwise ros2 topic
end because no node is connected to testTopic
.
Also, the String
message description (ie. composed by a unique data attribute)
come from index.ros.org,
aside from the other standard message type of std_msgs
.
The main ROS packages defining message types are grouped on common_interfaces with, for example geometry_msgs for objects defined on the Cartesian coordinate system, visualization_msgs focused on message types for visualization or sensor_msgs, well, for sensor-based data.
Continuous Talker
In a second version, we aim to create a node publishing continuously messages. For that purpose, we use an Event-Driven Programming style.
Technically in ROS, it is managed by the ros client, with the spin_once
or spin
functions.
These \(2\) functions connect events (the reception of a message for instance) with the appropriate callback.
spin_once
activates once the ros client and wait for the next event. It should be called in an infinite loop.
spin
handle the infinite loop directly (and should be preferred).
For the talker, you have to generate events with timers:
aNode.create_timer( duration, aCallback )
The call back function aCallBack
will be called after a duration
is terminated.
Furthermore, the call-back functions generally require a context of execution.
Some already defined and accessible elements
(for instance in our case, the publisher).
For that purpose, we implement our aCallBack
as a method of a class.
The new talker script will lookalike:
def infiniteTalk():
# Initialize ROS node with ROS client
rclpy.init()
aNode= Node( "infTalker" )
talker= ROSTalker(aNode)
# Start infinite loop
rclpy.spin(aNode)
# Clean everything and switch the light off
aNode.destroy_node()
rclpy.shutdown()
class ROSTalker:
def __init__(self, rosNode):
self._publisher= rosNode.create_publisher( String, 'testTopic', 10 )
self._timer = rosNode.create_timer(0.5, self.timer_callback)
self._i = 0
def timer_callback(self):
msg = String()
msg.data = 'Hello World: %d' % self._i
self._publisher.publish(msg)
self._i += 1
# Execute the function.
if __name__ == "__main__":
infiniteTalk()
Now python3 playground/talker.py
wil start a ROS node publishing \(2\) hello messages per second.
A Ctr-C
in the terminal should stop the process.
Listener: a simple topic subcriber
In a very similar way, listener node is implemented with topics subscription mechanisms associated with a callback.
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
def listen():
# Initialize ROS node with ROS client
rclpy.init()
aNode= Node( "listener" )
listener= ROSListener(aNode)
# Start infinite loop
rclpy.spin(aNode)
# Clean everything and switch the light off
aNode.destroy_node()
rclpy.shutdown()
class ROSListener():
def __init__(self, rosNode):
self._logger= rosNode.get_logger()
self._subscription= rosNode.create_subscription(
String, 'testTopic',
self.listener_callback, 10
)
def listener_callback(self, msg):
self._logger.info( 'I heard: ' + msg.data)
if __name__ == '__main__':
listen()
Run your listener as you run your talker. The \(2\) nodes work together and define a very simple first software architecture.
Logs
Notice that ROS provides its own, "print
" function with a logger instance.
The logger, attached to a node instance (aRosNode.get_logger()
), records logs through different channels: info
, debug
, warning
or error
.
More on docs.ros.org.
Logs can be followed with rqt_console
tool.
Follow the official tutorial to learn how to use this tool.