Manejando Threads Para Crear un Action Server – Spanish ROS Tutorial

Manejando Threads Para Crear un Action Server – Spanish ROS Tutorial

This tutorial is created by Robotics Ambassador 017 Jose

Rosbotics Ambassador Program (https://www.theconstruct.ai/robotics-ambassador/)

Lo que vamos a aprender

  1. Usar múltiples threads en un nodo.
  2. Crear callbacks groups para el manejo de callbacks.
  3. Emplear un action server para comandar el Turtlebot3.

Lista de recursos usados en esta publicación

  1. Usa este rosject: https://app.theconstruct.ai/l/615821d8/
  2. The Construct: https://app.theconstructsim.com/
  3. Cursos ROS: ROS2 Basics in 5 Days (Python): https://app.theconstructsim.com/courses/132
  4. Documentación ROS Callback Groups: https://docs.ros.org/en/foxy/How-To-Guides/Using-callback-groups.html
  5. Documentación ROS Executors: https://docs.ros.org/en/foxy/Concepts/About-Executors.html

Resumen

ROS (Robot Operating System) se está convirtiendo en el “framework” estándar para programar robots. En este tutorial se hará uso de trheads y grupos de callbacks para poder crear un Action Server para el TurtleBot3.Todo esto es simulado en un mundo de Gazebo. Ten en cuenta que se usará ROS2 Humble para este tutorial.

1. Abriendo el rosject

Para seguir este tutorial, necesitamos tener instalado ROS2 HUMBLE en nuestro sistema, y lo ideal sería tener un ros2_ws (Espacio de Trabajo ROS2). Para facilitarte la vida, ya hemos preparado un rosject para eso: https://app.theconstruct.ai/l/603ea5b0/.

Simplemente copiando el rosject (haciendo clic en el enlace de arriba), tendrás una configuración ya preparada para ti.

Después de haber copiado el rosject a tu propia área de trabajo, deberías ver el botón RUN. Haz clic en ese botón para lanzar el rosject (abajo tienes un ejemplo de rosject).

Rosject

Tras pulsar el botón RUN, deberías tener cargado el rosject. Ahora, pasemos a la siguiente sección para ponernos manos a la obra.

2. Descripción

En ROS2, los subscribers, timers, service servers y action servers usan callbacks para su funcionamiento. Al recibir un mensaje o un llamado, ejecutan algún método predefinido. Por otro lado, al crear un nodo y definir subscribers, timers, etc. de manera básica, se tiene un thread principal que se encarga de manejar los callbacks. Dicho nodo al tener solo un thread, no puede ejecutar más de un callback al mismo tiempo.

Este comportamiento hace necesario el uso de múltiples threads y callback groups para tareas más complejas que requieran que se ejecute más de un callback al mismo tiempo.

En este tutorial aplicaremos múltiples threads y callbacks para un action server que realizará lo siguiente:

  • Subscribirse al tópico /odom para conocer la posición y orientación del TurtleBot3
  • Calcular la distancia recorrida por el robot durante su movimiento usando un Timer
  • Mover el robot por posiciones específicas que se mandarán al llamar al action server

Rosject

3. Desarrollo

 

Múltiples threads

En esta situación es necesario el uso de más de un thread, de lo contrario, al momento de que se haga una petición al action server, se ejecutará su correspondiente callback. Esto evitará que los callbacks del subscriptor y del timer se ejecuten hasta que se termine la ejecución de su callback.

Para usar multiples threads, primero necesitamos importar MultiThreadedExecutor:

from rclpy.executors import MultiThreadedExecutorfloat64

Luego definir el número de threads necesarios (2 para este caso):

    # Use MultiThreadedExecutor
    executor = MultiThreadedExecutor(num_threads=2)
    executor.add_node(my_action_server)
    
    try:
        executor.spin()
    finally:
        my_action_server.destroy_node()
        rclpy.shutdown()

 

Callback groups

Solamente definir múltiples threads no es suficiente ya que existen los callback groups.

Cuando se ejecuta un nodo en un Multi-Threaded Executor, ROS 2 ofrece dos tipos diferentes de grupos de callbacks para controlar la ejecución de estos:

  • Mutually Exclusive Callback Group: Los callbacks de este grupo no se pueden ejecutar en paralelo.
  • Reentrant Callback Group: Los callbacks de este grupo si pueden ejecutarse al mismo tiempo.

Se debe tener en cuenta que, los callbacks pertenecientes a diferentes grupos de callbacks (de cualquier tipo) siempre pueden ejecutarse en paralelo entre sí.

En este tutorial usaremos 2 Mutually Exclusive Callback Groups. A uno de ellos le asignaremos el callback del action server y al otro le asignaremos los callbacks del subscriptor y del timer. Esto nos permitirá que al enviarse un request al action server, los callbacks del subscriptor y del timer se sigan ejecuanto al estar en otro grupo. Adicionalmente, estos dos callbacks al estar en un mismo Mutually Exclusive Callback Group no se ejecutarán al mismo tiempo, lo cual se require ya que el subscriptor actualiza la posición actual mientras que en el timer se calcula la distancia recorrida en ese periodo de tiempo.

Para usar callback groups primero importamos el grupo a usar:

from rclpy.callback_groups import MutuallyExclusiveCallbackGroup

Luego definimos los grupos:

self.mutuallyexclusive_group_1 = MutuallyExclusiveCallbackGroup()
self.mutuallyexclusive_group_2 = MutuallyExclusiveCallbackGroup()

Estos grupos se tienen que asignar como argumento al momento de instanciar el subscriptor, timer y action server. Si no se realiza esto, de manera predefinida los callbacks se asignarán a un mismo Mutually Exclusive Callback Group, el cual no permite la ejecución de los callbacks en paralelo a pesar de usar múltiples threads. Entonces asignamos:

Para el subscriptor:

# Subscriber
        self.subscription = self.create_subscription(
            Odometry,
            'odom',
            self.listener_callback,
            10,
            callback_group=self.mutuallyexclusive_group_1)

Luego definimos los grupos:

# Timer
        timer_period = 0.5  # segundos
        self.timer = self.create_timer(
            timer_period, 
            self.timer_callback, 
            callback_group=self.mutuallyexclusive_group_1)

Luego definimos los grupos:

# Action
        self._action_server = ActionServer(
            self, Move, 'move_as', 
            self.execute_callback, 
            callback_group=self.mutuallyexclusive_group_2)

 

4. Explicación del código

El action server usá una interfaz personalizada que se compone de:

  • Request: 2 arrays de coordenadas para las posiciones que el robot debe alcanzar.
  • Result: Distancia total recorrida durante el movimiento y un flag de confirmación de que todo se ejecuto correctamente.
  • Feedback: Distancia recorrida hasta el momento y el número de la posición alcanzada.

Move.action

float32[] pos_x
float32[] pos_y
---
bool status
float32 total_dist
---
int32 nro_pos
float32 current_dist

El código completo para el action server es el siguiente:

server_node.py

import rclpy
from rclpy.action import ActionServer
from rclpy.node import Node
​
from custom_interfaces.action import Move
from nav_msgs.msg import Odometry
​
from geometry_msgs.msg import Twist
from std_msgs.msg import Float32
import time
import math
import numpy as np
​
from rclpy.executors import MultiThreadedExecutor
from rclpy.callback_groups import MutuallyExclusiveCallbackGroup
​
class MyActionServer(Node):
​
    def __init__(self):
        super().__init__('my_action_server')
​
        self.mutuallyexclusive_group_1 = MutuallyExclusiveCallbackGroup()
        self.mutuallyexclusive_group_2 = MutuallyExclusiveCallbackGroup()
​
        # Subscriber
        self.subscription = self.create_subscription(
            Odometry,
            'odom',
            self.listener_callback,
            10,
            callback_group=self.mutuallyexclusive_group_1)
​
        # Action
        self._action_server = ActionServer(
            self, Move, 'move_as', 
            self.execute_callback, 
            callback_group=self.mutuallyexclusive_group_2)
​
        # Publisher for cmd_vel
        self.publisher_ = self.create_publisher(Twist, '/cmd_vel', 10)
​
    def timer_callback(self):
​
        delta_x = (self.pos[0] - self.pos_before[0])**2
        delta_y = (self.pos[1] - self.pos_before[1])**2
        self.distance.data += math.sqrt(delta_x + delta_y)
        self.pos_before = self.pos
​
    def listener_callback(self, msg):
​
        self.pos = [msg.pose.pose.position.x, msg.pose.pose.position.y]
        quaternion = [msg.pose.pose.orientation.x,
                      msg.pose.pose.orientation.y,
                      msg.pose.pose.orientation.z,
                      msg.pose.pose.orientation.w]
        self.roll, self.pitch, self.yaw = self.euler_from_quaternion(quaternion)
​
    def euler_from_quaternion(self, quaternion):
        """
        Converts quaternion (w in last place) to euler roll, pitch, yaw
        quaternion = [x, y, z, w]
        Below should be replaced when porting for ROS2 Python tf_conversions is done.
        """
        x = quaternion[0]
        y = quaternion[1]
        z = quaternion[2]
        w = quaternion[3]
​
        sinr_cosp = 2 * (w * x + y * z)
        cosr_cosp = 1 - 2 * (x * x + y * y)
        roll = np.arctan2(sinr_cosp, cosr_cosp)
​
        sinp = 2 * (w * y - z * x)
        pitch = np.arcsin(sinp)
​
        siny_cosp = 2 * (w * z + x * y)
        cosy_cosp = 1 - 2 * (y * y + z * z)
        yaw = np.arctan2(siny_cosp, cosy_cosp)
​
        return roll, pitch, yaw
​
    def execute_movement(self, a, b):
​
        self.vel = Twist()
​
        # Velocidad de desplazamiento
        self.vel_linear_x = 0.1
        self.vel_angular_z = 0.2
​
        # Máximo error
        self.linear_error = 0.08
        self.angular_error = 0.05
​
        # Calcular ángulo de rotación
        angulo_d = math.atan2(b - self.pos[1], a - self.pos[0])
​
        while ( abs(self.yaw - angulo_d) > self.angular_error ):
​
            self.vel.linear.x = 0.0
            self.vel.angular.z = self.vel_angular_z if self.yaw < angulo_d else -self.vel_angular_z ​ self.publisher_.publish(self.vel) time.sleep(0.5) ​ while ( math.sqrt((self.pos[0] - a)**2 + (self.pos[1] - b)**2) > self.linear_error ):
            self.vel.linear.x = self.vel_linear_x
            self.vel.angular.z = 0.0
​
            self.publisher_.publish(self.vel)
            time.sleep(0.5)
​
        self.vel.linear.x = 0.0 
        self.vel.angular.z = 0.0
        self.publisher_.publish(self.vel)
​
​
    def execute_callback(self, goal_handle):
        
        self.get_logger().info('Executing goal...')
​
        # Timer
        timer_period = 0.5  # segundos
        self.timer = self.create_timer(
            timer_period, 
            self.timer_callback, 
            callback_group=self.mutuallyexclusive_group_1)
​
        self.distance = Float32()
        self.distance.data = 0.0
        self.pos_before = self.pos
​
        feedback_msg = Move.Feedback()
​
        pos_x = goal_handle.request.pos_x
        pos_y = goal_handle.request.pos_y
​
        for i,val in enumerate(pos_x):
​
            self.execute_movement(pos_x[i], pos_y[i])
​
            feedback_msg.nro_pos = i + 1
            feedback_msg.current_dist = self.distance.data
​
            self.get_logger().info('Distancia recorrida hasta ahora: {0} '.format(feedback_msg.current_dist))
            self.get_logger().info('Posición {0} alcanzada'.format(feedback_msg.nro_pos))
            goal_handle.publish_feedback(feedback_msg)
​
        goal_handle.succeed()
​
        result = Move.Result()
        result.total_dist = self.distance.data
        result.status = True
        self.get_logger().info('Distancia total recorrida: {0}'.format(result.total_dist))
​
        return result
​
def main(args=None):
    rclpy.init(args=args)
​
    my_action_server = MyActionServer()
​
    # Use MultiThreadedExecutor
    executor = MultiThreadedExecutor(num_threads=2)
    executor.add_node(my_action_server)
    
    try:
        executor.spin()
    finally:
        my_action_server.destroy_node()
        rclpy.shutdown()
​
if __name__ == '__main__':
    main()

Donde el callback del subscriber self.listener_callback actualiza la posición y rotación del TurtleBot3, mientras que el callback del timer self.timer_callback calcula la distancia recorrida durante un periodo de 0.5 segundos.

En el método self.execute_movement es donde se ejecuta el movimiento del robot, primero rotandolo para que apunte a la posición deseada y luego desplazándolo linealmente hacia esa posición.

En el callback del action server self.execute_callback se inicia el timer, se llama al método self.execute_movement por cada posición deseada, y se envía el feedback y resultado al action cliente.

 

5. Simulación

Se tiene precargada una simulación del Turtlebot3 sobre la cual trabajaremos.

Simulación en Gazebo del Turtlebot3 moviendose

Para ello primero iniciamos el action server con el siguiente comando:

ros2 launch threads_tutorial server_node_launch.launch.py

Y en otra terminal hacemos el request (especificando las posiciones que el robot debe alcanzar) con el siguiente comando:

ros2 action send_goal -f /move_as custom_interfaces/action/Move "{pos_x: [2.089,1.312], pos_y: [-0.065,-0.016]}"

Con lo cual obtenemos el movimiento deseado del TurtleBot3!

Simulación en Gazebo del Turtlebot3 moviendose


Video Tutorial

Crea una Interfaz de Servicio Personalizada – Spanish ROS Tutorial

Crea una Interfaz de Servicio Personalizada – Spanish ROS Tutorial

This tutorial is created by Robotics Ambassador 017 Jose

Rosbotics Ambassador Program (https://www.theconstruct.ai/robotics-ambassador/

Lo que vamos a aprender

  1. Crear una interfaz de servicio personalizada.
  2. Emplear la interfaz creada para comandar el Turtlebot3.

Lista de recursos usados en esta publicación

  1. Usa este rosject: https://app.theconstruct.ai/l/603ea5b0/
  2. The Construct: https://app.theconstructsim.com/
  3. Cursos ROS: ROS2 Basics in 5 Days (Python): https://app.theconstructsim.com/courses/132

Resumen

ROS (Robot Operating System) se está convirtiendo en el “framework” estándar para programar robots. En este tutorial emplearemos los conceptos de servicios para comandar el robot “Turtlebot3” haciendo uso de una interfaz de servicio personalizada que crearemos.Todo esto es simulado en un mundo de Gazebo. Ten en cuenta que se usará ROS2 Humble para este tutorial.

PASO 1: Abriendo el rosject

Para seguir este tutorial, necesitamos tener instalado ROS2 HUMBLE en nuestro sistema, y lo ideal sería tener un ros2_ws (Espacio de Trabajo ROS2). Para facilitarte la vida, ya hemos preparado un rosject para eso: https://app.theconstruct.ai/l/603ea5b0/.

Simplemente copiando el rosject (haciendo clic en el enlace de arriba), tendrás una configuración ya preparada para ti.

Después de haber copiado el rosject a tu propia área de trabajo, deberías ver el botón RUN. Haz clic en ese botón para lanzar el rosject (abajo tienes un ejemplo de rosject).

Rosject

Tras pulsar el botón RUN, deberías tener cargado el rosject. Ahora, pasemos a la siguiente sección para ponernos manos a la obra.

PASO 2: Creación del paquete

El robot a usar es el Turtlebot3.

Rosject

Primero crearemos el paquete con las dependencias necesarias. Tener en cuenta que, para crear una interfaz de servicio personalizada solo puede hacer con un paquete CMake. Para ello ejecutamos en un terminal lo siguiente:

cd ~/ros2_ws/src
ros2 pkg create --build-type ament_cmake tutorial_interface_srv --dependencies rclcpp std_msgs

PASO 3: Creación de la interfaz

Luego crearemos dentro del paquete la carpeta srv que contendrá la interfaz de servicio. Dentro de este creamos el archivo Mover.srv

cd ~/ros2_ws/src/tutorial_interface_srv
mkdir srv
cd srv
touch Mover.srv

Dentro de este archivo colocamos el contenido de la interfaz que necesitamos, el cual se compone de la parte de petición (request) y respuesta (response), que se separan mediante 3 guiones (—). Toda esta estructura permite la comunicación entre cliente y servidor.

Dentro de el archivo recién creado colocar lo siguiente: Mover.srv

float64 angulo                  # Angulo a girar
float64 velocidad_lineal        # Velocidad Lineal (en m/s)
int32 tiempo                    # Duración del movimiento (en segundos)
---
bool completado                 # Se logró?

PASO 4: Modificando CMakeLists.txt y package.xml

Una vez creado el archivo del servicio personalizado, necesitamos modificar los siguientes archivos para que nuestro paquete compile correctamente:

CMakeLists.txt: Para convertir las interfaces definidas en código específico de un lenguaje (como C++ y Python) para que puedan utilizarse en esos lenguajes.
package.xml: Añadimos las dependencias adicionales necesarias.

CMakeLists.txt

En CMakeLists.txt debemos tener lo siguiente:

find_package()

find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)
find_package(rosidl_default_generators REQUIRED)

rosidl_generate_interfaces()

Aquí se colocará la interfaz de servicio que creamos dentro de la carpeta srv.

rosidl_generate_interfaces(${PROJECT_NAME}
  "srv/Mover.srv"
)

Con lo cual CMakeList.txt deberia quedarnos mínimante con lo siguiente:

cmake_minimum_required(VERSION 3.8)
project(tutorial_interface_srv)
​
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic)
endif()
​
# find dependencies
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)
find_package(rosidl_default_generators REQUIRED)
​
if(BUILD_TESTING)
  find_package(ament_lint_auto REQUIRED)
  # the following line skips the linter which checks for copyrights
  # comment the line when a copyright and license is added to all source files
  set(ament_cmake_copyright_FOUND TRUE)
  # the following line skips cpplint (only works in a git repo)
  # comment the line when this package is in a git repo and when
  # a copyright and license is added to all source files
  set(ament_cmake_cpplint_FOUND TRUE)
  ament_lint_auto_find_test_dependencies()
