Parte 5: Actions
Introducción¶
Ejercicios: 6 (5 básicos, 1 avanzado)
Tiempo Estimado de Finalización: 3 horas (solo ejercicios básicos)
Nivel de Dificultad: Avanzado
Objetivos¶
En esta parte del curso aprenderemos sobre un tercer método de comunicación disponible en ROS: los Actions. Los Actions son esencialmente una versión avanzada de los Services, y veremos exactamente en qué difieren estos dos y por qué podrías elegir emplear un action en lugar de un service para ciertas tareas.
Resultados de Aprendizaje Esperados¶
Al finalizar esta sesión podrás:
- Reconocer cómo los ROS Actions difieren de los ROS Services y explicar dónde este método podría ser útil en aplicaciones robóticas.
- Explicar la estructura de las interfaces de Action e identificar la información relevante dentro de ellas, permitiéndote construir Action Servers y Clients.
- Implementar nodes Python Action Client que puedan manejar feedback y results y también puedan cancelar un action a mitad de camino.
- Desarrollar nodes Action Server y Client que podrían usarse como base para una estrategia de exploración robótica.
Enlaces Rápidos¶
Ejercicios¶
- Ejercicio 1: Lanzar un Action Server y llamarlo desde la línea de comandos
- Ejercicio 2: Construir un Node Python Action Client
- Ejercicio 3: Crear una Interface de Action
- Ejercicio 4: Construir el Action Server "ExploreForward"
- Ejercicio 5: Construir un Client Básico "ExploreForward"
- Ejercicio 6 (Avanzado): Implementar una Estrategia de Exploración
Recursos Adicionales¶
Primeros Pasos¶
Paso 1: Lanza tu Entorno de ROS
Lanza tu entorno de ROS ahora para que tengas acceso a una instancia de terminal Linux (también conocida como TERMINAL 1).
Paso 2: Restaura tu trabajo (Solo para Usuarios de Computadoras del Laboratorio con WSL-ROS2)
Recuerda que cualquier trabajo que realices dentro del Entorno WSL-ROS2 no se conservará entre sesiones ni en diferentes computadoras del laboratorio, por lo que debes hacer una copia de seguridad de tu trabajo en tu unidad U:\ regularmente. Cuando se te solicite (al primer lanzamiento de WSL-ROS2 en TERMINAL 1) ingresa Y para restaurarlo1.
Paso 3: Lanza VS Code
Usuarios de WSL recuerda verificar esto.
Paso 4: Asegúrate de que el Repositorio del Curso esté Actualizado
Verifica que el Repositorio del Curso esté actualizado antes de comenzar con estos ejercicios. Consulta aquí cómo instalar y/o actualizar.
Llamar a un Action Server¶
Antes de hablar sobre qué son realmente los actions, vamos a ir directo al grano y ver uno en action (perdona el juego de palabras).
Ejercicio 1: Lanzar un Action Server y llamarlo desde la línea de comandos¶
Vamos a jugar un pequeño juego aquí. Vamos a lanzar el robot de simulación en un entorno misterioso ahora, y lo haremos lanzando Gazebo en modo headless, es decir, Gazebo se ejecutará en segundo plano, pero no habrá Interfaz Gráfica de Usuario (GUI) para mostrarnos cómo es realmente el entorno. Luego, usaremos un action server para hacer que nuestro robot escanee el entorno y tome fotos por nosotros, ¡para revelar su entorno!
-
Para lanzar el robot de simulación en este entorno misterioso, usa el siguiente comando
ros2 launchen TERMINAL 1:... ¡aparentemente no ocurrirá nada (por ahora)!
-
A continuación, abre una nueva terminal (TERMINAL 2) y echa un vistazo a todos los topics que están actualmente activos en la red de ROS (¡ya deberías saber exactamente cómo hacer esto!).
La salida de esto debería confirmar que ROS y nuestro robot están efectivamente activos...
¿Cómo?
Cuando el robot está activo, la salida del comando
ros2 topic listdebería proporcionar una larga lista de topics, varios de los cuales hemos estado trabajando a lo largo de este curso, comocmd_vel,odom,scan, y así sucesivamente. Si la simulación del robot no está activa, se nos presentaría una lista mucho más pequeña, que contiene solo los topics básicos de ROS:
TERMINAL 2:
-
A continuación (aún en TERMINAL 2), ejecuta el siguiente comando para lanzar un Action Server en la red:
-
Ahora, abre otra nueva instancia de terminal (TERMINAL 3), de manera que puedas ver esta y la TERMINAL 2 lado a lado. Ingresa el siguiente comando para listar todos los actions que están activos en la red de ROS:
Debería haber un elemento aquí llamado
/camera_sweep. Usa el comandoinfopara obtener más información sobre este:Esto nos indica el nombre del action:
Action: /camera_sweep, así como el número de nodes client y server que tiene este action. Actualmente, el action debería tener 0 clients y 1 server, y el node que actúa como servidor aquí debería aparecer como/camera_sweep_action_server_node(el node que acabamos de lanzar con el comandoros2 runen TERMINAL 2).Finalmente, llama al comando
ros2 action infonuevamente, pero esta vez proporcionando un argumento adicional:Action: /camera_sweep Action clients: 0 Action servers: 1 /camera_sweep_action_server [rigsa_interfaces/action/CameraSweep]El argumento
-tmuestra adicionalmente el tipo de action frente al node servidor, indicándonos el tipo de interface utilizada por el servidor. -
Ahora busquemos más información sobre la interface en sí. Al igual que con cualquier interface (mensaje, service o action) podemos usar el comando
ros2 interfacepara esto. En TERMINAL 3 ingresa lo siguiente:Lo cual debería presentarnos lo siguiente:
Hay tres partes en una interface de action, y hablaremos sobre estas con más detalle en breve, pero por ahora, todo lo que necesitamos saber es que para llamar a un action, necesitamos enviar al action server un Goal.
Comparación con ROS Services
Esto es un poco como enviar una Request a un ROS Service Server, como hicimos en la sesión anterior.
-
Podemos emitir un goal a un action server desde la línea de comandos usando el comando
ros2 actionnuevamente. Vamos a intentarlo en TERMINAL 3.Primero, identifiquemos el sub-comando correcto de
ros2 action:Commands: info Print information about an action list Output a list of action names send_goal Send an action goalComo se muestra arriba, hay tres sub-comandos para elegir, ¡y ya hemos usado los dos primeros! Claramente, el comando
send_goales el que necesitamos ahora.Obtengamos ayuda sobre este:
A partir de esto, aprendemos que hay tres argumentos posicionales, que deben suministrarse en el orden correcto:
Sabemos por nuestra investigación anterior con los comandos
ros2 action list,infoyros2 interface showcómo proporcionar los datos correctos aquí:action_name:/camera_sweepaction_type:rigsa_interfaces/action/CameraSweepgoal: un paquete de datos (en formato YAML) que contiene dos parámetros:sweep_angle: el ángulo (en grados) que el robot rotará en su lugar (es decir, el 'barrido')image_count: el número de imágenes que capturará desde su cámara frontal mientras realiza el 'barrido'
-
Ahora, de nuevo en TERMINAL 3, intenta usar el comando
ros2 action send_goal, pero vigila la TERMINAL 2 mientras lo haces:ros2 action send_goal /camera_sweep rigsa_interfaces/action/CameraSweep \ "{sweep_angle: 0, image_count: 0}"Después de llamar al action, se te debería presentar un mensaje (en TERMINAL 3) indicando que el
Goal was rejected.En TERMINAL 2 (donde se está ejecutando el action server), deberíamos ver información adicional sobre por qué fue así. Lee esto y luego regresa a TERMINAL 3 e intenta de nuevo enviar un goal al action server, ¡esta vez suministrando entradas válidas!Una vez que se hayan suministrado parámetros de goal válidos, el action server (en TERMINAL 2) responderá para informarte de lo que va a hacer. Luego deberás esperar a que complete su tarea...
-
Una vez que el action haya completado (podría tardar hasta 20 segundos), debería aparecer un mensaje en TERMINAL 3 para informarnos del resultado:
Result: image_paths: - ~/ros_action_examples/img01.jpg - ~/ros_action_examples/img02.jpg - ~/ros_action_examples/img03.jpg - ... Goal finished with status: SUCCEEDEDAdicionalmente, también deberíamos ver algo más de texto en TERMINAL 2:
[INFO] [#####] [camera_sweep_action_server_node]: camera_sweep_action_server_node completed successfully: - Angular sweep = # degrees - Images captured = # - Time taken = # seconds-
El result del action (presentado en TERMINAL 3) es una serie de rutas de archivos, que ilustran las imágenes que han sido capturadas y dónde se han guardado en el sistema de archivos. Navega a este directorio en TERMINAL 3 (usando
cd) y echa un vistazo al contenido usandoll(un alias útil para el comandols):Deberías ver el mismo número de archivos de imagen que el solicitado con el parámetro
image_count. -
Lanza
eogen este directorio y navega por todas las imágenes para revelar el entorno misterioso de tu robot:
-
-
Hagamos esto una vez más. Cierra la ventana de
eog, regresa a TERMINAL 3 y emite el comandoros2 action send_goalnuevamente, pero esta vez usa el flag opcional-f:ros2 action send_goal -f /camera_sweep rigsa_interfaces/action/CameraSweep \ "{sweep_angle: 0, image_count: 0}"Tip
¡No olvides suministrar nuevamente parámetros de goal válidos!
Ahora, además de recibir un result una vez que el action haya completado, ¡también recibimos algunas actualizaciones regulares mientras el action está en progreso (también conocido como "feedback")!
-
Para terminar, cierra el action server en TERMINAL 2 y el proceso de Gazebo headless en TERMINAL 1 ingresando Ctrl+C en cada terminal.
¿Qué es un ROS Action?¶
En el ejercicio anterior lanzamos un action server y luego lo llamamos desde la línea de comandos usando el sub-comando ros2 action send_goal. De la misma manera que un ROS Service, el action también nos proporcionó un result una vez que la tarea se completó.
Usando el flag -f pudimos pedir al servidor que nos proporcionara feedback en tiempo real sobre cómo iba (en TERMINAL 3). El Feedback es una de las características clave que diferencia a un ROS Action de un ROS Service: un Action Server proporciona feedback a intervalos regulares mientras trabaja hacia su goal. Otra característica de los ROS Actions es que pueden cancelarse a mitad de camino (con lo que jugaremos en breve).
En última instancia, los Actions usan una combinación de comunicación basada tanto en Topics como en Services, para crear un protocolo de mensajería más avanzado. Basándose en los ROS Services, los Actions están diseñados para ser usados en tareas de larga duración, debido a la provisión de feedback y la capacidad de cancelar un proceso a mitad de camino. Puedes leer más sobre Actions en la documentación oficial de ROS 2 aquí (que también incluye una animación muy buena para explicar cómo funcionan).
El Formato de las Interfaces de Action¶
Al igual que los Services, las Interfaces de Action tienen múltiples partes, y necesitamos saber qué formato tienen estos mensajes de action para poder usarlos.
Ejecutamos ros2 interface show en el ejercicio anterior, para interrogar la interface de action utilizada por el action server /camera_sweep:
Como sabemos del Ejercicio 1, para llamar a este action server necesitamos enviar un goal, y (en este caso) hay dos parámetros de goal que deben proporcionarse:
sweep_angle: un valor de punto flotante de 32 bitsimage_count: un entero de 32 bits
Preguntas
- ¿Cuáles son los nombres de los parámetros de interface de result y feedback? (Hay tres en total.)
- ¿Qué tipos de datos usan estos parámetros?
Aprenderás cómo usamos esta información para desarrollar nodes Python Action Server y Client en los siguientes ejercicios.
Creación de Python Action Clients¶
En el ejercicio anterior llamamos a un Action Server preexistente desde la línea de comandos, enviándole un goal. Veamos ahora cómo podemos hacer esto desde dentro de un node Python de ROS.
Ejercicio 2: Construir un Node Python Action Client¶
-
En TERMINAL 1 lanza la simulación del mundo misterioso nuevamente, pero esta vez con un argumento adicional:
Con el switch
with_guiconfigurado entrue, ahora deberíamos poder ver realmente el mundo simulado esta vez.(¡No es muy emocionante que digamos!) -
Luego, en TERMINAL 2, lanza el Camera Sweep Action Server nuevamente:
Parte 1: Un Action Client Mínimo¶
-
Ahora, en TERMINAL 3, crea un nuevo paquete llamado
part5_actionsusando el enfoque que hemos estado usando para crear paquetes a lo largo de este curso. -
Navega a la carpeta
scriptsde tu paquete usando el comandocd: -
Aquí, crea un nuevo archivo Python llamado
camera_sweep_action_client.py(usando el comandotouch) y hazlo ejecutable (usandochmod). -
Luego, declara esto como un ejecutable en el
CMakeLists.txtdel paquete: -
Como mínimo absoluto, el Action Client puede construirse de la siguiente manera:
El Node camera_sweep_action_client.py -
Construyamos el Node ahora, para poder ejecutarlo. Regresa a TERMINAL 3 y usa Colcon para construir el paquete:
-
Vuelve a cargar el
.bashrc: -
Ejecuta tu node con
ros2 run:Con el comando anterior tal como está, el servidor rechazará el goal (vuelve a TERMINAL 2 para averiguar por qué). ¿Cómo puedes modificar el comando anterior (agregando algunos argumentos adicionales), para que el node
camera_sweep_action_client.pyenvíe realmente un goal válido?2 -
Habiendo modificado el comando
ros2 runpara enviar exitosamente un goal válido al action servercamera_sweep, el robot debería comenzar a girar, capturando imágenes a medida que avanza.El Client que hemos creado aquí hace una llamada al action server y luego termina, ¡ni siquiera espera a que el servidor complete su tarea! La única manera de saber si el action fue exitoso sería vigilar TERMINAL 2, para ver al server del action responder al goal que se le envió... El client en sí no proporciona feedback durante el action, ni el result al final. Veamos cómo incorporar eso ahora...
Parte 2: Manejo de un Result¶
-
Regresa al archivo
camera_sweep_action_client.pyen VS Code. -
Para poder manejar el result que se envía desde un action server, primero necesitamos manejar la respuesta que el servidor envía al goal en sí.
Dentro del método
send_goal()de la claseCameraSweepActionClient(), encuentra la línea que dice:y cámbiala a:
self.send_goal_future = self.actionclient.send_goal_async(goal) self.send_goal_future.add_done_callback(self.goal_response_callback)Este método ya no retorna el future que se envía desde
send_goal_async(), sino que ahora lo maneja y le agrega un callback:goal_response_callback. Este callback ahora puede usarse para informar al client de si el servidor ha aceptado el goal o no. -
Por lo tanto, necesitamos definir este callback ahora. Defínelo como un nuevo método de clase de la clase
CameraSweepActionClient()(es decir, debajo del método de clasesend_goal()que ya ha sido definido)...def goal_response_callback(self, future): goal_handle = future.result() if not goal_handle.accepted: self.get_logger().warn("The goal was rejected by the server.") return self.get_logger().info("The goal was accepted by the server.") self.get_result_future = goal_handle.get_result_async() self.get_result_future.add_done_callback(self.get_result_callback)La entrada a este método será el future que crea la llamada
send_goal_async(). Lo asignamos a un atributo llamadogoal_handleaquí, y podemos usarlo para dos propósitos:- Verificar si el goal que enviamos fue aceptado por el servidor.
- Si fue aceptado, entonces podemos obtener el result (usando
get_result_async()) y podemos adjuntar otro callback para procesar realmente ese result:get_result_callback.
-
Ahora también necesitamos definir este callback. Define
get_result_callbackcomo otro nuevo método de la claseCameraSweepActionClient()(es decir, debajo del método de clasegoal_response_callback()que acabamos de definir)...def get_result_callback(self, future): result = future.result().result self.get_logger().info( f"The action has completed.\n" f"Result (Image Paths):\n " + "\n ".join(result.image_paths) ) rclpy.shutdown()La entrada a este método de clase es otro objeto future que contiene el result real enviado desde el servidor. Lo asignamos a
resulty usamos una llamadaget_logger().info()para imprimirlo en el terminal cuando el action haya finalizado.Como sabemos de nuestro trabajo anterior, la interface de Action
CameraSweepcontiene un parámetro de Result llamadoimage_paths. -
Finalmente, en el método
a solo esto: (porquemain, cambia esto:send_goal()ya no retorna unfuture) -
Y luego también cambia:
a esto:
-
¡Guarda todos tus cambios!
-
Ejecuta este node nuevamente ahora (con el comando
ros2 run) y observa los cambios en acción.Nuestro node client ahora nos presenta el result que envía el servidor al completarse el action, pero ¿no sería bueno poder ver el feedback en tiempo real mientras el action tiene lugar? Agreguemos esto ahora...
Parte 3: Manejo de Feedback¶
-
Regresa al archivo
camera_sweep_action_client.pyen VS Code. -
Para poder manejar el feedback que se envía desde un action server, ¡necesitamos agregar otro callback! Regresa al método
send_goal()y a la línea donde realmente estamos enviando el goal al servidor:Tal como está, todo lo que hacemos aquí es enviar el goal, pero también podemos agregar un feedback callback a esto:
self.send_goal_future = self.actionclient.send_goal_async( goal=goal, feedback_callback=self.feedback_callback )El
feedback_callbackse ejecutará cada vez que se reciba un nuevo mensaje de feedback del servidor. -
Para definir qué queremos hacer con estos mensajes de feedback, necesitamos agregar otro nuevo método a la clase
CameraSweepActionClient(). Debajo del método de claseget_result_callback()que definimos antes, agrega también este nuevo:def feedback_callback(self, feedback_msg): feedback = feedback_msg.feedback fdbk_current_angle = feedback.current_angle fdbk_current_image = feedback.current_image self.get_logger().info( f"\nFEEDBACK:\n" f" - Current angular position = {fdbk_current_angle:.1f} degrees.\n" f" - Image(s) captured so far = {fdbk_current_image}." )Como sabemos de nuestro trabajo anterior, la interface
CameraSweepcontiene dos parámetros de feedback:current_angleycurrent_image. -
Guarda todos tus cambios nuevamente, ejecuta el node nuevamente con el comando
ros2 runy observa los cambios en acción.El node que hemos construido ahora puede enviar un goal a un action server, procesar el feedback devuelto por el servidor mientras el action está en progreso, y presentarnos el result una vez que todo esté completo.
Como se discutió anteriormente, la otra característica clave de los Actions es la capacidad de cancelarlos a mitad de camino. Veamos también cómo incorporar esto ahora.
Parte 4: Cancelar un Action¶
-
Primero, crea una copia de tu node
camera_sweep_action_client.pyy llámalacancel_camera_sweep.py. Asegúrate de que TERMINAL 3 esté ubicada en el directorioscriptsde tu paquetepart5_actionsantes de ejecutar lo siguiente: -
No olvides declarar esto como un ejecutable adicional en el
CMakeLists.txtdel paquete. También necesitarás reconstruir el paquete concolcon build(regresa para un recordatorio). -
Abre el archivo
cancel_camera_sweep.pyen VS Code. -
Queremos que este client pueda cancelar el goal en dos circunstancias diferentes:
- El client en sí es apagado por el usuario (mediante un Ctrl+C en el terminal).
- Un evento condicional que ocurre mientras el action está en curso.
Para abordar el punto 1 primero, necesitamos recurrir a parte del trabajo que hicimos en la Parte 2 en la implementación de procedimientos de apagado seguro...
-
Como esperamos que recuerdes, primero necesitamos importar
SignalHandlerOptionsen nuestro node, así que agrega esto como una importación adicional al comienzo del código:Luego, en la función del node
main(), modifica la llamadarclpy.init(): -
Dentro del método
__init__()de nuestra claseCameraSweepActionClient()ahora necesitamos agregar algunos flags adicionales:En el método de clase
get_result_callback(), podemos entonces asegurarnos de que el flagself.goal_succeededse establezca enTruecuando se recibe un result. En este método de clase, localiza la línearclpy.shutdown()y agrega la siguiente línea adicional justo encima de ella: -
Los Actions pueden cancelarse usando un método
cancel_goal_async()delgoal_handleque se obtiene delgoal_response_callback(). Por ello, necesitamos hacerlo accesible en toda nuestra claseCameraSweepActionClient(). Localiza el método de clasegoal_response_callback()y agrega esta línea al final como la última línea del métodogoal_response_callback():Esto hace que
goal_handlesea accesible en toda la claseCameraSweepActionClient()comoself.goal_handle. -
Solo podemos intentar cancelar un Action cuando está en progreso, por lo tanto, el feedback callback es el mejor lugar para activar esto. Localiza el método de clase
feedback_callback()y coloca lo siguiente al final de él:if self.stop: future = self.goal_handle.cancel_goal_async() future.add_done_callback(self.cancel_goal)Aquí, llamamos al método
cancel_goal_async()desdeself.goal_handley agregamos otro nuevo callback (cancel_goal()) a él (es decir, para encapsular lo que queremos que suceda cuando el action sea cancelado). -
Ahora, definamos esto como otro nuevo (¡y final!) método de clase:
def cancel_goal(self, future): cancel_response = future.result() if len(cancel_response.goals_canceling) > 0: self.get_logger().info('Goal successfully canceled') self.goal_cancelled = True else: self.get_logger().info('Goal failed to cancel')La entrada a este callback es otro future, que podemos usar para determinar si el goal ha sido cancelado (como se muestra arriba). Si lo ha sido, entonces establecemos nuestro flag
self.goal_cancelledenTrue. -
Finalmente, regresa a la función
main()del node. Vamos a reemplazar la línearclpy.spin(action_client)ahora, con unrclpy.spin_once(), envuelto dentro de untry-except, ¡envuelto dentro de un loopwhile!while not action_client.goal_succeeded: try: rclpy.spin_once(action_client) if action_client.goal_cancelled: break except KeyboardInterrupt: print("Ctrl+C") action_client.stop = TrueEl loop
whilese ejecutará hasta que el action complete exitosamente, o hasta que el goal sea cancelado, o apaguemos el node con una interrupción Ctrl+C.Revisa el node para ver cómo todo esto fluirá a través de tu clase.
-
Es posible que deseemos cancelar un goal condicionalmente si, por ejemplo, ha transcurrido demasiado tiempo desde que se realizó la llamada, o si el caller ha sido informado de algo más que ha ocurrido mientras tanto (¡quizás nos estamos quedando sin espacio de almacenamiento en el robot y no podemos guardar más imágenes!).
Para los propósitos de este ejercicio, queremos modificar nuestro node para que el action siempre sea cancelado después de que se hayan capturado un total de 5 imágenes. Esto puede hacerse realizando una modificación bastante pequeña al
feedback_callback(). Intenta implementar esto ahora.- Intenta ahora introducir una llamada condicional al método
cancel_goal()una vez que se hayan capturado un total de 5 imágenes. - Podrías usar el atributo
current_imagedel mensajeCameraSweepFeedbackpara activar esto.
- Intenta ahora introducir una llamada condicional al método
Un Resumen de los ROS Actions¶
Los ROS Actions funcionan de manera muy similar a los ROS Services, pero tienen las siguientes diferencias clave:
- Pueden cancelarse: Si algo tarda demasiado, o si ha ocurrido algo más, entonces un Action Client puede cancelar un Action cuando lo necesite.
- Proporcionan feedback: para que un client pueda monitorear lo que está sucediendo y actuar en consecuencia (es decir, cancelar el action, si es necesario).
Por lo tanto, este mecanismo es útil para operaciones que pueden tardar mucho en ejecutarse, y donde podría ser necesaria una intervención.
Creación de tus Propios Action Servers, Clients e Interfaces¶
Importante
Cancela todos los procesos activos que puedas tener en ejecución antes de continuar.
Hasta ahora hemos visto cómo llamar a un action server preexistente, pero ¿qué pasa si realmente queremos configurar el nuestro propio, y también usar nuestras propias Interfaces de Action personalizadas?
Para empezar, echa un vistazo al Action Server con el que has estado trabajando en los ejercicios anteriores. Has estado lanzándolo con el siguiente comando:
Preguntas
- ¿A qué paquete pertenece el node del action server?
- ¿Dónde (en el directorio de ese paquete) es probable que esté ubicado este node?
Una vez que hayas identificado el nombre y la ubicación del código fuente, ábrelo en VS Code y revísalo para ver cómo funciona todo. No te preocupes demasiado por todo el contenido relacionado con la obtención y manipulación de imágenes de cámara, aprenderemos más sobre esto en la siguiente parte de este curso. En cambio, enfócate en la estructura general del código y en la manera en que se implementa el action server.
Algunas cosas a revisar:
-
La manera en que el servidor se inicializa y los numerosos callbacks que se le adjuntan:
self.actionserver = ActionServer( node=self, action_type=CameraSweep, action_name="camera_sweep", execute_callback=self.server_execution_callback, # (1)! callback_group=ReentrantCallbackGroup(), # (2)! goal_callback=self.goal_callback, # (3)! cancel_callback=self.cancel_callback # (4)! )- Este callback contiene todo el código que ejecutará el servidor una vez que se le envíe un goal válido (es decir, la funcionalidad principal del Action).
- Este node servidor se configura como un Executor Multi-hilo (consulta la configuración en
main()), para controlar la ejecución de los diversos callbacks que necesitamos. Aquí, asignamos el Action Server a un Reentrant Callback Group, permitiendo que todos sus callbacks se ejecuten en paralelo, así como otros callbacks de subscriber también. - Este callback se usa para verificar los parámetros del goal que se han enviado al servidor, para decidir si aceptar o rechazar una solicitud.
- Este callback contiene todo lo que necesita suceder en caso de que el Action sea cancelado a mitad de camino.
-
Echa un vistazo a los diversos callbacks de Action para ver qué sucede en cada uno:
- ¿Cómo se verifican los parámetros de goal y, consecuentemente, se aceptan o rechazan?
- ¿Cómo se implementan las cancelaciones y cómo se monitorea esto en el
server_execution_callback()principal? - ¿Cómo se maneja y publica el feedback?
- ¿Cómo se maneja y publica también el result?
-
Finalmente, considera las operaciones de apagado.
Implementación de una Estrategia de "Exploración" usando el Marco de Actions¶
Una estrategia de exploración permite a un robot navegar autónomamente por un entorno desconocido mientras simultáneamente evita colisiones. Una manera de lograr esto es utilizar dos estados de movimiento distintos: moverse hacia adelante y girar en su lugar, alternando repetidamente entre ellos. El Movimiento Browniano y el Levy Flight son ejemplos de este tipo de enfoque. Aleatorizar el tiempo pasado en uno o ambos de estos dos estados resultará en una estrategia de navegación que permite al robot explorar lenta y aleatoriamente un entorno. El movimiento hacia adelante podría realizarse hasta que, por ejemplo, se haya recorrido cierta distancia, haya transcurrido un tiempo determinado o algo se interponga en el camino. De manera similar, la dirección, velocidad y/o duración del giro también podría aleatorizarse para lograr esto.
En los próximos ejercicios construiremos nuestra propia Interface de Action, y nodes Server y Client con el objetivo de crear la base para un comportamiento de exploración básico. Habiendo completado estos ejercicios tendrás algo que, con mayor desarrollo, podría convertirse en un tipo de comportamiento de exploración como el descrito arriba.
Ejercicio 3: Crear una Interface de Action¶
En el Ejercicio 2 creamos el paquete part5_actions. Dentro de este paquete ahora definiremos una Interface de Action para ser usada por el Action Server y Client subsiguientes que crearemos en los ejercicios posteriores.
-
Las interfaces de action deben definirse dentro de una carpeta
actionen la raíz del directorio del paquete, así que vamos a crearla ahora en TERMINAL 1:-
Primero, navega a tu paquete:
-
Luego usa
mkdirpara crear un nuevo directorio:
-
-
Aquí ahora definiremos una Interface de Action llamada
ExploreForward: -
Ábrela en VS Code y define la estructura de datos de la Interface de la siguiente manera:
ExploreForward.action#goal float32 fwd_velocity # The speed at which the robot should move forwards (m/s) float32 stopping_distance # Minimum distance of an approaching obstacle before the robot stops (meters) --- #result float32 total_distance_travelled # Total distance travelled during the action (meters) float32 closest_obstacle # LaserScan distance to the closest detected obstacle up ahead (meters) --- #feedback float32 current_distance_travelled # Distance travelled so far during the action (meters)Esta interface tiene por lo tanto dos parámetros de goal, uno de feedback y dos de result:
Goal:
fwd_velocity: La velocidad (en m/s) a la que el robot debe moverse hacia adelante cuando se llama al action server.stopping_distance: La distancia (en metros) a la que el robot debe detenerse frente a cualquier objeto o pared límite que esté delante de él (estos datos provendrán de los datosLaserScandel sensor LiDAR del robot).
Feedback:
current_distance_travelled: La distancia recorrida (en metros) desde que se inició el action actual (basada en datos deOdometry).
Result:
total_distance_travelled: La distancia total recorrida (en metros) durante el transcurso del action (basada en datos deOdometry).closest_obstacle: La distancia al obstáculo más cercano por delante del robot cuando el action se completa.
-
Ahora, declara esta interface en el archivo
CMakeLists.txtdel paquete, para que se pueda crear el código Python necesario: -
También modifica el archivo
package.xml(encima de la línea<export>) para incluir las dependenciasrosidlnecesarias: -
Ahora ejecuta
colconpara generar el código fuente necesario para esta nueva interface:-
Primero, siempre asegúrate de estar en la raíz del Workspace:
-
Luego ejecuta
colcon build: -
Y finalmente vuelve a cargar el
.bashrc:
-
-
Ahora podemos verificar que funcionó.
-
Usa
ros2 interfacepara listar todos los tipos de interface disponibles, pero usa la opción-apara mostrar solo las interfaces de tipo action:Busca un mensaje con el prefijo "
part5_actions". -
A continuación, muestra la estructura de datos de la interface:
Esto debería coincidir con el contenido del archivo
part5_actions/action/ExploreForward.actionque creamos arriba.
-
Ejercicio 4: Construir el Action Server "ExploreForward"¶
-
En TERMINAL 1 navega a la carpeta
scriptsde tu paquetepart5_actions, luego:- Crea un script Python llamado
explore_server.py - Hazlo ejecutable
- Decláralo como un ejecutable en
CMakeLists.txt
- Crea un script Python llamado
-
Abre el archivo en VS Code.
-
La tarea del node Server es la siguiente:
- El action server debe hacer que el robot se mueva hacia adelante hasta detectar un obstáculo por delante.
- Debe usar la Interface
ExploreForward.actionque creamos en el ejercicio anterior. -
El Server deberá configurarse para aceptar dos parámetros de goal:
- La velocidad (en m/s) a la que el robot debe moverse hacia adelante cuando se llama al action server.
-
La distancia (en metros) a la que el robot debe detenerse frente a cualquier objeto o pared límite que esté delante de él.
Para hacer esto necesitarás suscribirte al topic
/scan. Ten en cuenta que un objeto no estará necesariamente directamente frente al robot, por lo que puede que necesites monitorear un rango de puntos de datos deLaserScan(dentro del arregloranges) para que la evasión de colisiones sea efectiva (recuerda el ejemplo de callback de LaserScan de la Parte 3).
-
Asegúrate de hacer algunas verificaciones de errores en los parámetros del goal para garantizar que se realice una solicitud válida. Esto se hace adjuntando un
goal_callbackal Action Server.fwd_velocity: Debe ser una velocidad moderada, dentro de los límites de velocidad del robot.stopping_distance: Debe ser mayor que el límite mínimo detectable del Sensor LiDAR, lo suficientemente grande para evitar colisiones de manera segura.
-
Mientras tu servidor realiza su tarea debe proporcionar el siguiente feedback a un Client:
-
La distancia recorrida (en metros) desde que se inició el action actual.
Para hacer esto necesitarás suscribirte al topic
/odom. Recuerda el trabajo que hiciste en la Parte 2 sobre esto.Consejos
- La orientación del robot no debería cambiar durante el transcurso de una sola llamada al action, solo sus posiciones
linear.xylinear.ydeberían variar. - Ten en cuenta, sin embargo, que el robot no necesariamente se moverá a lo largo del eje
XoY, por lo que necesitarás considerar la distancia total recorrida en el planoX-Y. - Hicimos esto en el ejercicio
move_squarede la Parte 2, así que consúltalo si necesitas un recordatorio.
- La orientación del robot no debería cambiar durante el transcurso de una sola llamada al action, solo sus posiciones
-
-
Finalmente, al completarse el action, tu servidor debe estar configurado para proporcionar los siguientes dos parámetros de result:
- La distancia total recorrida (en metros) durante el transcurso del action.
- La distancia al obstáculo que hizo que el robot se detuviera (si el action server ha hecho su trabajo correctamente, esto debería ser muy similar al
stopping_distanceque proporcionó el Action Client en el goal).
-
Hay código de plantilla para ayudarte con esto:
La Plantilla explore_server.pyTambién puede que quieras echar un vistazo al código de
camera_sweep_action_server.pyde los ejercicios anteriores para ayudarte a construir esto: muchas de las técnicas usadas aquí serán similares (excluyendo todo lo relacionado con la cámara).
Pruebas
Cuando quieras probar las cosas, ¿por qué no usar el entorno Nav World de la Parte 3 (en TERMINAL 1)?:
No olvides que para lanzar el servidor necesitarás haber construido todo con colcon siguiendo el proceso habitual de tres etapas.
Haz esto en TERMINAL 2 y luego podrás ejecutarlo:
Además, no olvides que no necesitas haber desarrollado un Node Python Client para probar el servidor. Usa la herramienta CLI ros2 action send_goal para hacer llamadas al servidor (como lo hicimos en el Ejercicio 1).
Ejercicio 5: Construir un Client Básico "ExploreForward"¶
-
En TERMINAL 3 navega a la carpeta
scriptsde tu paquetepart5_actions, crea un script Python llamadoexplore_client.py, hazlo ejecutable y agrégalo a tuCMakeLists.txt. -
Ejecuta
colcon buildahora, para que no tengas que preocuparte por ello más tarde (de nuevo, siguiendo el proceso de tres etapas como arriba)...Aquí está de nuevo, por si acaso:
-
Paso 1:
-
Paso 2:
-
Paso 3:
-
-
Abre el archivo
explore_client.pyen VS Code. -
La tarea del Action Client es la siguiente:
- El client necesita emitir un goal correctamente formateado al servidor.
- El client debe programarse para monitorear los datos de feedback del Server. Si detecta (a partir del feedback) que el robot ha recorrido una distancia mayor de 2 metros sin detectar un obstáculo, entonces debe cancelar la llamada al action actual.
-
Usa las técnicas que usamos en el node Client del Ejercicio 2 como guía para ayudarte con esto.
-
Una vez que tengas todo en su lugar, lanza el action client con
ros2 runcomo se muestra a continuación:Si todo está bien, este node client debería llamar al action server, que (a su vez) hará que el robot se mueva hacia adelante hasta que esté a cierta distancia de un obstáculo por delante (
stopping_distance), momento en el que el robot se detendrá, y tu node client también debería detenerse. Una vez que esto suceda, reorienta tu robot (usando el nodeteleop_keyboard) y lanza el node client nuevamente para asegurarte de que esté deteniéndose de manera robusta frente a los obstáculos repetidamente, y cuando se aproxime desde diferentes ángulos.Importante
Asegúrate de que tu funcionalidad de cancelación también funcione correctamente, garantizando que:
- El robot nunca se mueva más de 2 metros durante una llamada al action determinada.
- Un action se aborta a mitad de camino si el node client se apaga con Ctrl+C.
Ejercicio 6 (Avanzado): Implementar una Estrategia de Exploración¶
Hasta ahora, tu node Action Client debería tener la capacidad de llamar al servidor ExploreForward.action para hacer que el robot se mueva hacia adelante 2 metros, o hasta que llegue a un obstáculo (lo que ocurra primero), pero podrías construir sobre esto ahora y convertirlo en un comportamiento de exploración completo:
- Entre llamadas al action, tu node client podría hacer que el robot gire en su lugar para enfrentar una dirección diferente y luego emitir otra llamada al action para hacer que el robot se mueva hacia adelante nuevamente.
- El proceso de giro podría hacerse aleatoriamente (idealmente), o en una cantidad fija cada vez.
- Programando tu node client para repetir este proceso una y otra vez, el robot (de manera algo aleatoria) recorrería su entorno de forma segura, deteniéndose antes de chocar con cualquier obstáculo y reorientándose cada vez que deje de moverse hacia adelante.
Conclusión¶
En la Parte 5 de este curso has aprendido:
- Cómo funcionan los ROS Actions y por qué pueden ser útiles.
- Cómo desarrollar Action Client Nodes en Python que pueden monitorear el action en tiempo real (a través de feedback), y que también pueden cancelar el action solicitado, si es necesario.
- Cómo usar las herramientas estándar de ROS para interrogar interfaces de action, permitiéndonos así construir clients para llamarlas.
- Cómo construir action servers, clients e interfaces personalizados.
- Cómo aprovechar este marco para implementar una estrategia de exploración.
Topics, Services o Actions: ¿Cuál Elegir?¶
Ahora deberías haber desarrollado una buena comprensión de los tres métodos de comunicación disponibles dentro de ROS para facilitar la comunicación entre ROS Nodes:
- Mensajería basada en Topics.
- ROS Services.
- ROS Actions.
A través de este curso has adquirido algo de experiencia práctica usando los tres, pero puede que aún te preguntes cómo seleccionar el apropiado para cierta tarea robótica...
Esta página web de ROS.org resume todo esto muy bien (y brevemente), así que deberías leerla para asegurarte de saber qué es qué. En resumen:
- Topics: Son más apropiados para transmitir flujos de datos continuos como datos de sensores e información del estado del robot, y para publicar datos que probablemente sean requeridos por una variedad de Nodes en una red de ROS.
- Services: Son más apropiados para procedimientos muy cortos como cálculos rápidos (cinemática inversa, etc.) y para realizar acciones discretas cortas que es poco probable que salgan mal o que no necesitarán intervención (p. ej., encender un LED de advertencia cuando la batería está baja).
- Actions: Son más apropiados para tareas de larga duración (como mover un robot), o para operaciones donde podríamos necesitar cambiar de opinión y hacer algo diferente o cancelar un comportamiento invocado a mitad de camino.
¡Guarda tu trabajo!¶
Sandbox del Laboratorio (rigsa.io)
Tu código se guarda en /home/student/ros2_ws/src dentro de la sesión.
Antes de cerrar la sesión, descárgalo para no perderlo:
- En VS Code, haz clic derecho sobre la carpeta
srcen el explorador de archivos → Descargar… - O desde la terminal integrada:
Luego descarga el archivo
mi_codigo_parte5.zipdesde el explorador de VS Code.
WSL-ROS2 (Computadoras del laboratorio)
Ejecuta el siguiente script en cualquier instancia de Terminal WSL-ROS2 inactiva:
Luego podrás restaurarlo en un entorno WSL-ROS2 nuevo la próxima vez que lo inicies (wsl_ros restore).
-
Recuerda: también puedes usar el comando
wsl_ros restoreen cualquier momento. ↩ -
Necesitas establecer explícitamente los parámetros
goal_imagesygoal_angleen tiempo de ejecución, de manera similar a como lo hicimos aquí con el service clientnumber_game_client.py. ↩