Introduction to ROS2
Robot Operating System 2 aka ROS2 is a set of open-source libraries and tools used to build robot applications.
The ROS2 Graph
The ROS2 Graph describes the general terminology and structure of ROS2 at the user-level. The ROS2 graph consists of:
- Nodes
- Topics
- Messages
- Parameters
- Services
- Actions
Nodes
Nodes are instances of executables that act as individual computation entitites in the ROS2 environment. They perform specific tasks, such as processing sensor data or controlling hardware. Nodes communicate with each other by publishing and subscribing to topics or by using services. Nodes typically consist of combinations of publishers, subscribers, and services. Nodes may also communicate using services, actions, and parameters.
Topics
Topics are named buses that nodes use to exchange messages. Nodes can both publish messages on a topic and receive messages in a callback function on a subscribed topic. Each topic is limited to an individual message type specified in a node's executable.
Messages
Messages are data structures used to communicate information betyouen nodes via topics. These structures contain combinations of fields and data types, and are defined in .msg files. The field is the name of specific data in the structure, while the data type is typically either a primitive data type or another message type. Messages define specific information such as sensor data or control input.
Parameters
Parmaeters are configuration values assigned to nodes. Parameters can either be node-specific or applied to multiple nodes in the ROS2 environment.
Services
Services provide an alternate communication method with a request-response mechanism. A client sends requests to a server and receives responses after the sevice is complete.
Actions
Actions are another communication alternative intended for long-running tasks. Actions consist of three elements: a goal, status updates, and a result.
Creating a ROS2 Workspace
For ROS2 packages to be accessible to the system, the ROS2 installation and workspaces must be source. The ROS2 installation contains all default packages and supporting software. ROS2 workspaces are user-defined locations that contain packages, nodes, etc.
Configure the .zshrc
file to source the ROS2 installation. In a terminal on the backseat
, enter the following command:
echo "source /opt/ros/jazzy/setup.zsh" >> ~/.zshrc
Before, the .zshrc
file was edited by opening the file and then copying/pasting new lines. The above command adds source /opt/ros/jazzy/setup.zsh
as a new line at the end of the .zshrc
file. Resource the .zshrc
file to apply the changes:
source ~/.zshrc
Now, create a ROS2 workspace for this course:
cd && mkdir auvc_ws && cd auvc_ws && mkdir src
This ROS2 workspace is called auvc_ws
and is located at the home directory. It contains an empty src
directory for initialization. In the future, all your custom packages must be located in src
.
Compile the empty workspace:
cd ~/auvc_ws && colcon build
The ROS2 workspace now contains directories named build
, install
, logs
, src
.
The build
directory is used during compiling to build executables from packages in src
. The install
directory contains the final results from build
. The logs
directory contains log data about the workspace.
Configure the .zshrc
file to source the auvc_ws
workspace:
echo "source $HOME/auvc_ws/install/setup.zsh" >> ~/.zshrc
Resource the .zshrc
file:
source ~/.zshrc
Now all installation files and packages within your auvc_ws
workspace will be sourced in each new terminal. When you compile new packages or recompile existing packages, you should resource the workspace with source ~/auvc_ws/install/setup.zsh
.
Creating ROS2 Packages
Create a new ROS2 package called intro_to_ros
for this lesson. First, navigate to ~/auvc_ws/src
then use ros2 pkg create
:
cd ~/auvc_ws/src
ros2 pkg create --build-type ament_python intro_to_ros
Now, open the ROS2 package in Code:
code ~/auvc_ws/src/intro_to_ros
Since you created the package on the backseat
, it is not currently backed up on GitHub. Go to https://github.com on your laptop and create an empty repository called intro_to_ros
. This repository will store all files for your intro_to_ros
package. Now sync your package on backseat
to your Git repository.
Now, on the backseat
, initialize the Git repository:
cd ~/auvc_ws/src/intro_to_ros
git init
Add the remote repository:
git remote add origin https://github.com/YOUR_GITHUB_USERNAME/intro_to_ros.git
Add your files and commit:
git add .
git commit -m "initial commit"
git branch -M main
Now, to publish your branch and push your initial commit, go to the Source Control
tab and click Publish Branch
.
Publisher/Subscriber Example
This section contains a ROS2 tutorial for writing publisher and subscriber nodes that communicate.
Each of these nodes has a specific purpose: the publisher node publishes Vector3
message with random values at a frequency of 1 Hz
, while the subscriber node receives those Vector3
message and prints the corresponding unit vector. This example differs from one in the presentation and is a better example of inter-node communication using topics and messages.
Publisher Node
Create a new file in intro_to_ros/intro_to_ros/
called publisher.py
.
Add the shebang to the first line of the file:
#!/usr/bin/env python3
Next, specify the imports. In all nodes, import rclpy
and from rclpy.node import Node
are required to properly use the ROS2 Python3 client library. For this specific node, since you're using Vector3
from the geometry_msgs
package, you must import it as well. Other libraries like numpy
or opencv2
can be imported as needed.
import rclpy
from rclpy.node import Node
from geometry_msgs.msg import Vector3
import random
Now, create a class for the node which inherits from Node
:
class TutorialPublisher(Node):
Within the class's constructor, you start the node and define attributes relating to ROS2 communication.
def __init__(self):
super().__init__("tutorial_publisher")
self.publisher = self.create_publisher(
Vector3,
"/tutorial/vector3",
10
)
self.publisher_timer = self.create_timer(
1.0, self.run_node
)
self.get_logger().info("starting publisher node")
This constuctor does four things:
- Start a node named
tutorial_publisher
in an instance of theTutorialPublisher
class. - Create a publisher that broadcasts
Vector3
messages to the topic named/tutorial/vector3
. - Create a timer that runs the
run_node
method every1.0
seconds. - Log that the node is starting in the terminal.
For this tutorial, the run_node
method will publish random Vector3
messages.
def run_node(self):
msg = Vector3()
msg.x = random.uniform(-10.0, 10.0)
msg.y = random.uniform(-10.0, 10.0)
msg.z = random.uniform(-10.0, 10.0)
self.publisher.publish(msg)
self.get_logger().info(f"Vector3\n\tx: {msg.x}\ty: {msg.y}\tz: {msg.z}")
This method, when called, will generate a Vector3
message with pseudo random values.
Lastly, define the main
function. rclpy.init
starts the ROS2 Python3 client, node
creates an instance of the TutorialPublisher
class, rclpy.spin
keeps node
running until there is an exception, destroy_node
destroys node
at the end of the program's lifespan, and rclpy.shutdown
closes the ROS2 Python3 client if it is still active.
def main(args=None):
rclpy.init(args=args)
node = TutorialPublisher()
try:
rclpy.spin(node)
except KeyboardInterrupt:
print("\nKeyboardInterrupt received, shutting down...")
finally:
node.destroy_node()
if rclpy.ok():
rclpy.shutdown()
if __name__=="__main__":
main()
Subscriber Node
Create a new file in intro_to_ros/intro_to_ros/
called subscriber.py
.
Add the shebang and imports:
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from geometry_msgs.msg import Vector3
import numpy as np
Now, create a class for the node which inherits from Node
:
class TutorialSubscriber(Node):
Within the class's constructor, you start the node and define attributes relating to ROS2 communication.
def __init__(self):
super().__init__("tutorial_subscriber")
self.subscriber = self.create_subscription(
Vector3,
"/tutorial/vector3",
self.callback,
10
)
self.subscriber
self.get_logger().info("starting subscriber node")
This constuctor does three things:
- Start a node named
tutorial_subscriber
in an instance of theTutorialPublisher
class. - Create a subscriber that receives
Vector3
messages on the topic named/tutorial/vector3
in the methodself.callback
. - Log that the node is starting in the terminal.
For each subscriber that a node contains, there must be a callback method. Define a callback method for the subscriber attribute called subscription
:
def callback(self, msg):
magnitude = np.sqrt(msg.x ** 2 + msg.y ** 2 + msg.z ** 2)
data = Vector3()
data.x = msg.x / magnitude
data.y = msg.y / magnitude
data.z = msg.z / magnitude
self.get_logger().info(f"Vector3\n\tx: {data.x}\ty: {data.y}\tz: {data.z}")
Lastly, define the main function:
def main(args=None):
rclpy.init(args=args)
node = TutorialSubscriber()
try:
rclpy.spin(node)
except KeyboardInterrupt:
print("\nKeyboardInterrupt received, shutting down...")
finally:
node.destroy_node()
if rclpy.ok():
rclpy.shutdown()
if __name__=="__main__":
main()
Updating package.xml
Before building the package, all dependencies for ROS2 packages and system libraries in all nodes in the package must be added to package.xml
. Below the license
line, add a build_depend
tag and exec_depend
tag for each dependency that is a ROS2 package or a system library.
For the intro_to_ros
package, you only use ROS2 packages; numpy
and random
are Python3 libraries, so they do not need to be added.
<build_depend>rclpy</build_depend>
<build_depend>geometry_msgs</build_depend>
<exec_depend>rclpy</exec_depend>
<exec_depend>geometry_msgs</exec_depend>
Updating setup.py
Before building the package, all executables must have an entry point in setup.py
. Add the following lines within the square brackets in entry_points={'console_scripts': [],}
:
'publisher = intro_to_ros.publisher:main',
'subscriber = intro_to_ros.subscriber:main',
The variable names in this case define the name of the built executable. intro_to_ros.publisher.main
specifies the package and the name of the executable in the src
directory.
Build the Package
The intro_to_ros
is now configured properly to build the publisher.py
and subscriber.py
nodes. In a terminal on the backseat
, build the package:
cd ~/auvc_ws
colcon build --packages-select intro_to_ros --symlink-install
A warning may appear saying that easy_install is deprecated since the --symlink-install
argument is specified, but this warning can be ignored.
Since this is a new package, source the workspace's install file:
source ~/auvc_ws/install/setup.zsh
Testing the Publisher Node
Let's test the publisher node. In a terminal on the backseat
, start an instance of publisher.py
:
ros2 run intro_to_ros publisher
In the terminal, you should see the x, y, and z components of a pseudo random vector being printed every 1.0
seconds. Shutdown the node using CTRL + C or CMD + C.
Testing the Subscriber Node
Now, let's test the subscriber node. In a terminal on the backseat
, start an instance of subscriber.py
:
ros2 run intro_to_ros subscriber
Initially, the node will only log that it is started. Since there are no messages on the topic when only the subscriber node is running, the callback function does not run.
To test the subsriber, publish a single Vector
message. Open a new terminal on the backseat
while the subscriber node is running. In that terminal, enter the following command:
ros2 topic pub /tutorial/vector3 geometry_msgs/Vector3 '{x: 1.0, y: 1.0, z: 1.0}'
Look at the terminal where the subscriber node is running. You should find that the node logged a unit vector with equal components.
Testing the Publisher/Subscriber Pair
Now that you have confirmed each node works independently, run an instance of each node in two seperate terminals on the backseat
.
Review with a TA or instructor to check-off this section.
Problem Set
Problem Set Setup
Installing ArduSub
MAVROS
is a ROS2 package that enables communication between flight control softwares like ArduSub
and the ROS2 environment. For the purposes of this problem set and future simulations, you must install ArduSub
. When you interface with the ROVs themselves, you'll connect to a SITL
running onboard and therefore you will not need to run your own.
Install ArduSub
from the official Git repository on the backseat
.
cd && git clone --recurse-submodules https://github.com/blksail-edu/ardupilot
cd ~/ardupilot
Create a new virtual environment for ArduSub
and install the following dependencies:
python3 -m pip install empy==3.3.4 pexpect mavproxy future dronecan setuptools
Also install the ccache
system library:
sudo apt-get install ccache
Now, configure ArduPilot
:
./waf configure
. ~/.profile
./waf clean
On the backseat
, use the following commands in a terminal to add ArduPilot
to path:
echo "export PATH=$PATH:$HOME/ardupilot/Tools/autotest" >> ~/.zshrc
echo "export PATH=/usr/lib/ccache:$PATH" >> ~/.zshrc
Next, install the gcc-arm cross-compiler
:
cd ~/ardupilot && mkdir TARGET_DIR && cd TARGET_DIR
wget -c https://firmware.ardupilot.org/Tools/STM32-tools/gcc-arm-none-eabi-10-2020-q4-major-x86_64-linux.tar.bz2
Unpack the compiler:
tar -xjvf gcc-arm-none-eabi-10-2020-q4-major-x86_64-linux.tar.bz2
Add the compiler to path on the backseat
using the following terminal command:
echo "PATH=$PATH:$HOME/ardupilot/TARGET_DIR/gcc-arm-none-eabi-10-2020-q4-major/bin" >> ~/.zshrc
Configuring MAVROS
Before you can use ArduSub
, you must finalize the MAVROS
installation. We have preinstalled MAVORS
on your Raspberry Pi, but you must fun the following commands to install an additional library:
cd && wget https://raw.githubusercontent.com/mavlink/mavros/ros2/mavros/scripts/install_geographiclib_datasets.sh
chmod +x ./install_geographiclib_datasets.sh
sudo ./install_geographiclib_datasets.sh
Launching ArduSub
Now, in a terminal on the backseat
, start a SITL
with the following command:
~/ardupilot/Tools/autotest/sim_vehicle.py --vehicle=ArduSub --aircraft="bwsibot" -L RATBeach --out=udp:YOUR_COMPUTER_IP:14550
The first time you start the SITL, it may take some time to configure.
Launching MAVROS
Once the SITL
is running, you can connect to it by launching MAVROS
in another terminal on the backseat
:
ros2 launch mavros apm.launch fcu_url:=udp://127.0.0.1:14550@14555 gcs_url:=udp://:14550@YOUR_COMPUTER_IP:14550 tgt_system:=1 tgt_component:=1 system_id:=255 component_id:=240
When you run this command on the backseat
, MAVROS
may warn you that RTT is too high for timesync.
The above ros2 launch
command only connects to the SITL running locally on the RPI. You will use different arguments to connect to the BlueROV2 vehicles.
Getting Your Computer's IP
To get your computer's IP address,
- macOS
- Windows
With the backseat
connected to your Mac, then go to System Settings
and select Network
. Find the RNDIS/Ethernet Gadget. Once that interface has a yellow light, click on it. Use the IP address
field.
In a powershell
terminal, run the following command:
ipconfig
Use the IP address of the interface with your Raspberry Pi. If you are unsure which interface that is, search View Network Connections
in Windows and find the interface for
USB Ethernet/RNDIS Gadget
.
Problem One
With both ArduSub
and MAVROS
running on the backseat
, you can access simulated messages from the BlueROV2 vehicle in the ROS2 environment.
In your intro_to_ros
package, create a new node called bluerov2_sensors
. This node should do the following:
- Subscribe to the BlueROV2's battery sensor messages.
- Subscribe to the BlueROV2's inertial measurement unit (IMU) sensor messages.
- Have a class attribute for the battery message type.
- Have a class attribute for the IMU message type.
- Update the battery attribute to the latest battery sensor message when it is received.
- Update the IMU attribute to the latest IMU sensor message when it is received.
- Create a timer and a corresponding method to check whether the battery has fallen below a safe voltage. If it has, log a message to the terminal. This method should run every
5.0
seconds.
Update package.xml
and setup.py
to account for your new node, then rebuild the intro_to_ros
package.
Problem Two
Open QGroundControl
on your Windows or macOs machine. You should see the the BlueROV2
on the United State's west coast. Modify your bluerov2_sensors
node so that it prints the battery voltage at each callback, then, run your node.
In QGroundControl, arm the ROV and change the flight mode to GUIDED
. Then, click on a point anywhere else in the water and select Go to location
. After holding down space or dragging the slider, you should see the ROV start moving.
Now check the terminal where your node is running. What do you observe about the battery's voltage?
Review with a TA or instructor to check-off this section.
Problem Three
Create a new node called 'physics_sim'. This node should do the following:
- Subscribe to Pose2D messages on the topic named
/physics/
pose2d`. - Have a method that runs your
simulate_auv2_motion
function fromProblem 10
inUnderwater Physics
. - Have a method called
posed2d_callback
that receivesPose2D
messages. When you receive, run thesimulate_auv2_motion
usingx0=msg.x
,y0=msg.y
, andtheta0=msg.theta
. For the remaining inputs, use the following values (not accurate to the BlueROV2):
T = np.ndarray(shape=(4,), dtype=float, buffer=np.array([2.0, 1.0, 2.0, 1.0]))
alpha = np.pi / 4
L = 0.3
l = 0.2
mass = 1.0
dt = 0.1
t_final = 10.0
- Save the plots as .png file(s) on the
backseat
.
Add any additional attributes or methods as needed. Update package.xml
and setup.py
to account for your new node, then rebuild the intro_to_ros
package.
For this problem especially, use the --symlink-install
argument when you build the package. This way, if your node does not work as intended, you do not have to recompile the package when you make changes to physics_sim.py
.
With your node running on the backseat
, open a new terminal and run the following command:
ros2 topic pub -1 /physics/pose2d geometry_msgs/Pose2D '{x: 1.0, y: 1.0, theta: 0.5}