endif()
​
rosidl_generate_interfaces(${PROJECT_NAME}
  "srv/Mover.srv"
)
​
ament_package()

package.xml

En package.xml debemos añadir:

<build_depend>rosidl_default_generators</build_depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<member_of_group>rosidl_interface_packages</member_of_group>

Y deberia quearnos mínimamente con lo siguiente:

<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
  <name>tutorial_interface_srv</name>
  <version>0.0.0</version>
  <description>TODO: Package description</description>
  <maintainer email="user@todo.todo">user</maintainer>
  <license>TODO: License declaration</license>
​
  <buildtool_depend>ament_cmake</buildtool_depend>
​
  <depend>rclcpp</depend>
  <depend>std_msgs</depend>
​
  <build_depend>rosidl_default_generators</build_depend>
  <exec_depend>rosidl_default_runtime</exec_depend>
  <member_of_group>rosidl_interface_packages</member_of_group>
​
  <test_depend>ament_lint_auto</test_depend>
  <test_depend>ament_lint_common</test_depend>
​
  <export>
    <build_type>ament_cmake</build_type>
  </export>
</package>

PASO 5: Compilación del paquete

Por último, compilamos el paquete para poder usar la interfaz de servicio creada.

cd ~/ros2_ws
colcon build --packages-select tutorial_interface_srv
source install/setup.bash

Es importante realizar el último paso, de lo contrario no se encontrará la interfaz de servicio que se generó. Para asegurarnos de que interfaz se generó correctamente usamos los siguiente:

ros2 interface show tutorial_interface_srv/srv/Mover

Esto nos debería mostrar el contenido de Mover.srv en la terminal:

Simulación en Gazebo del Turtlebot3 moviendose

PASO 6: Usando la interfaz

Para hacer uso de la interfaz, se creo un paquete (tutorial_servicios) que contiene un servidor y un cliente que se comunican mediante el mensaje Mover.srv que acabamos de generar.

Simulación en Gazebo del Turtlebot3 moviendose

El cliente hace una petición al servidor enviándole el ángulo que necesita girar, la velocidad lineal y el tiempo que debe moverse con esa velocidad. Una vez realizado el movimiento el servidor enviará un mensaje de confirmación al cliente como respuesta. Para ello, ejecutamos la simulación en Gazebo (la cual se puede obtener desde este link).

export TURTLEBOT3_MODEL=waffle
ros2 launch turtlebot3_gazebo empty_world.launch.py

Simulación en Gazebo del Turtlebot3 moviendose

Luego en otra terminal ejecutamos el servidor:

ros2 launch tutorial_servicios mover_server_launch_file.launch.py

Y en otra terminal ejecutamos el cliente:

ros2 launch tutorial_servicios mover_client_launch_file.launch.py

Y listo!! ya estamos haciendo uso de la interfaz de servicio recién generada.

