Introdução ao Robot Operating System (ROS)
O Robot Operating System (ROS) é um framework de código aberto para desenvolvimento de software robótico. Apesar do nome, não é um sistema operacional, mas uma coleção de ferramentas, bibliotecas e convenções que simplificam a criação de sistemas robóticos complexos. Desenvolvido pela Open Robotics, o ROS é amplamente usado em robôs industriais, drones, veículos autônomos e pesquisa acadêmica.
Existem duas versões principais:
- ROS 1: Lançado em 2007, focado em simplicidade, mas com limitações em sistemas distribuídos.
- ROS 2: Introduzido em 2017, projetado para aplicações modernas, com suporte a tempo real, sistemas embarcados e redes distribuídas.
source /opt/ros/humble/setup.bash
ros2 run demo_nodes_cpp talker
Explicação: O comando source
configura o ambiente ROS 2, carregando variáveis e bibliotecas. O ros2 run
executa o nó talker
, que publica mensagens. Isso demonstra a modularidade do ROS, onde nós (programas) comunicam-se via mensagens.
Motivo: Iniciar o ROS 2 e executar um nó é o primeiro passo para entender sua arquitetura distribuída, permitindo comunicação entre componentes.
Resultado: O nó talker
começa a publicar mensagens periódicas, visíveis em um terminal com ros2 topic echo
.
Características do ROS
O ROS fornece um ecossistema robusto para robótica, com ferramentas para comunicação, visualização e integração de hardware.
🎯 Características principais
Modularidade
Divisão em nós (nodes) que executam tarefas específicas, comunicando-se via mensagens.
Comunicação Distribuída
Suporte a tópicos, serviços e ações para troca de dados entre processos, mesmo em máquinas diferentes.
Integração com Hardware
Drivers para sensores, atuadores e plataformas robóticas (ex.: LIDAR, câmeras).
Ferramentas de Visualização
RViz e Gazebo para simulação e depuração de robôs.
Explicação: A modularidade permite dividir um sistema robótico em componentes independentes (ex.: um nó para controle de motores, outro para processamento de sensores). A comunicação distribuída usa o middleware DDS em ROS 2, garantindo robustez em redes.
Motivo: Essas características tornam o ROS flexível, reutilizável e escalável, ideal para projetos complexos.
Resultado: Desenvolvedores podem criar, testar e integrar módulos sem reescrever código, acelerando o desenvolvimento.
Arquitetura do ROS 2
O ROS 2 é construído sobre o Data Distribution Service (DDS), um padrão de comunicação que suporta sistemas distribuídos e tempo real. Sua arquitetura é baseada em nós, tópicos, serviços e ações.
📌 Componentes principais
- Nós (Nodes): Processos que executam tarefas específicas (ex.: leitura de sensores).
- Tópicos (Topics): Canais para comunicação assíncrona via publicador-assinante.
- Serviços (Services): Comunicação síncrona para requisição-resposta.
- Ações (Actions): Tarefas de longa duração com feedback (ex.: navegação).
- DDS Middleware: Gerencia a comunicação entre nós, oferecendo confiabilidade e baixa latência.
ros2 node list
Explicação: O comando ros2 node list
exibe todos os nós ativos no sistema ROS 2, identificando processos em execução. Cada nó é um programa independente que se comunica via tópicos ou serviços.
Motivo: Entender a arquitetura ajuda a projetar sistemas modulares, onde cada nó tem uma função clara, facilitando manutenção e escalabilidade.
Resultado: Executar o comando retorna uma lista de nós, como /talker
ou /listener
, se estiverem rodando.
Nó
Unidade executável que realiza uma tarefa específica.
Tópico
Canal para mensagens contínuas entre publicadores e assinantes.
Serviço
Comunicação bidirecional para tarefas rápidas.
Instalando o ROS 2 no Ubuntu
O ROS 2 é compatível com Linux (Ubuntu é recomendado), macOS e Windows, mas Ubuntu é o ambiente principal. Aqui, instalamos o ROS 2 Humble Hawksbill no Ubuntu 22.04.
⚙️ Passos de instalação
# Configurar fontes do ROS 2
sudo apt update && sudo apt install curl gnupg lsb-release
sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null
# Instalar ROS 2 Humble
sudo apt update
sudo apt install ros-humble-desktop
# Configurar ambiente
echo "source /opt/ros/humble/setup.bash" >> ~/.bashrc
source ~/.bashrc
Explicação: Os comandos adicionam o repositório oficial do ROS 2, instalam a versão completa (ros-humble-desktop
) com ferramentas como RViz, e configuram o ambiente para carregar o ROS 2 automaticamente. O pacote desktop
inclui bibliotecas, ferramentas e exemplos.
Motivo: Configurar o ambiente corretamente é essencial para executar nós e usar ferramentas do ROS 2 sem erros.
Resultado: Após a instalação, comandos como ros2 run
e ros2 topic
estão disponíveis. Executar ros2 --version
confirma a instalação (ex.: "humble").
ros2 --version
Nós e Comunicação no ROS 2
Nós são os blocos fundamentais do ROS, cada um executando uma tarefa específica. Eles se comunicam via tópicos (publicador-assinante), serviços (requisição-resposta) ou ações (tarefas prolongadas).
📌 Criando um nó simples
# simple_node.py
import rclpy
from rclpy.node import Node
class SimpleNode(Node):
def __init__(self):
super().__init__('simple_node')
self.get_logger().info('Nó simples iniciado!')
def main():
rclpy.init()
node = SimpleNode()
rclpy.spin(node)
rclpy.shutdown()
if __name__ == '__main__':
main()
Explicação: O script cria um nó chamado simple_node
usando a biblioteca rclpy
(ROS Client Library para Python). O método rclpy.spin
mantém o nó ativo, e get_logger
exibe uma mensagem no terminal. O ROS 2 usa o DDS para gerenciar comunicação entre nós.
Motivo: Criar nós é o primeiro passo para construir sistemas robóticos modulares, permitindo que cada componente seja desenvolvido e testado separadamente.
Resultado: Executar ros2 run my_package simple_node
(após configurar um pacote ROS) inicia o nó, que imprime "Nó simples iniciado!" no terminal. O nó aparece em ros2 node list
.
ros2 run my_package simple_node
Publicadores e Assinantes
A comunicação via tópicos é o modelo mais comum no ROS, usando o padrão publicador-assinante. Um nó publicador envia mensagens a um tópico, e nós assinantes recebem essas mensagens.
📊 Exemplo: Publicador e Assinante
# talker.py
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
class Talker(Node):
def __init__(self):
super().__init__('talker')
self.publisher = self.create_publisher(String, 'chatter', 10)
self.timer = self.create_timer(1.0, self.timer_callback)
def timer_callback(self):
msg = String()
msg.data = 'Olá, ROS 2!'
self.publisher.publish(msg)
self.get_logger().info('Publicando: "%s"' % msg.data)
def main():
rclpy.init()
node = Talker()
rclpy.spin(node)
rclpy.shutdown()
if __name__ == '__main__':
main()
# listener.py
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
class Listener(Node):
def __init__(self):
super().__init__('listener')
self.subscription = self.create_subscription(
String, 'chatter', self.listener_callback, 10)
def listener_callback(self, msg):
self.get_logger().info('Recebido: "%s"' % msg.data)
def main():
rclpy.init()
node = Listener()
rclpy.spin(node)
rclpy.shutdown()
if __name__ == '__main__':
main()
Explicação: O talker.py
cria um nó que publica mensagens do tipo String
no tópico chatter
a cada segundo. O listener.py
cria um nó que se inscreve no mesmo tópico, recebendo e exibindo as mensagens. O ROS 2 gerencia a comunicação via DDS.
Motivo: O modelo publicador-assinante é ideal para comunicação contínua, como dados de sensores ou comandos de controle.
Resultado: Executar ros2 run my_package talker
e ros2 run my_package listener
em terminais separados faz o listener
exibir "Recebido: Olá, ROS 2!" a cada segundo.
ros2 topic echo /chatter
Exemplos Práticos com ROS 2
A seguir, exemplos práticos demonstram como usar o ROS 2 para tarefas robóticas comuns, incluindo simulações com Gazebo e CoppeliaSim.
📊 Exemplo 1: Publicando dados de sensor
# sensor_publisher.py
import rclpy
from rclpy.node import Node
from sensor_msgs.msg import Range
import random
class SensorPublisher(Node):
def __init__(self):
super().__init__('sensor_publisher')
self.publisher = self.create_publisher(Range, 'sensor_range', 10)
self.timer = self.create_timer(0.5, self.timer_callback)
def timer_callback(self):
msg = Range()
msg.header.frame_id = 'sensor_frame'
msg.range = random.uniform(0.1, 5.0)
self.publisher.publish(msg)
self.get_logger().info(f'Distância: {msg.range:.2f} m')
def main():
rclpy.init()
node = SensorPublisher()
rclpy.spin(node)
rclpy.shutdown()
if __name__ == '__main__':
main()
Explicação: O nó simula um sensor de distância, publicando mensagens do tipo Range
no tópico sensor_range
a cada 0.5 segundos. O campo range
contém um valor aleatório entre 0.1 e 5.0 metros.
Motivo: Sensores são comuns em robótica, e este exemplo mostra como publicar dados para outros nós processarem.
Resultado: Executar o nó com ros2 run my_package sensor_publisher
publica valores de distância, visíveis com ros2 topic echo /sensor_range
.
🔄 Exemplo 2: Serviço de controle
# control_service.py
import rclpy
from rclpy.node import Node
from std_srvs.srv import Trigger
class ControlService(Node):
def __init__(self):
super().__init__('control_service')
self.srv = self.create_service(Trigger, 'toggle_robot', self.toggle_callback)
def toggle_callback(self, request, response):
response.success = True
response.message = 'Robô ligado!' if random.choice([True, False]) else 'Robô desligado!'
self.get_logger().info(response.message)
return response
def main():
rclpy.init()
node = ControlService()
rclpy.spin(node)
rclpy.shutdown()
if __name__ == '__main__':
main()
Explicação: O nó cria um serviço chamado toggle_robot
que simula ligar/desligar um robô, retornando uma mensagem de sucesso. O serviço usa o tipo Trigger
, que não requer dados de entrada.
Motivo: Serviços são úteis para comandos pontuais, como ativar/desativar componentes.
Resultado: Executar ros2 run my_package control_service
inicia o serviço. Chamar ros2 service call /toggle_robot std_srvs/srv/Trigger
retorna a resposta (ex.: "Robô ligado!").
🚗 Exemplo 3: Simulação de Robô Diferencial no Gazebo
Este exemplo simula um robô diferencial com LIDAR no Gazebo, controlado por comandos de velocidade via ROS 2 e visualizado no RViz.
Passos:
- Instale o Gazebo e o pacote de integração com ROS 2:
sudo apt install ros-humble-ros-gz
cd ~/ros2_ws/src
ros2 pkg create --build-type ament_cmake mybot_gazebo --dependencies rclpy geometry_msgs sensor_msgs ros_gz_bridge
mybot.urdf
) para o robô diferencial com rodas, chassi e LIDAR (simplificado para o exemplo):
Gazebo/Blue
1.0
1.0
1.0
1.0
0 0 0 0 0 0
360
1
-3.14
3.14
0.1
10.0
0.01
/mybot
scan:=laser_scan
lidar
/mybot
base_to_left_wheel
base_to_right_wheel
0.4
0.2
20
cmd_vel
odom
odom
base_link
launch/sim_gazebo.launch.py
) para iniciar o Gazebo e conectar com ROS 2:import os
from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration
from launch_ros.actions import Node
import xacro
def generate_launch_description():
pkg_path = get_package_share_directory('mybot_gazebo')
urdf_path = os.path.join(pkg_path, 'urdf', 'mybot.urdf')
doc = xacro.process_file(urdf_path)
robot_desc = doc.toxml()
return LaunchDescription([
DeclareLaunchArgument('use_sim_time', default_value='true', description='Use simulation time'),
Node(
package='robot_state_publisher',
executable='robot_state_publisher',
name='robot_state_publisher',
output='screen',
parameters=[{'robot_description': robot_desc, 'use_sim_time': LaunchConfiguration('use_sim_time')}]
),
Node(
package='gz_ros2_control',
executable='gz_ros2_control_demos',
arguments=['-gazebo_namespace', '/gzserver', '-controller_manager_name', '/controller_manager'],
output='screen'
),
Node(
package='ros_gz_bridge',
executable='parameter_bridge',
arguments=['/mybot/laser_scan@sensor_msgs/msg/LaserScan@gz.msgs.LaserScan',
'/mybot/cmd_vel@geometry_msgs/msg/Twist@gz.msgs.Twist',
'/mybot/odom@nav_msgs/msg/Odometry@gz.msgs.Odometry'],
output='screen'
),
Node(
package='gz_sim',
executable='gz_sim',
arguments=['-r', 'empty.sdf'],
output='screen'
),
Node(
package='ros_gz_sim',
executable='create',
arguments=['-topic', '/robot_description', '-name', 'mybot'],
output='screen'
),
])
cd ~/ros2_ws
colcon build
source install/setup.bash
ros2 launch mybot_gazebo sim_gazebo.launch.py
ros2 topic pub /mybot/cmd_vel geometry_msgs/msg/Twist "{linear: {x: 0.5}, angular: {z: 0.0}}"
rviz2
Explicação: O arquivo URDF define um robô diferencial com duas rodas e um LIDAR, usando plugins do Gazebo para movimentação (libgazebo_ros_diff_drive.so
) e sensores (libgazebo_ros_laser.so
). O arquivo de lançamento inicia o Gazebo, publica o modelo do robô, e conecta tópicos ROS 2 com o Gazebo via ros_gz_bridge
. O comando ros2 topic pub
envia velocidades lineares, fazendo o robô se mover. O RViz visualiza os dados do LIDAR no tópico /mybot/laser_scan
.
Motivo: O Gazebo é amplamente usado para simulações realistas com física (gravidade, colisões) e sensores, permitindo testar algoritmos de navegação e percepção em um ambiente virtual antes de usar hardware real. A integração com ROS 2 facilita o desenvolvimento modular.[](https://docs.ros.org/en/humble/Tutorials/Advanced/Simulators/Gazebo/Gazebo.html)
Resultado: O Gazebo exibe o robô em um mundo vazio, movendo-se conforme os comandos de velocidade. O RViz mostra os dados do LIDAR como pontos em 2D, permitindo verificar a percepção do ambiente. O tópico /mybot/odom
publica odometria, útil para navegação.
🤖 Exemplo 4: Simulação de Robô Diferencial no CoppeliaSim
Este exemplo simula um robô diferencial com sensor de proximidade no CoppeliaSim, controlado via Python e integrado com ROS 2.
Passos:
- Instale o CoppeliaSim EDU (versão gratuita para uso educacional) e o pacote ROS 2 para CoppeliaSim:
sudo apt install ros-humble-coppeliasim
cd ~/ros2_ws/src
ros2 pkg create --build-type ament_python mybot_coppelia --dependencies rclpy geometry_msgs sensor_msgs
mybot_scene.ttt
):
- Adicione um robô diferencial (ex.: modelo "Pioneer_p3dx" do CoppeliaSim).
- Adicione um sensor de proximidade ao robô.
- Configure o script Lua do robô para publicar odometria e ler comandos de velocidade:
-- mybot_script.lua (anexado ao robô no CoppeliaSim)
function sysCall_init()
leftMotor=sim.getObjectHandle('Pioneer_p3dx_leftMotor')
rightMotor=sim.getObjectHandle('Pioneer_p3dx_rightMotor')
proxSensor=sim.getObjectHandle('Pioneer_p3dx_ultrasonicSensor1')
pub=simROS2.createPublisher('/mybot/odom', 'nav_msgs/Odometry')
sub=simROS2.createSubscriber('/mybot/cmd_vel', 'geometry_msgs/Twist', 'cmdVel_callback')
proxPub=simROS2.createPublisher('/mybot/proximity', 'sensor_msgs/Range')
end
function cmdVel_callback(msg)
local linear=msg.linear.x
local angular=msg.angular.z
local wheelRadius=0.1
local wheelDistance=0.4
local vl=(linear - angular * wheelDistance / 2) / wheelRadius
local vr=(linear + angular * wheelDistance / 2) / wheelRadius
sim.setJointTargetVelocity(leftMotor, vl)
sim.setJointTargetVelocity(rightMotor, vr)
end
function sysCall_sensing()
local result, dist=sim.readProximitySensor(proxSensor)
if result>0 then
local msg={header={frame_id='base_link'}, range=dist}
simROS2.publish(proxPub, msg)
end
end
function sysCall_cleanup()
simROS2.shutdownPublisher(pub)
simROS2.shutdownPublisher(proxPub)
simROS2.shutdownSubscriber(sub)
end
coppelia_controller.py
):# coppelia_controller.py
import rclpy
from rclpy.node import Node
from geometry_msgs.msg import Twist
class CoppeliaController(Node):
def __init__(self):
super().__init__('coppelia_controller')
self.publisher = self.create_publisher(Twist, '/mybot/cmd_vel', 10)
self.timer = self.create_timer(0.1, self.timer_callback)
def timer_callback(self):
msg = Twist()
msg.linear.x = 0.3 # Velocidade linear constante
msg.angular.z = 0.0
self.publisher.publish(msg)
self.get_logger().info('Publicando: linear=%f, angular=%f' % (msg.linear.x, msg.angular.z))
def main():
rclpy.init()
node = CoppeliaController()
rclpy.spin(node)
rclpy.shutdown()
if __name__ == '__main__':
main()
# Terminal 1: Iniciar CoppeliaSim com a cena
coppeliasim ~/ros2_ws/src/mybot_coppelia/scenes/mybot_scene.ttt
# Terminal 2: Executar o controlador
cd ~/ros2_ws
source install/setup.bash
ros2 run mybot_coppelia coppelia_controller
rviz2
Explicação: O CoppeliaSim usa um script Lua anexado ao robô para gerenciar a comunicação ROS 2, publicando odometria no tópico /mybot/odom
e dados do sensor de proximidade em /mybot/proximity
, enquanto lê comandos de velocidade de /mybot/cmd_vel
. O script Python publica comandos de velocidade constantes. O CoppeliaSim oferece cinco motores físicos (MuJoCo, Bullet, ODE, Vortex, Newton), permitindo simulações precisas de dinâmica e sensores. A cena é configurada com um robô Pioneer_p3dx, que inclui motores e um sensor de proximidade.
Motivo: O CoppeliaSim é versátil para simulações complexas, com suporte a múltiplos motores físicos e APIs (Python, Lua, C++), sendo ideal para educação e pesquisa. Sua integração com ROS 2 permite testar algoritmos em cenários customizados.[](https://pmc.ncbi.nlm.nih.gov/articles/PMC9415907/)
Resultado: O robô se move linearmente no CoppeliaSim, e o sensor de proximidade publica distâncias quando detecta objetos. No RViz, os dados de /mybot/proximity
são visualizados como valores de distância, e /mybot/odom
mostra a trajetória do robô.
ROS 1 vs ROS 2
O ROS 2 foi projetado para superar as limitações do ROS 1, especialmente em sistemas distribuídos e aplicações críticas.
Critério | ROS 1 | ROS 2 |
---|---|---|
Middleware | ROS Master (centralizado) | DDS (distribuído) |
Suporte a Tempo Real | Limitado | Robusto (DDS com QoS) |
Plataformas | Principalmente Linux | Linux, Windows, macOS, embarcados |
Comunicação | Tópicos, Serviços | Tópicos, Serviços, Ações |
Licença | BSD | Apache 2.0 |
Explicação: O ROS 1 usa um mestre centralizado, tornando-o menos robusto para redes distribuídas. O ROS 2 elimina o mestre, usando DDS para comunicação descentralizada e suporte a qualidade de serviço (QoS).
Motivo: O ROS 2 é mais adequado para aplicações modernas, como robôs autônomos e IoT, onde confiabilidade e tempo real são críticos.
Resultado: Projetos novos devem adotar o ROS 2, enquanto o ROS 1 é mantido para sistemas legados.
Projeto Final: Simulador de Robô Móvel
Crie um sistema ROS 2 que simula um robô móvel, com um nó publicando comandos de velocidade e outro processando a posição do robô.
📋 Passos do projeto
- Crie um pacote ROS 2 com
ros2 pkg create robot_sim
. - Implemente um nó publicador de velocidade (
velocity_publisher.py
). - Implemente um nó assinante que calcula a posição (
position_calculator.py
). - Teste com
ros2 topic echo
e visualize com RViz.
# velocity_publisher.py
import rclpy
from rclpy.node import Node
from geometry_msgs.msg import Twist
class VelocityPublisher(Node):
def __init__(self):
super().__init__('velocity_publisher')
self.publisher = self.create_publisher(Twist, 'cmd_vel', 10)
self.timer = self.create_timer(1.0, self.timer_callback)
def timer_callback(self):
msg = Twist()
msg.linear.x = 0.5 # Velocidade linear (m/s)
msg.angular.z = 0.2 # Velocidade angular (rad/s)
self.publisher.publish(msg)
self.get_logger().info(f'Publicando velocidade: linear={msg.linear.x}, angular={msg.angular.z}')
def main():
rclpy.init()
node = VelocityPublisher()
rclpy.spin(node)
rclpy.shutdown()
if __name__ == '__main__':
main()
# position_calculator.py
import rclpy
from rclpy.node import Node
from geometry_msgs.msg import Twist, Pose
class PositionCalculator(Node):
def __init__(self):
super().__init__('position_calculator')
self.subscription = self.create_subscription(Twist, 'cmd_vel', self.velocity_callback, 10)
self.publisher = self.create_publisher(Pose, 'robot_pose', 10)
self.x = 0.0
self.y = 0.0
self.theta = 0.0
self.timer = self.create_timer(0.1, self.update_pose)
def velocity_callback(self, msg):
self.linear_vel = msg.linear.x
self.angular_vel = msg.angular.z
def update_pose(self):
dt = 0.1
self.x += self.linear_vel * dt * self.theta
self.y += self.linear_vel * dt * self.theta
self.theta += self.angular_vel * dt
pose = Pose()
pose.position.x = self.x
pose.position.y = self.y
pose.orientation.z = self.theta
self.publisher.publish(pose)
self.get_logger().info(f'Posição: x={self.x:.2f}, y={self.y:.2f}, theta={self.theta:.2f}')
def main():
rclpy.init()
node = PositionCalculator()
rclpy.spin(node)
rclpy.shutdown()
if __name__ == '__main__':
main()
Explicação: O velocity_publisher
publica comandos de velocidade (Twist
) no tópico cmd_vel
. O position_calculator
recebe esses comandos, calcula a posição do robô (x, y, theta) e publica no tópico robot_pose
. A integração numérica simples atualiza a pose a cada 0.1s.
Motivo: Este projeto simula um robô móvel, um caso comum em robótica, ensinando comunicação entre nós e processamento de dados.
Resultado: Executar ambos os nós (após configurar o pacote) mostra a posição sendo atualizada no terminal. Usar ros2 topic echo /robot_pose
exibe as mensagens de pose, e RViz pode visualizar o movimento.
Quiz Interativo
Teste seus conhecimentos sobre ROS: