Este artigo explora o pacote move_base do ROS, um componente crucial para a navegação autônoma de robôs. Ele integra algoritmos de planejamento de caminho global e local para guiar o robô do ponto de partida até o destino, adaptando-se a obstáculos.
Esturtura de Dados para Metas
O ROS utiliza a estrutura MoveBaseActionGoal para definir os objetivos de navegação. Os campos mais importantes incluem a posição (x, y, z) e orientação (quaternion x, y, z, w) do alvo.
rosmsg show MoveBaseActionGoal
[move_base_msgs/MoveBaseActionGoal]:
std_msgs/Header header
uint32 seq
time stamp
string frame_id
actionlib_msgs/GoalID goal_id
time stamp
string id
move_base_msgs/MoveBaseGoal goal
geometry_msgs/PoseStamped target_pose
std_msgs/Header header
uint32 seq
time stamp
string frame_id
geometry_msgs/Pose pose
geometry_msgs/Point position
float64 x
float64 y
float64 z
geometry_msgs/Quaternion orientation
float64 x
float64 y
float64 z
float64 w
Configuração de Parâmetros
O move_base requer a configuração de diversos parâmetros, como raio do robô, distância de alcance do objetivo, velocidade e custos de operação. Esses parâmetros são geralmente definidos em arquivos YAML, como:
base_local_planner_params.yamlcostmap_common_params.yamlglobal_costmap_params.yamllocal_costmap_params.yaml
Planejamento de Caminho Global
O planejamento global, realizado por pacotes como navfn, determina a rota geral do robô até o destino. Algoritmos como Dijkstra ou A* são empregados para encontrar o caminho de menor custo no mapa de ocupação (costmap).
Planejamento de Caminho Local
O planejamento local, frequentemente implementado pelo pacote base_local_planner, é responsável por gerar comandos de velocidade (linear e angular) em tempo real para que o robô siga o caminho global e evite obstáculos imediatos. Algoritmos como Trajectory Rollout e Dynamic Window Approach (DWA) são utilizados:
- Amostragem de estados de velocidade do robô (dx, dy, dtheta).
- Simulação de trajetórias para cada estado amostrado.
- Avaliação das trajetórias com base em critérios como colisão e tempo.
- Seleção da trajetória ótima.
- Repetição do processo.
Simulação com ArbotiX: Definindo Metas Manuais
Para testes, pode-se usar um ambiente simulado com o ArbotiX. Inicie o simulador e o move_base com um mapa (por exemplo, um mapa em branco):
# Iniciar o simulador do robô
roslaunch rbx1_bringup fake_turtlebot.launch
# Iniciar o move_base com um mapa em branco
roslaunch rbx1_nav fake_move_base_blank_map.launch
Visualize o robô e o ambiente em RViz:
# Para versões Fuerte:
# rosrun rviz rviz -d `rospack find rbx1_nav`/nav_fuerte.vcg
# Para versões Indigo/Kinetic:
rosrun rviz rviz -d `rospack find rbx1_nav`/nav.rviz
É possível enviar metas diretamente via tópico /move_base_simple/goal:
# Mover 1 metro para frente
rostopic pub /move_base_simple/goal geometry_msgs/PoseStamped \
'{ header: { frame_id: "base_link" }, pose: { position: { x: 1.0, y: 0, z: 0 }, orientation: { x: 0, y: 0, z: 0, w: 1 } } }'
# Mover de volta à origem
rostopic pub /move_base_simple/goal geometry_msgs/PoseStamped \
'{ header: { frame_id: "map" }, pose: { position: { x: 0, y: 0, z: 0 }, orientation: { x: 0, y: 0, z: 0, w: 1 } } }'
Em RViz, a linha azul rerpesenta o caminho global planejado, e as setas vermelhas indicam o caminho local em tempo real. A opção 2D Nav Goal no RViz permite definir um ponto de destino interativamente.
Simulação com Obstáculos
Para testar o comportamento com obstáculos, utilize um lançamento que inclua um mapa com barreiras:
roslaunch rbx1_nav fake_move_base_obstacle.launch
Ao executar um script de navegação (como o move_base_square.py que faz o robô percorrer um quadrado), observa-se que o move_base ajusta o caminho global para contornar os obstáculos:
#!/usr/bin/env python
import roslib; roslib.load_manifest('rbx1_nav')
import rospy
import actionlib
from actionlib_msgs.msg import *
from geometry_msgs.msg import Pose, Point, Quaternion, Twist
from move_base_msgs.msg import MoveBaseAction, MoveBaseGoal
from tf.transformations import quaternion_from_euler
from visualization_msgs.msg import Marker
from math import radians, pi
class MoveBaseSquare:
def __init__(self):
rospy.init_node('nav_test', anonymous=False)
rospy.on_shutdown(self.shutdown)
square_size = rospy.get_param("~square_size", 1.0)
quaternions = list()
euler_angles = (pi/2, pi, 3*pi/2, 0)
for angle in euler_angles:
q_angle = quaternion_from_euler(0, 0, angle, axes='sxyz')
q = Quaternion(*q_angle)
quaternions.append(q)
waypoints = list()
waypoints.append(Pose(Point(square_size, 0.0, 0.0), quaternions[0]))
waypoints.append(Pose(Point(square_size, square_size, 0.0), quaternions[1]))
waypoints.append(Pose(Point(0.0, square_size, 0.0), quaternions[2]))
waypoints.append(Pose(Point(0.0, 0.0, 0.0), quaternions[3]))
self.init_markers()
for waypoint in waypoints:
p = Point()
p = waypoint.position
self.markers.points.append(p)
self.cmd_vel_pub = rospy.Publisher('cmd_vel', Twist)
self.move_base = actionlib.SimpleActionClient("move_base", MoveBaseAction)
rospy.loginfo("Waiting for move_base action server...")
self.move_base.wait_for_server(rospy.Duration(60))
rospy.loginfo("Connected to move base server")
rospy.loginfo("Starting navigation test")
i = 0
while i < 4 and not rospy.is_shutdown():
self.marker_pub.publish(self.markers)
goal = MoveBaseGoal()
goal.target_pose.header.frame_id = 'map'
goal.target_pose.header.stamp = rospy.Time.now()
goal.target_pose.pose = waypoints[i]
self.move(goal)
i += 1
def move(self, goal):
self.move_base.send_goal(goal)
finished_within_time = self.move_base.wait_for_result(rospy.Duration(60))
if not finished_within_time:
self.move_base.cancel_goal()
rospy.loginfo("Timed out achieving goal")
else:
state = self.move_base.get_state()
if state == GoalStatus.SUCCEEDED:
rospy.loginfo("Goal succeeded!")
def init_markers(self):
marker_scale = 0.2
marker_lifetime = 0
marker_ns = 'waypoints'
marker_id = 0
marker_color = {'r': 1.0, 'g': 0.7, 'b': 1.0, 'a': 1.0}
self.marker_pub = rospy.Publisher('waypoint_markers', Marker)
self.markers = Marker()
self.markers.ns = marker_ns
self.markers.id = marker_id
self.markers.type = Marker.SPHERE_LIST
self.markers.action = Marker.ADD
self.markers.lifetime = rospy.Duration(marker_lifetime)
self.markers.scale.x = marker_scale
self.markers.scale.y = marker_scale
self.markers.color.r = marker_color['r']
self.markers.color.g = marker_color['g']
self.markers.color.b = marker_color['b']
self.markers.color.a = marker_color['a']
self.markers.header.frame_id = 'map'
self.markers.header.stamp = rospy.Time.now()
self.markers.points = list()
def shutdown(self):
rospy.loginfo("Stopping the robot...")
self.move_base.cancel_goal()
rospy.sleep(2)
self.cmd_vel_pub.publish(Twist())
rospy.sleep(1)
if __name__ == '__main__':
try:
MoveBaseSquare()
except rospy.ROSInterruptException:
rospy.loginfo("Navigation test finished.")
As áreas sombreadas ao redor dos obstáculos em RViz representam o "buffer" de segurança inflado (definido pelo parâmetro inflation_radius), garantindo que o robô mantenha uma distância segura. É possível reconfigurar dinamicamente os parâmetros do move_base durante a execução para ajustar o comportamento de navegação.