Simulaciones - IvanCS-Chenfu/Gazebo GitHub Wiki
Una vez creado el URDF del robot, necesitaremos un archivo el cual inserte el robot en Gazebo usando el servicio /spawn_entity
En el archivo sim_box_bot.cpp
#include <rclcpp/rclcpp.hpp>
Importa la API principal de ROS 2 en C++. Permite crear nodos, imprimir logs, usar servicios, etc.
#include <gazebo_msgs/srv/spawn_entity.hpp>
Este es el tipo de mensaje de servicio que se usa para insertar modelos en Gazebo. Es parte del paquete gazebo_msgs
.
Servicio: /spawn_entity
Tipo: gazebo_msgs::srv::SpawnEntity
#include <ament_index_cpp/get_package_share_directory.hpp>
Esta utilidad de ROS 2 permite obtener la ruta absoluta del paquete my_box_robot
para acceder al archivo espinas_bot.xacro
.
#include <fstream>
Librería estándar de C++ para leer archivos como el URDF.
class BoxSpawner : public rclcpp::Node {
Definimos una clase que hereda de rclcpp::Node
. Al instanciarse, este nodo:
Se conecta al servicio /spawn_entity
Lee el archivo URDF
Envía una petición para insertar el robot
BoxSpawner() : Node("box_spawner") {
Inicializa el nodo con el nombre box_spawner
. Esto aparecerá en ROS 2 como nombre de nodo.
auto client = this->create_client<gazebo_msgs::srv::SpawnEntity>("/spawn_entity");
Crea un cliente para el servicio /spawn_entity
(disponible si usas libgazebo_ros_factory.so
en el mundo).
while (!client->wait_for_service(std::chrono::seconds(1))) {
RCLCPP_INFO(this->get_logger(), "Esperando el servicio /spawn_entity...");
}
Este bucle espera a que el servicio esté disponible. Sin esto, podrías fallar si Gazebo no ha terminado de inicializarse.
auto request = std::make_shared<gazebo_msgs::srv::SpawnEntity::Request>();
Creamos una instancia del mensaje de tipo SpawnEntity::Request
.
request->name = "espinas_bot"; // aqui
Nombre del modelo dentro de Gazebo. No es el nombre del archivo, es cómo se verá en el entorno simulado.
request->xml = read_urdf();
Cargamos el contenido del archivo .xacro
(convertido a string). Este es el modelo que Gazebo insertará.
request->robot_namespace = "/espinas_bot"; // aqui
Este es el namespace
de todos los topics del robot. Si usas esto, los topics serán como:
/espinas_bot/cmd_vel
/espinas_bot/joint_states
Esto evita colisiones si tienes múltiples robots en el mismo entorno.
request->initial_pose.position.x = 0.0;
request->initial_pose.position.y = 0.0;
request->initial_pose.position.z = 0.15;
Define la posición del robot en el mundo (x, y, z). z = 0.15
eleva el robot ligeramente sobre el suelo.
auto result = client->async_send_request(request);
Envía la petición de forma asíncrona al servicio de Gazebo.
if (rclcpp::spin_until_future_complete(this->get_node_base_interface(), result) ==
rclcpp::FutureReturnCode::SUCCESS)
Esto hace que el nodo se quede bloqueado hasta que reciba una respuesta del servicio. Si el resultado es exitoso
std::string read_urdf() {
std::string urdf_path = ament_index_cpp::get_package_share_directory("my_box_robot") + "/urdf/espinas_bot.xacro";
Obtiene la ruta absoluta del archivo espinas_bot.xacro
dentro del paquete my_box_robot
.
std::ifstream urdf_file(urdf_path);
std::stringstream buffer;
buffer << urdf_file.rdbuf();
return buffer.str();
}
Lee todo el archivo y lo convierte en un string para enviarlo como request->xml
.
int main(int argc, char **argv) {
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<BoxSpawner>());
rclcpp::shutdown();
return 0;
}
Inicializa ROS 2
Lanza el nodo BoxSpawner
Espera a que termine (spin
)
Apaga ROS 2 correctamente
En el archivo CMAkeLists.txt
Para compilar este archivo necesitas:
add_executable(sim_box_bot src/sim_box_bot.cpp)
ament_target_dependencies(sim_box_bot
rclcpp
gazebo_msgs
ament_index_cpp
)
En el archivo package.xml
Para compilar este archivo necesitas:
<depend>rclcpp</depend>
<depend>gazebo_msgs</depend>
<depend>ament_index_cpp</depend>
El archivo sim_world.launch.py.
from launch import LaunchDescription
from launch.actions import ExecuteProcess
from launch_ros.actions import Node
import os
Importa lo necesario de launch
para ejecutar procesos
Importa Node
de launch_ros.actions
, que permite lanzar nodos ROS 2 directamente.
os
se usa para construir rutas dinámicamente
def generate_launch_description():
Función principal que devuelve un LaunchDescription
, es decir, la lista de cosas que se lanzarán con ros2 launch
.
world_path = os.path.join(
os.getenv('HOME'),
'ros', 'Gazebo_ws', 'src', 'my_box_robot', 'worlds', 'empty.world'
)
Construye la ruta absoluta al archivo empty.world
usando la variable de entorno HOME
.
return LaunchDescription([
ExecuteProcess(
cmd=[
'gazebo',
'--verbose',
world_path,
'-s', 'libgazebo_ros_factory.so'
],
output='screen'
)
])
Gazebo en modo detallado (--verbose
)
El mundo empty.world
Con el plugin libgazebo_ros_factory.so
(para usar /spawn_entity
)
Node(
package='my_box_robot',
executable='sim_box_bot',
name='spawn_bot_node',
output='screen'
)
Este bloque lanza el ejecutable sim_box_bot
del paquete my_box_robot
.
package
: el nombre del paquete
executable
: el nombre del ejecutable compilado (debe coincidir con el de CMakeLists.txt
)
name
: el nombre del nodo que aparecerá en ros2 node list
output='screen'
: imprime los logs en pantalla
En el archivo CMAkeLists.txt
add_executable(sim_box_bot src/sim_box_bot.cpp)
ament_target_dependencies(sim_box_bot
rclcpp
gazebo_msgs
ament_index_cpp
)
install(TARGETS sim_box_bot
DESTINATION lib/${PROJECT_NAME}
)
Compila el nodo
Declara sus dependencias
Instala el ejecutable para que ros2 launch
lo encuentre
install(DIRECTORY
launch
DESTINATION share/${PROJECT_NAME}/
)
Así se copia tu carpeta launch/
al entorno de instalación de ROS.
En el archivo package.xml
Asegúrate de tener:
<depend>rclcpp</depend>
<depend>gazebo_msgs</depend>
<depend>ament_index_cpp</depend>