Simulación en Gazebo del Turtlebot3 moviendose


Video Tutorial

Como usar en ROS2 un paquete de ROS1 generado por SolidWorks to URDF – Spanish ROS Tutorial

Como usar en ROS2 un paquete de ROS1 generado por SolidWorks to URDF – Spanish ROS Tutorial

This tutorial is created by Rosbotics Ambassador 025 Miguel

Rosbotics Ambassador Program https://www.theconstruct.ai/rosbotics-ambassador/)

Que vamos a aprender:

  1. Como crear un paquete en ROS 2
  2. Como modificar el URDF para hacerlo compatible con Rviz 2
  3. Como modificar el launch file
  4. Como modificar el package.xml para encontrar los modelos

Lista de recursos usados en este post

  1. Usa este rosject: https://app.theconstructsim.com/l/5fea4bb1/
  2. The construct: https://app.theconstructsim.com
  3. Cursos de ROS 2:
    1. URDF para el modelado de robots en ROS2: https://app.theconstructsim.com/courses/83
    2. Conceptos básicos de ROS2 en 5 días (Python): https://app.theconstructsim.com/courses/132

Descripción

En este tutorial, aprenderás el proceso de portar un paquete de ROS 1, creado originalmente en SolidWorks, a un formato URDF compatible con ROS 2. Exploraremos los pasos esenciales para migrar tu diseño robótico al nuevo entorno, asegurando la compatibilidad y funcionalidad en la última versión de ROS. Sigue la guía detallada para una transición sin complicaciones y mantén tus proyectos robóticos al día con las últimas tecnologías.

Abriendo el rosject

Para poder seguir al pie de la letra este tutorial, necesitamos tener ROS 2 Humble instalado en nuestro sistema, e idealmente un workspace para crear los paquetes en él. Para facilitarnos el proceso, ya hemos preparado un rosject para esto: https://app.theconstructsim.com/l/5e643001/

Solo copiando el rosject (Abriendo el enlace anterior), tendremos un entorno previamente configurado.

Después de que el rosject se haya copiado correctamente en tu entorno, debemos presionar el botón Ejecuta para ejecutar e inicializar el entorno del rosject.

[Inserta imagen del Rosject]

Crear un paquete de ROS 2 con ament_python

Para poder crear nuestro paquete de ROS 2, necesitamos situarnos en el workspace, y para eso, necesitamos la terminal.

Abramos una terminal oprimiendo el botón de abrir una nueva terminal:

[Inserte imagen de la terminal]

Una vez dentro de la terminal vamos a movernos al ros2_ws/src y creamos nuestro paquete:
cd ~/ros2_ws/src
ros2 pkg create lidar3d --build-type ament_python --dependencies rclpy

