Introdução ao Nav2
O nav2, também conhecido como Navigation2, é a segunda geração do sistema de navegação para robôs desenvolvido para funcionar com o ROS 2. É um framework modular e adaptável que fornece todas as ferramentas necessárias para desenvolver e executar algoritmos de navegação em robôs, especialmente robôs móveis. Graças à sua integração com ROS 2, o nav2 aproveita comunicações em tempo real, segurança e outras melhorias essenciais para aplicações robóticas modernas. Sua arquitetura é totalmente baseada em nós, serviços e ações, tendo como uma de suas principais características o uso de árvores de comportamento (behavior trees), que facilitam a definição de comportamento de navegação do robô através de serviços modulares.
Entre as principais funcionalidades do nav2, destacam-se:
- Map server - funcionalidade de carregar servir e armazenar mapas;
- AMCL - funcionalidade de localização do robô em um mapa;
- Planner - planejamento de rota desviando de obstáculos;
- Controller - controle do robô enquanto segue a rota;
- Smoother - torna a rota planejada mais contínua, sem mudanças bruscas;
- Costmap 2D - converte dados de sensor em um mapas de custo representativos do ambiente de trabalho do robô;
- Behavior trees - construção de comportamentos complexos de forma modular;
- Recoveries - fallback em caso de falha no percurso;
- Waypoint follower - navegação através de uma sequência de pontos;
- Lifecycle Manager - gerenciamento do ciclo de vida dos nós;
- Core - sistema de plugins para habilitar seus próprios algoritmos e comportamentos;
- Collision monitor - monitora dados dos sensores buscando riscos iminentes de colisão;
- Simple commander - API em Python para interagir com o Nav2 programáticamente;
1. Setup
Para usar o Nav2, deve-se primeiro garantir que o ROS está instalado e configurado adequadamente. Ver seção de configuração do ROS
A seguir, vamos instalar três pacotes necessários para interagir com o Nav2 e, especificamente, com o robô Turtlebot3 Burger:
- Bash
- Zsh
sudo apt install ros-humble-navigation2 ros-humble-nav2-bringup ros-humble-turtlebot3*
sudo apt install ros-humble-navigation2 ros-humble-nav2-bringup "ros-humble-turtlebot3*"
Essa configuração tem que ser feita em todos os dispositivos que vão interagir diretamente com o robô. Ou seja, em cada computador que vai se comunicar com o robô e na raspberry pi embarcada. Não basta configurar apenas em um computador!
Após isso, vamos mexer em uma configuração que pode nos causar problemas mais para frente quando formos interagir com o turtlebot, que é o algoritmo de DDS utilizado pelo ROS. Para isso, instale o seguinte pacote:
sudo apt install ros-humble-rmw-cyclonedds-cpp
A seguir, vamos adicionar uma variável de ambiente para que o ROS saiba dessa mudança:
- Bash
- Zsh
- Zsh com a minha config.
echo "export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp" >> ~/.bashrc
echo "export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp" >> ~/.zshrc
# Nada. Já está configurado meu bem =)
# Caso esteja em uma versão antiga, só de um git pull no repo Dotfiles
cd Dotfiles && git pull origin main
A seguir, vamos mexer na configuração padrão do turtlebot para que ele use o
método adequado de localização. Vá para a pasta
/opt/ros/humble/share/turtlebot3_navigation2/param
e modifique o arquivo
burger.yaml
. Lá, vamos fazer uma modificação simples: na linha em que está
escrito robot_model_type: "differential"
vamos mudar para robot_model_type:
"nav2_amcl::DifferentialMotionModel"
.
Pronto! Tudo configurado para fazermos nosso primeiro mapa e nossa primeira navegação!
2. Criando um mapa
Para criar nosso mapa, vamos primeiro precisar de um robô. Está com o seu
turtlebot aí do lado? Ótimo! Só garanta que ele está no mesmo ROS_DOMAIN_ID
e
ignore a próxima instrução. Não está com o robô? Beleza também, pois podemos
usar o Gazebo. Para isso, rode o seguinte comando:
ros2 launch turtlebot3_gazebo turtlebot3_world.launch.py
Pode ser que esse comando trave ao rodar pela primeira vez. Quando isso
aconteceu, tive sucesso ao mudar o modelo do robô (TURTLEBOT_MODEL
) para
waffle
. Após isso, o Gazebo abriu e, curiosamente, consegui mudar novamente
para o burger
e também abriu sem problemas. Tente fazer isso também se tiver
probleams.
Esse comando deve abrir uma janela com um mundo de teste e um turtlebot já carregado. Vamos mover o robô? Para isso, use:
ros2 run turtlebot3_teleop teleop_keyboard
Siga as instruções que aparecem no terminal para movimentar o robô.
Não é possível mover o robô se a janela do terminal com o teleop não estiver em foco. Isso pode deixar meio atabalhoado ver o robô e comandá-lo ao mesmo tempo. Que tal pedir ajuda ao amigo do lado e já testar a configuração de rede do ROS2 de vocês?
A seguir, vamos usar outro lançador para rodar os nós necessários para criar o mapa. Rode:
ros2 launch turtlebot3_cartographer cartographer.launch.py use_sim_time:=True
Notou o argumento use_sim_time:=True
? Para mapear um ambiente com o robô
real, basta que essa variável seja False
Se tudo deu certo, você deve ter agora uma segunda janela aberta, dessa vez com um software chamado Rviz. O Rviz é uma ferramenta de visualização 3D para o ROS que permite aos usuários visualizar dados de sensores, estados de robôs, trajetórias e outros aspectos relacionados à robótica em tempo real.
E é isso! Já estamos mapeando! Mova seu robô pelo ambiente e você vai perceber que o mapa vai aos poucos sendo construído. Quando seu mapa já não tiver quase nenhum ponto indefinido, você pode salvá-lo no seu computador para usar mais para frente durante a navegação. Para isso, rode:
ros2 run nav2_map_server map_saver_cli -f <nome-do-mapa>
Substitua o nome-do-mapa
pelo local onde quer gravar seu mapa. Eu coloquei
dentro da minha pasta Documents
, então o comando ficou assim:
mkdir -p ~/Documents/Maps
ros2 run nav2_map_server map_saver_cli -f ~/Documents/Maps/my-map
Prontinho! Você agora deve ter um arquivo de imagem (pgm
) e outro arquivo com
metadados sobre o mapa (yaml
). É só disso que precisa para poder navegar na
próxima etapa!
Apenas como ponto de comparação, meu mapa nesse momento estava assim:
3. Navegando no mapa criado
Estamos quase lá! Agora, para que possamos navegar usando nosso mapa, vamos usar os seguintes lançadores:
# Se ainda estiver com o gazebo aberto, não precisa disso
ros2 launch turtlebot3_gazebo turtlebot3_world.launch.py
E:
# Substitua o arquivo-do-mapa pelo local onde está o seu mapa
ros2 launch turtlebot3_navigation2 navigation2.launch.py use_sim_time:=True map:=<arquivo-do-mapa>.yaml
Se tudo deu certo, você deve estar com o Gazebo e o Rviz abertos novamente.
Toda a nossa navegação vai acontecer na janela do Rviz. Lá, você primeiro vai
setar a initial pose e depois vai mandar poses para que o robô navegue até
elas. Veja o gif
abaixo para uma referência visual do processo:
4. Usando o Simple Commander API
O Simple Commander é uma API feita em Python para interagir com as ações e serviços do Nav2 sem precisar criar os nós para isso diretamente. Com ele, é possível setar a pose inicial, passar pontos como objetivo e até mesmo criar uma lista de pontos pelos quais o robô obrigatoriamente deve passar.
4.1. Setup simple commander
Para fazer o setup do simple commander, vamos precisar instalar alguns pacotes. Rode:
sudo apt install ros-humble-nav2-simple-commander ros-humble-tf-transformations python3-transforms3d
O ros-humble-nav2-simple-commander
é a API em si, já o
ros-humble-tf-transformations
e python3-transforms3d
são duas bibliotecas
que permitem fazer transformações entre quaternions e ângulos de Euler, que é
algo útil para trabalhar com a pose de forma intuitiva.
4.2. Enviando a pose inicial
A seguir, vamos criar um script para utilizar o simple commander. Para isso, crie um novo arquivo com:
touch nav2_test.py
chmod +x nav2_test.py
O conteúdo desse arquivo pode ser visto abaixo:
#! /usr/bin/env python3
import rclpy
from nav2_simple_commander.robot_navigator import BasicNavigator
from geometry_msgs.msg import PoseStamped
from tf_transformations import quaternion_from_euler
rclpy.init()
nav = BasicNavigator()
q_x, q_y, q_z, q_w = quaternion_from_euler(0.0, 0.0, 0.0)
initial_pose = PoseStamped()
initial_pose.header.frame_id = 'map'
initial_pose.header.stamp = nav.get_clock().now().to_msg()
initial_pose.pose.position.x = 0.0
initial_pose.pose.position.y = 0.0
initial_pose.pose.position.z = 0.0
initial_pose.pose.orientation.x = q_x
initial_pose.pose.orientation.y = q_y
initial_pose.pose.orientation.z = q_z
initial_pose.pose.orientation.w = q_w
nav.setInitialPose(initial_pose)
nav.waitUntilNav2Active()
rclpy.shutdown()
O vídeo abaixo demonstra o funcionamento desse script:
Apesar de usarmos um script simples nesse exemplo, o mais indicado é criar um pacote em um workspace ROS para lidar com o simple commander
4.3. Navegando até pose
Vamos criar agora um novo script, agora para enviar uma pose como objetivo para o Nav2. Crie o arquivo com:
touch nav2_go_to_pose.py
chmod +x nav2_go_to_pose.py
E preencha o arquivo com:
#! /usr/bin/env python3
import rclpy
from nav2_simple_commander.robot_navigator import BasicNavigator
from geometry_msgs.msg import PoseStamped
from tf_transformations import quaternion_from_euler
from math import pi
rclpy.init()
nav = BasicNavigator()
q_x, q_y, q_z, q_w = quaternion_from_euler(0.0, 0.0, pi/4)
goal_pose = PoseStamped()
goal_pose.header.frame_id = 'map'
goal_pose.header.stamp = nav.get_clock().now().to_msg()
goal_pose.pose.position.x = 1.0
goal_pose.pose.position.y = 0.0
goal_pose.pose.position.z = 0.0
goal_pose.pose.orientation.x = q_x
goal_pose.pose.orientation.y = q_y
goal_pose.pose.orientation.z = q_z
goal_pose.pose.orientation.w = q_w
nav.goToPose(goal_pose)
while not nav.isTaskComplete():
print(nav.getFeedback())
rclpy.shutdown()
O vídeo abaixo exemplifica o comportamento do script:
Note que, embora os scripts estejam fazendo dois passos separadamente, é possível criar apenas um script que configura a pose inicial e manda um objetivo ao nav2
4.4. Navegando através de waypoints
Vamos agora criar um terceiro exemplo. Nesse, vamos trabalhar com vários waypoints e o nav2 fazendo com que o robô vá para cada um deles em sequência. Vamos criar o arquivo:
touch nav_waypoints.py
chmod +x nav_waypoints.py
E vamos preencher ele com:
- Script
- Função p/ criar poses
#! /usr/bin/env python3
import rclpy
from nav2_simple_commander.robot_navigator import BasicNavigator
from geometry_msgs.msg import PoseStamped
from tf_transformations import quaternion_from_euler
from math import pi
rclpy.init()
nav = BasicNavigator()
goal_pose1 = create_pose_stamped(nav, 2.5, 1.0, 1.57)
goal_pose2 = create_pose_stamped(nav, 0.0, 1.0, 1.57)
goal_pose3 = create_pose_stamped(nav, 0.0, 0.0, 0.00)
waypoints = [goal_pose1, goal_pose2, goal_pose3]
nav.followWaypoints(waypoints)
while not nav.isTaskComplete():
print(nav.getFeedback())
rclpy.shutdown()
def create_pose_stamped(navigator, pos_x, pos_y, rot_z):
q_x, q_y, q_z, q_w = tf_transformations.quaternion_from_euler(0.0, 0.0, rot_z)
pose = PoseStamped()
pose.header.frame_id = 'map'
pose.header.stamp = nav.get_clock().now().to_msg()
pose.pose.position.x = pos_x
pose.pose.position.y = pos_y
pose.pose.position.z = pos_x
pose.pose.orientation.x = q_x
pose.pose.orientation.y = q_y
pose.pose.orientation.z = q_z
pose.pose.orientation.w = q_w
return pose
Note que trata-se basicamente do exemplo anterior, porém com uma lista de
pontos chamada waypoints
.
5. Trocando o gazebo pelo webots
O gazebo classic já é um software antigo e com suporte limitado. Sendo assim, é possível observar alguns bugs em sua execução. Uma alternativa mais moderna é o webots. Segue um breve tutorial de como botar o webots para funcionar em conjunto com o nav2:
5.1. Instalando o webots
Para instalar o webots, vamos seguir o tutorial oficial. Primeiro, vamos configurar a nova fonte do apt:
sudo mkdir -p /etc/apt/keyrings
cd /etc/apt/keyrings
sudo wget -q https://cyberbotics.com/Cyberbotics.asc
echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/Cyberbotics.asc] https://cyberbotics.com/debian binary-amd64/" | sudo tee /etc/apt/sources.list.d/Cyberbotics.list
A seguir, rode um update
para ver se está tudo ok:
sudo apt update
Se o comando executar com sucesso, significa que estamos prontos para instalar o webots:
sudo apt install webots
Para finalizar, vamos instalar o pacote do ros2 para interagir com o webots:
sudo apt install ros-humble-webots-ros2
5.2. Rodando o nav2 com o webots
Vamos rodar dois launchers aqui. Um para rodar o turtlebot no webots:
ros2 launch webots_ros2_turtlebot robot_launch.py
E outro para o mapeamento e navegação simultanea:
ros2 launch nav2_bringup tb3_simulation_launch.py slam:=True
O lançador acima funciona também em conjunto com o Gazebo. Ele permite que você navegue e mapeie simultaneamente.
Assim como no exemplo com o gazebo, para salvar o mapa rode:
ros2 run nav2_map_server map_saver_cli -f <caminho-arquivo-mapa>
Terminou de mapear e quer só navegar com um mapa já pronto? Rode o lançador do
nav2_bringup
, só que com essas opções:
ros2 launch nav2_bringup tb3_simulation_launch.py map:=<arquivo-do-mapa>.yaml
Assim como no exemplo com o Gazebo, o tb3_simulation_launch
conta com o
argumento use_sim_time
. Vale o teste se ele também não serve para comandar o
robô real.
5.3. Modificando o mundo padrão do webots
Usando o launcher do webots_ros2_turtlebot
é impossível modificar o mundo e
salvá-lo, pois ele está dentro de uma pasta protegida contra escrita. Existem
alguns meios de modificar o arquivo do mundo. Minha sugestão é uma rápida e
suja. Primeiro, você deve abrir o webots:
webots
Agora, abra um mundo usando Ctrl+Shift+O
. Você deve procurar pelo arquivo
/opt/ros/humble/share/webots_ros2_turtlebot/worlds/turtlebot3_burger_example.wbt
.
Abra esse mundo. Agora, basta editar o mundo como quiser. Quando terminar,
salve-o indo em File->Save World As
. Salve o mundo novo em sua home
. A
seguir, feche o webots e vamos rodar mais alguns comandos:
sudo mv /opt/ros/humble/share/webots_ros2_turtlebot/worlds/turtlebot3_burger_example.wbt /opt/ros/humble/share/webots_ros2_turtlebot/worlds/turtlebot3_burger_example.wbt.bkup
Isso vai garantir que você não vai perder o mundo original. Agora, mova o
arquivo que você salvou na home
para a pasta de mundos do pacote:
sudo mv <novo-mundo-salvo>.wbt /opt/ros/humble/share/webots_ros2_turtlebot/worlds/turtlebot3_burger_example.wbt
Pronto! Agora, quando você rodar o launcher do webots com o turtlebot, o mundo que será carregado será o que você editou!