Una vez creado nuestro paquete ingresamos a él y copiamos el contenido de nuestro paquete generado por Solidworks:
cd lidar3d
cp -r ../lidar3d/* .

Ahora vamos a abrir el editor de código para modificar nuestros archivos. Para abrir el editor de código oprimimos el botón de editor de código:

 

[Inserte imagen de vscode]

Nos situamos en ros2_ws/src/lidar3d/urdf y abrimos el archivo de urdf:

[Inserte imagen del URDF]

Primero vamos a añadir un nuevo objeto vacío de base_link con un joint al base_link generado por Solidworks ya que rviz 2 no soporta base_link con inercia y le cambiamos el nombre al base_link de Solidworks:

[Inserte imagen de vscode con urdf modificado]

Ahora debido a que modificamos el nombre del paquete, vamos a usar la función de sustituir palabra integrada con Vscode:

[inserte imagen de sustitución]

El siguiente paso es ir a ros2_ws/src/lidar3d/launch para crear un launch de Python compatible con ROS 2, para eso damos click derecho y Nuevo archivo y le ponemos de nombre display.launch.py

[inserte imagen del launch file]

Por ultimo antes de compilar, debemos decirle a nuestro paquete donde se encuentran los solidos URDF los meshes y los launch files, para eso nos vamos a ros2_ws/src/lidar3d y añadimos las respectivas instrucciones install para que encuentre nuestras carpetas:

[Inserte imagen del package modificado]

Ya que tenemos nuestro paquete configurado podemos compilar y ejecutar nuestro launch file para poder generar las configuraciones por defecto de rviz2

chmod +x ~/ros2_ws/src/lidar3d/launch/*.py
cd ~/ros2_ws
colcon build --packages-select lidar3d
source install/setup.bash
ros2 launch lidar3d display.launch.py

[Inserte imagen de rviz y terminal]

Para poder activar las configuraciones requeridas de rviz 2 vamos a modificar nuesto Fixed Frame y cambiamos map por base_link, ahora le damos a añadir y para poder depurar mejor nuestro objeto añadimos el arbol de TF con la opcion TF, y por último vamos a añadir el modelo de nuestro robot añadiendo RobotModel:

[Inserte imagen de rviz]

Como podemos observar, aun no se ve nuestro robot, para eso debemos en la sub-opción de Description Topic seleccionar el topico de robot_description

[Inserte imagen de rviz con el modelo]

Video Tutorial

 

Moviendo el Turtlebot3 a posiciones deseadas en Gazebo – Spanish ROS Tutorial

Moviendo el Turtlebot3 a posiciones deseadas en Gazebo – Spanish ROS Tutorial

This tutorial is created by Rosbotics Ambassador 017 Jose

Rosbotics Ambassador Program ((https://www.theconstruct.ai/rosbotics-ambassador/)

Lo que vamos a aprender

  1. Subscribirse al tópico /odom y asi conocer la posición estimada del robot.
  2. Publicar en el tópico /cmd_vel, para así mover al robot de acuerdo con la posición estimada.

Lista de recursos usados en esta publicación

  1. Usa este rosject: https://app.theconstructsim.com/l/5fc977f5/
  2. The Construct: https://app.theconstructsim.com/
  3. Cursos ROS: ROS2 Basics in 5 Days (Python): https://app.theconstructsim.com/courses/132

Resumen

ROS (Robot Operating System) se está convirtiendo en el “framework” estándar para programar robots. En este tutorial emplearemos los conceptos de tópicos y nodos para mover el robot “Turtlebot3” hacia múltiples posiciones deseadas. Hacemos uso de los mensajes del tópico /odom para conocer la posición estimada del robot y publicar en el tópico /cmd_vel para mover el robot a las posiciones deseadas. Todo esto es simulado en un mundo de Gazebo. Ten encuesta que se usará ROS2 Humble para este tutorial.

PASO 1: Abriendo el rosject

Para seguir este tutorial, necesitamos tener instalado ROS2 HUMBLE en nuestro sistema, y lo ideal sería tener un ros2_ws (Espacio de Trabajo ROS2). Para facilitarte la vida, ya hemos preparado un rosject para eso: https://app.theconstructsim.com/l/5fc977f5/.

Simplemente copiando el rosject (haciendo clic en el enlace de arriba), tendrás una configuración ya preparada para ti.

Después de haber copiado el rosject a tu propia área de trabajo, deberías ver el botón RUN. Haz clic en ese botón para lanzar el rosject (abajo tienes un ejemplo de rosject).

Rosject

Tras pulsar el botón RUN, deberías tener cargado el rosject. Ahora, pasemos a la siguiente sección para ponernos manos a la obra.

PASO 2: Desarrollo del paquete

El robot a usar es el Turtlebot3.

Rosject

Estrategia de control

El Turtlebot3 se moverá por una serie de posiciones en un orden específico, es decir, se moverá de su posición inicial a la 1ra posición deseada, una vez alcanzada, se moverá a la 2da posición y así sucesivamente. Para lograr esto, necesitamos de una estrategia.

Tengamos en cuenta que el robot conocerá las posiciones que debe alcanzar y su posición actual estimada gracias al tópico /odom, del cual aprovecharemos la posición en X, posición Y, y la rotación en Z.

Posicion robot

Con ello, para alcanzar cada posición el Turtlebot3 realizará 2 movimientos: Primero, girar hasta que quede mirando a la posición deseada; Segundo, avanzar de manera recta hasta llegar a la posición.

Movimiento robot

Adicionalmente, debemos tener en cuenta el robot no se moverá en una línea recta de manera perfecta, por lo que se requiere compensar esa desviación conforme avanza. Esto se puede realizar añadiendo una velocidad angular cuando la orientación cambie demasiado.

Creación del paquete

Primero creamos un paquete en python para realizar un nodo-publicador-subscriptor que mueva al Turtlebot3 de la forma deseada. Necesitamos añadir las dependencias necesarias. Para ello ejecutamos en un terminal lo siguiente:

cd ~/ros2_ws/src
ros2 pkg create --build-type ament_python turtlebot3_tutorial --dependencies std_msgs geometry_msgs

Creación del nodo

Creamos el archivo que contendrá el nodo.

cd ~/ros2_ws/src/turtlebot3_tutorial/turtlebot3_tutorial
touch turtlebot3_node.py

Copiamos el siguiente código dentro del archivo.

import rclpy
from rclpy.node import Node
from geometry_msgs.msg import Twist
from nav_msgs.msg import Odometry
from rclpy.qos import ReliabilityPolicy, QoSProfile
import numpy as np
import math
​
class Robot(Node):
​
    def __init__(self):
        super().__init__('turtlebot3_node')
        # Posiciones estimadas
        self.pos_x = 0
        self.pos_y = 0
        self.roll = 0
        self.pitch = 0
        self.yaw = 0
​
        # Velocidad de desplazamiento
        self.vel_linear_x = 0.2
        self.vel_angular_z = 0.4
​
        # Máximo error
        self.linear_error = 0.08
        self.angular_error = 0.05   
​
        # Posiciones deseadas
        self.posiciones = [[1,1], [0,2], [0, 0]]
        self.index = 0
        self.pos_x_d, self.pos_y_d = self.posiciones[self.index]
        self.angulo_d = 0
​
        # Fase
        self.fase = 1 
​
        # Subscriptor
        self.subscriber = self.create_subscription(
            Odometry,
            '/odom',
            self.listener_callback,
            QoSProfile(depth=10, reliability=ReliabilityPolicy.RELIABLE))
​
        # Publicador
        self.publisher_ = self.create_publisher(Twist, 'cmd_vel', 10)
        self.vel = Twist()
        timer_period = 0.1
        self.timer = self.create_timer(timer_period, self.timer_callback)
​
    def timer_callback(self):
​
        if (self.index < len(self.posiciones)): 
            self.pos_x_d, self.pos_y_d = self.posiciones[self.index]
 
            if (self.fase == 1):
                # Calcular ángulo de rotación
                self.angulo_d = math.atan2(self.pos_y_d - self.pos_y, self.pos_x_d - self.pos_x) 
                if ( abs(self.yaw - self.angulo_d) > self.angular_error ):
                    self.vel.linear.x = 0.0
                    self.vel.angular.z = self.vel_angular_z if self.yaw < self.angulo_d else -self.vel_angular_z 
                else: 
                    self.vel.linear.x = 0.0 
                    self.vel.angular.z = 0.0 
                    self.fase += 1 
            if (self.fase == 2): 
                if ( math.sqrt((self.pos_x - self.pos_x_d)**2 + (self.pos_y - self.pos_y_d)**2) > self.linear_error ):
                    self.vel.linear.x = self.vel_linear_x
                    if ( abs(self.yaw - self.angulo_d) > self.angular_error):
                        self.vel.angular.z = self.vel_angular_z if self.yaw < self.angulo_d else -self.vel_angular_z
                    else:
                        self.vel.angular.z = 0.0
                else:
                    self.vel.linear.x = 0.0
                    self.vel.angular.z = 0.0
                    self.fase = 1
                    # Siguiente posición deseada
                    self.index += 1

        self.publisher_.publish(self.vel)
​
    def listener_callback(self, msg):
        self.pos_x = msg.pose.pose.position.x
        self.pos_y = msg.pose.pose.position.y
        quaternion = [msg.pose.pose.orientation.x,
                      msg.pose.pose.orientation.y,
                      msg.pose.pose.orientation.z,
                      msg.pose.pose.orientation.w]
        self.roll, self.pitch, self.yaw = self.euler_from_quaternion(quaternion)
​
        self.get_logger().info('Pos_x: "%f"' % self.pos_x)
        self.get_logger().info('Pos_y: "%f"' % self.pos_y)
        self.get_logger().info('Ori_z: "%f"' % self.yaw)
        self.get_logger().info('-------------------------')
​
    def euler_from_quaternion(self, quaternion):
        """
        Converts quaternion (w in last place) to euler roll, pitch, yaw
        quaternion = [x, y, z, w]
        Below should be replaced when porting for ROS2 Python tf_conversions is done.
        """
        x = quaternion[0]
        y = quaternion[1]
        z = quaternion[2]
        w = quaternion[3]
​
        sinr_cosp = 2 * (w * x + y * z)
        cosr_cosp = 1 - 2 * (x * x + y * y)
        roll = np.arctan2(sinr_cosp, cosr_cosp)
​
        sinp = 2 * (w * y - z * x)
        pitch = np.arcsin(sinp)
​
        siny_cosp = 2 * (w * z + x * y)
        cosy_cosp = 1 - 2 * (y * y + z * z)
        yaw = np.arctan2(siny_cosp, cosy_cosp)
​
        return roll, pitch, yaw
            
def main(args=None):
    # initialize the ROS communication
    rclpy.init(args=args)
    robot = Robot()
    
    rclpy.spin(robot)
​
    robot.destroy_node()
    rclpy.shutdown()
​
if __name__ == '__main__':
    main()

Repasemos las partes más importantes del código:

  • Aquí iniciamos el subscriptor del tópico /odom y el publicador al tópico /cmd_vel
        # Subscriptor
        self.subscriber = self.create_subscription(
            Odometry,
            '/odom',
            self.listener_callback,
            QoSProfile(depth=10, reliability=ReliabilityPolicy.RELIABLE))
​
        # Publicador
        self.publisher_ = self.create_publisher(Twist, 'cmd_vel', 10)
        self.vel = Twist()
        timer_period = 0.1
        self.timer = self.create_timer(timer_period, self.timer_callback)
  • En el método listener_callback recibimos las posiciones estimadas del robot y las acondicionamos para emplearlas en la lógica de control.
    def listener_callback(self, msg):
        self.pos_x = msg.pose.pose.position.x
        self.pos_y = msg.pose.pose.position.y
        quaternion = [msg.pose.pose.orientation.x,
                      msg.pose.pose.orientation.y,
                      msg.pose.pose.orientation.z,
                      msg.pose.pose.orientation.w]
        self.roll, self.pitch, self.yaw = self.euler_from_quaternion(quaternion)

        self.get_logger().info('Pos_x: "%f"' % self.pos_x)
        self.get_logger().info('Pos_y: "%f"' % self.pos_y)
        self.get_logger().info('Ori_z: "%f"' % self.yaw)
        self.get_logger().info('-------------------------')
  • Debido a que en el tópico /odom la orientación es expresada en cuaterniones, el método euler_from_quaternion es necesario para convertir la orientación del robot de cuaterniones a la notación roll-pitch-yaw. Esto nos permite obtener la rotación del robot alrededor del eje z (yaw).
    def euler_from_quaternion(self, quaternion):
        """
        Converts quaternion (w in last place) to euler roll, pitch, yaw
        quaternion = [x, y, z, w]
        Below should be replaced when porting for ROS2 Python tf_conversions is done.
        """
        x = quaternion[0]
        y = quaternion[1]
        z = quaternion[2]
        w = quaternion[3]

        sinr_cosp = 2 * (w * x + y * z)
        cosr_cosp = 1 - 2 * (x * x + y * y)
        roll = np.arctan2(sinr_cosp, cosr_cosp)

        sinp = 2 * (w * y - z * x)
        pitch = np.arcsin(sinp)

        siny_cosp = 2 * (w * z + x * y)
        cosy_cosp = 1 - 2 * (y * y + z * z)
        yaw = np.arctan2(siny_cosp, cosy_cosp)

        return roll, pitch, yaw
  • Este es el método definido en el timer y contiene la estrategia de control explicada anteriormente. Se ejecuta cada 0.1s y actualiza la velocidad del robot de acuerdo con la posición estimada.
   def timer_callback(self):

        if (self.index < len(self.posiciones)): 
            self.pos_x_d, self.pos_y_d = self.posiciones[self.index]
 
            if (self.fase == 1):
                # Calcular ángulo de rotación
                self.angulo_d = math.atan2(self.pos_y_d - self.pos_y, self.pos_x_d - self.pos_x) 
                if ( abs(self.yaw - self.angulo_d) > self.angular_error ):
                    self.vel.linear.x = 0.0
                    self.vel.angular.z = self.vel_angular_z if self.yaw < self.angulo_d else -self.vel_angular_z 
                else: 
                    self.vel.linear.x = 0.0 
                    self.vel.angular.z = 0.0 
                    self.fase += 1 
            if (self.fase == 2): 
                if ( math.sqrt((self.pos_x - self.pos_x_d)**2 + (self.pos_y - self.pos_y_d)**2) > self.linear_error ):
                    self.vel.linear.x = self.vel_linear_x
                    if ( abs(self.yaw - self.angulo_d) > self.angular_error):
                        self.vel.angular.z = self.vel_angular_z if self.yaw < self.angulo_d else -self.vel_angular_z
                    else:
                        self.vel.angular.z = 0.0
                else:
                    self.vel.linear.x = 0.0
                    self.vel.angular.z = 0.0
                    self.fase = 1
                    # Siguiente posición deseada
                    self.index += 1

        self.publisher_.publish(self.vel)

Creación del launch

Una vez que el nodo esta listo creamos el archivo launch con lo siguiente:

cd ~/ros2_ws/src/turtlebot3_tutorial
mkdir launch
cd launch
touch turtlebot3_launch.launch.py
chmod +x turtlebot3_launch.launch.py

Copiamos el siguiente código dentro del archivo que acabamos de crear:

from launch import LaunchDescription
from launch_ros.actions import Node
​
def generate_launch_description():
    return LaunchDescription([
        Node(
            package='turtlebot3_tutorial',
            executable='turtlebot3_node',
            output='screen'),
    ])

Una vez hecho lo anterior, editamos el archivo setup.py para añadir nuestro nodo.

from setuptools import setup
import os # <-- Añadir
from glob import glob # <-- Añadir
​
package_name = 'turtlebot3_tutorial'
​
setup(
    name=package_name,
    version='0.0.0',
    packages=[package_name],
    data_files=[
        ('share/ament_index/resource_index/packages',
            ['resource/' + package_name]),
        ('share/' + package_name, ['package.xml']),
        (os.path.join('share', package_name), glob('launch/*.launch.py')) # <-- Añadir
    ],
    install_requires=['setuptools'],
    zip_safe=True,
    maintainer='user',
    maintainer_email='user@todo.todo',
    description='TODO: Package description',
    license='TODO: License declaration',
    tests_require=['pytest'],
    entry_points={
        'console_scripts': [
            'turtlebot3_node = turtlebot3_tutorial.turtlebot3_node:main' # <-- Añadir de acuerdo al nombre de tu paquete y script
        ],
    },
)

Compilación del paquete

Por último, compilamos el paquete antes de ejecutar la simulación.

cd ~/ros2_ws
pip install setuptools==58.2.0
colcon build --packages-select turtlebot3_tutorial
source install/setup.bash

PASO 3: Simulando en Gazebo

Finalmente simularemos el comportamiento en gazebo, para ello primero iniciamos la simulación del Turtlebot3, cuyo paquete ya esta en este rosject. Esta se puede obtener desde este link.

export TURTLEBOT3_MODEL=waffle
ros2 launch turtlebot3_gazebo empty_world.launch.py

Simulación en Gazebo del Turtlebot3 moviendose

Una vez que se inicio la simulación, ya podemos iniciar el nodo que creamos.

ros2 launch turtlebot3_tutorial turtlebot3_launch.launch.py

Y listo! Ya podemos observar al robot moviendose por todas las posiciones deseadas en el orden especificado.

Simulación en Gazebo del Turtlebot3 moviendose

 

Video Tutorial

Cómo usar Robot State Publisher – ROS Spanish Tutorial

Cómo usar Robot State Publisher – ROS Spanish Tutorial

This tutorial is created by Rosbotics Ambassador 017 Jose

Rosbotics Ambassador Program https://www.theconstruct.ai/rosbotics-ambassador/)

Lo que vamos a aprender

  • Como iniciar Robot State Publisher
  • Como usar la herramienta rqt_tf_tree
  • Como visualizar el sistema robótico en RViz
  • Como visualizar el sistema robótico en Gazebo

 

Lista de recursos usados en esta publicación

  1. Usa este rosject: https://app.theconstructsim.com/l/5e2843be/
  2. The Construct: https://app.theconstructsim.com/
  3. Cursos ROS: TF ROS: https://app.theconstructsim.com/courses/10

 

Resumen

ROS (Robot Operating System) se está convirtiendo en el “framework” estándar para programar robots. En esta publicación, aprenderemos a usar adecuadamente Robot State Publisher para publicar las transformaciones TF de los sistemas coordenados de nuestro robot. Así como, lo necesario para visualizar el robot en RViz y Gazebo.

 

Abriendo el rosject

Para seguir este tutorial, necesitamos tener instalado ROS en nuestro sistema, y lo ideal sería tener un catkin_ws (Espacio de Trabajo ROS). Para facilitarte la vida, ya hemos preparado un rosject para eso: https://app.theconstructsim.com/l/5e2843be/.

Simplemente copiando el rosject (haciendo clic en el enlace de arriba), tendrás una configuración ya preparada para ti.

Después de haber copiado el rosject a tu propia área de trabajo, deberías ver el botón RUN. Haz clic en ese botón para lanzar el rosject (abajo tienes un ejemplo de rosject).

Tras pulsar el botón RUN, deberías tener cargado el rosject. Ahora, pasemos a la siguiente sección para ponernos manos a la obra.

 

Iniciando Robot State Publisher y visualizando el robot en RViz

Para usar Robot State Publisher necesitamos de la descripción de un robot en archivos urdf o xacro. En este rosject usaremos archivos xacro de un robot móvil simple, los cuales están en la carpeta robot de nuestro paquete robot_tutorial.


Robot a usar en el tutorial – mobile_robot

 

Creamos un archivo launch robot_visualization.launch para iniciar robot_state_publisher. Para ello, en un nuevo terminal colocamos lo siguiente:
cd /home/user/catkin_ws/src/robot_tutorial
mkdir launch
touch launch/robot_visualization.launch

En ese archivo creado colocamos:

robot_visualization.launch
<launch>
<!-- Cargar el URDF en el servidor de parámetros de ROS -->
<param name="robot_description" command="$(find xacro)/xacro $(find robot_tutorial)/robot/mobile_robot.urdf.xacro" />
<!-- Iniciamos robot_state_publisher para publicar las tranformaciones tf -->
<node name="robot_state_publisher" pkg="robot_state_publisher" type="robot_state_publisher"
output="screen">
<param name="publish_frequency" type="double" value="5.0" />
</node>
</launch>

Con esto:

 

  • Cargamos la descripción del robot en el parámetro robot_description.

 

  • Iniciamos el nodo robot_state_publisher que tomará la descripción del robot para publicar las transformaciones con una frecuencia de 5Hz.

Previamente, antes de iniciar el launch nos aseguramos de compilar nuestro paquete, para ello, en un terminal colocamos:

cd /home/user/catkin_ws
catkin_make
source devel/setup.bash
rospack profile

Con ello, ya podemos iniciar nuestro archivo launch:
roslaunch robot_tutorial robot_visualization.launch

Al iniciar el launch y visualizar el árbol TF usando rosrun rqt_tf_tree rqt_tf_tree obtenemos que no están presentes las transformaciones de las juntas no fijas, es decir, de las ruedas.

Árbol de tf publicado por robot_state_publisher – incompleto

 

Esto se debe a que robot_state_publisher necesita que se publiquen los valores de las juntas no fijas para hacer las transformaciones, de lo contrario solo publicará las trasformaciones estáticas (juntas fijas).

Por ello debemos añadir un nodo que publique los valores de las juntas de alguna manera. Usaremos para ello joint_state_publisher_gui. Añadimos lo siguiente al archivo recién creado:

 

robot_visualization.launch
<!-- Enviar valores falsos de las articulaciones -->
<node pkg="joint_state_publisher_gui" type="joint_state_publisher_gui" name="joint_state_publisher_gui" output="screen" />
<!-- Iniciamos rviz -->
<node name="rviz" pkg="rviz" type="rviz" args="-d $(find robot_tutorial)/launch/robot.rviz" />

Con esto:

 

  • Iniciamos el nodo joint_state_publisher_gui para que publique los valores de las juntas de las ruedas.
  • Iniciamos RViz con una configuración que guardaremos en launch/robot.rviz

Con ello ya vemos las transformaciones faltantes al usar rqt_tf_tree y también podemos ver el robot en RViz.

Árbol de tf publicado por robot_state_publisher – completo


Robot en RViz – completo

cd /home/user/catkin_ws/src/robot_tutorial
mkdir config
touch config/robot_control.yaml

En este archivo nuevo, colocaremos el controlador joint_state_controller, que será el encargado de publicar los valores de las juntas no fijas del robot simulado en Gazebo.

robot_control.yaml

# Publicar todos los estados de las juntas (/joint_states) -----------------------------------
joint_state_controller:
type: joint_state_controller/JointStateController
publish_rate: 50

Posteriormente, creamos otro archivo launch donde colocaremos lo necesario para la simulación.
cd /home/user/catkin_ws/src/robot_tutorial
touch launch/spawn_robot.launch

En este archivo, colocamos lo siguiente:
<launch>
<arg name="x" default="0.0" />
<arg name="y" default="0.0" />
<arg name="z" default="0.2" />
<arg name="robot_name" default="mobile_robot" />
<!-- Cargar el URDF en el servidor de parámetros de ROS -->
<param name="robot_description" command="$(find xacro)/xacro $(find robot_tutorial)/robot/mobile_robot.urdf.xacro" />

<!– Cargamos los controladores –>
<rosparam file=”$(find robot_tutorial)/config/robot_control.yaml” command=”load”/>
<!– Iniciamos robot_state_publisher para publicar las tranformaciones tf –>
<node name=”robot_state_publisher” pkg=”robot_state_publisher” type=”robot_state_publisher”
output=”screen”>
<param name=”publish_frequency” type=”double” value=”5.0″ />
</node>
<!– Iniciamos un mundo vacio en gazebo –>
<include file=”$(find gazebo_ros)/launch/empty_world.launch”/>
<!– Iniciamos los controladores –>
<node name=”controller_spawner” pkg=”controller_manager” type=”spawner” respawn=”false”
output=”screen” ns=”/” args=”joint_state_controller”/>
<!– Hacer aparecer el robot –>
<node name=”urdf_spawner” pkg=”gazebo_ros” type=”spawn_model” respawn=”false” output=”screen”
args=”-urdf -x $(arg x) -y $(arg y) -z $(arg z) -model $(arg robot_name) -param robot_description”/>
<!– Iniciamos rviz –>
<node name=”rviz” pkg=”rviz” type=”rviz” args=”-d $(find robot_tutorial)/launch/robot.rviz” />
</launch>

Lo que hace este archivo launch es:

    • Establecer una posición de aparición del robot en el mundo de Gazebo.
    • Asignamos un nombre al robot (mobile_robot).

 

  • Cargar la descripción del robot en el parámetro robot_description.

 

  • Cargamos el controlador joint_state_controller que definimos previamente. 
  • Iniciar el nodo robot_state_publisher con una frecuencia de publicación de 5Hz.
  • Iniciamos un mundo vacío en Gazebo.
  • Iniciamos el controlador usando el nodo controller_spawner.
  • Hacemos aparecer el robot en el mundo de Gazebo usando el nodo urdf_spawner pasándole los parámetros de posición, nombre y descripción del robot que previamente definimos.
  • Iniciamos RViz con la configuración que ya guardamos en launch/robot.rviz

Iniciamos este launch con:
roslaunch robot_tutorial spawn_robot.launch

Al iniciar el launch y visualizar el árbol TF usando rosrun rqt_tf_tree rqt_tf_tree observamos el mismo problema anterior, esto se debe a que, para gazebo, es necesario colocar las transmisiones a todas las juntas no fijas al final del archivo xacro de nuestro robot.

Árbol de tf publicado por robot_state_publisher – incompleto

Robot en RViz – incompleto

Para ello nos dirigimos al archivo xacro principal de nuestro robot para añadir en la parte final lo siguiente:

mobile_robot.urdf.xacro

<gazebo>
<plugin name="gazebo_ros_control" filename="libgazebo_ros_control.so">
</plugin>
</gazebo>
<transmission name="tran1">
<type>transmission_interface/SimpleTransmission</type>
<joint name="left_wheel_joint">
<hardwareInterface>hardware_interface/EffortJointInterface</hardwareInterface>
</joint>
<actuator name="motor1">
<hardwareInterface>hardware_interface/EffortJointInterface</hardwareInterface>
<mechanicalReduction>1</mechanicalReduction>
</actuator>
</transmission>
<transmission name="tran2">
<type>transmission_interface/SimpleTransmission</type>
<joint name="right_wheel_joint">
<hardwareInterface>hardware_interface/EffortJointInterface</hardwareInterface>
</joint>
<actuator name="motor2">
<hardwareInterface>hardware_interface/EffortJointInterface</hardwareInterface>
<mechanicalReduction>1</mechanicalReduction>
</actuator>
</transmission>

Con esto:

  • Añadimos el plugin gazebo_ros_control.
  • Añadimos la transmisión a la junta left_wheel_joint (debe ser el mismo nombre de la junta usada en la definición del joint).
  • Añadimos la transmisión a la junta right_wheel_joint (debe ser el mismo nombre de la junta usada en la definición del joint).

Con ello ya se visualizan las transformaciones de todas las juntas del robot y ya tenemos el robot tanto en RVIZ como en Gazebo listo para añadir los controladores y sensores que se requieran.

 

Video Tutorial

Como crear publicador y suscriptor para turtlebot 3 – Spanish ROS Tutorial

Como crear publicador y suscriptor para turtlebot 3 – Spanish ROS Tutorial

This tutorial is created by Rosbotics Ambassador 025 Miguel

Rosbotics Ambassador Program https://www.theconstruct.ai/rosbotics-ambassador/)

Que vamos a aprender:

  1. Como crear un paquete en ROS 1
  2. Como escribir un publicador para el robot Turtlebot 3
  3. Como escribir un suscriptor en el mismo código

Lista de recursos usados en este post

  1. Usa este rosject: https://app.theconstructsim.com/l/5e643001/
  2. The construct: https://app.theconstructsim.com
  3. Cursos de ROS 1:
  1. Fundamentos de ROS en 5 días (Python): https://app.theconstructsim.com/courses/55
  2. Navegación ROS en 5 días: https://app.theconstructsim.com/courses/57

Descripción

Turtlebot 3 es uno de los robots compatibles con ros mas usados hoy en día en universidades y por personas del común para aprender el funcionamiento de ROS y el stack de Navegación. En este post, vamos a aprender como crear un nodo de ROS1, el cual nos va a permitir crear un teleop casero para entender como crear un suscriptor y publicador en el mismo código.

Abriendo el rosject

Para poder seguir al pie de la letra este tutorial, necesitamos tener ROS 1 Noetic instalado en nuestro sistema, e idealmente un workspace para crear los paquetes en él. Para facilitarnos el proceso, ya hemos preparado un rosject para esto: https://app.theconstructsim.com/l/5e643001/

Solo copiando el rosject (Abriendo el enlace anterior), tendremos un entorno previamente configurado.

Después de que el rosject se haya copiado correctamente en tu entorno, debemos presionar el botón Ejecuta para ejecutar e inicializar el entorno del rosject.

Crear un paquete de ROS 1 con geometry_msgs

Para poder crear nuestro paquete de ROS 1, necesitamos situarnos en el workspace, y para eso, necesitamos la terminal.

Abramos una terminal oprimiendo el botón de abrir una nueva terminal:

Una vez dentro de la terminal vamos a movernos al catkin/src y creamos nuestro paquete:
cd ~/catkin_ws/src
catkin_create_pkg turtlebot_publisher_subscriber geometry_msgs

Una vez creado nuestro paquete ingresamos a él y por convención creamos la carpeta scripts:
cd turtlebot_publisher_suscriber
mkdir scripts

Ahora vamos a abrir el editor de código para crear nuestro nodo. Para abrir el editor de código oprimimos el botón de editor de código:

 

Nos situamos en catkin_es/src/turtlebot_publisher_susbriber/scripts y oprimimos clic derecho New File y le damos el nombre de turtlebot.py para crear nuestro código


Primero vamos a importar las librerías, inicializar nuestro nodo, y crear el listener con su respectivo callback:

Para entenderlo mejor vamos a desglosar el código:

El primer rectángulo nos permite observar que estamos usando el entorno de Python 3, e importamos la librería de rospy para crear los nodos y geometry_msgs.msg Twist que es el tipo de dato Twist ya que este es el que usa /cmd_vel para comunicarse.

En el segundo rectángulo estamos creando un mensaje de tipo Twist, creamos la función listener la cual se suscribe de manera anónima a al tópico cmd_vel que usa como tipo de dato Twist, y por último cuando recibe un mensaje hace un llamado al callback que usando un loginfo imprime en pantalla el contenido del mensaje, este mensaje es la velocidad angular y lineal del turtlebot 3.

Por último en el tercer rectángulo inicializamos el nodo llamado publisher_suscriber e informamos por terminal que el nodo se inicializó correctamente.

Ahora vamos a crear el nodo publicador:

Como podemos observar creamos la función talker que recibe un mensaje como parámetro, ahora nos suscribimos al tópico /cmd_vel y publicamos el mensaje

Ahora vamos a crear una función que use el teclado para mover al turtlebot:

 

Lo que hace la función keyInput, esta nos pide ingresar una tecla, si se oprime w cambiamos el valor lineal de x para mover el robot hacia adelante, si se oprime s cambia el valor lineal a negativo para moverse hacia atrás, si se oprime a cambia el valor angular de z para girar hacia la izquierda y d gira hacia la derecha. 

Por último, vamos a hacer llamados continuos a estas funciones añadiendo un ciclo while al código inicial:

 

Acá lo que hacemos es crear un ciclo while para que esté constantemente solicitando una tecla, imprimiendo la velocidad del turtlebot 3, y cambiando la velocidad del turtlebot.

Para probar el código vamos a movernos a nuestro workspace, y compilamos el entorno:
cd ~/catkin_ws
catkin_make
source ~/catkin_ws/devel/setup.bash

Y si no tuvimos ningún error podemos correr el código y revisar si funciona

 

Video Tutorial

 

 

 

 

Pin It on Pinterest