Usando parámetros en nuestros nodos

Introducción

Un parámetro es un valor de configuración de un nodo. Se puede pensar en los parámetros como configuraciones de nodo. Los parámetros admiten los siguientes tipos de valores:

    • Booleano.
    • Número entero.
    • Número en coma flotante.
    • Cadena de texto.
    • Un array de los tipos anteriores.
    • Un array de bytes.

En ROS2 no hay un servidor de parámetros como en ROS1, por lo que los parámetros son específicos de cada nodo. Esto implica que si un nodo finaliza la ejecución sus parámetros dejarán de estar disponibles. Hay que destacar que, como norma general,  es obligatorio declarar los parámetros dentro del código del nodo. 

Hay dos formas de pasar los valores de los distintos parámetros a un nodo:

    • Desde la propia línea de comando al ejecutar el nodo.
    • Desde un archivo launch.

De ambas formas podemos pasar los parámetros indicándolos uno a uno o cargándolos desde un archivo YAML.

Trabajando con parámetros desde la línea de comando

Para trabajar con parámetros desde el terminal se emplea el comando ros2 param <subcommand> [arguments]. A continuación se muestran cuales son los distintos subcomandos disponibles y los argumentos que admiten.

Listar los parámetros de uno o todos los nodos

ros2 param list
ros2 param list /<node_name>

Obtener la descripción de un parámetro

ros2 param describe /<node_name> <parameter_name>

Obtener el valor de un parámetro

ros2 param get /<node_name> <parameter_name>

Establecer el valor de un parámetro

ros2 param set /<node_name> <parameter_name> <parameter_value>

Eliminar un parámetro

ros2 param delete /<node_name> <parameter_name>

Pasar parámetros a un nodo al ejecutarlo

En la misma línea en la que ejecutamos un nodo podemos incluir los parámetros que queremos pasarle. Para ello usamos la etiqueta –ros-args para indicar que seguidamente se van a pasar argumentos de ROS y luego se van incluyendo los parámetros mediante -p <parametro>:=<valor>.  

A continuación se muestra como pasar al nodo tres parámetros, uno de tipo string, otro de tipo entero y un array.

ros2 run <my_package> <my_node> [my_args...] --ros-args -p my_str_param:="Hello" -p my_int_param:=4 -p my_array_param:="[3.1, 5.7, 6.2]"

Cargar los parámetros desde un archivo YAML

Mediante el subcomando load podemos cargar todos los parámetros pertenecientes a un nodo que se encuentren almacenados en un archivo YAML.

ros2 param load <node_name> <yaml_file>

Volcar los parámetros a un archivo YAML

El subcomando dump permite guardar todos los parámetros en un archivo YAML.

ros2 param dump
ros2 param dump /<node_name> 

Trabajando con parámetros desde el código del nodo en Python

Declarar los parámetros

Para acceder desde el código de nuestro nodo a los parámetros lo primero que debemos hacer es declararlos mediante la función declare_parameter( ) si lo hacemos para un solo parámetro o usando declare_parameters( ) si queremos declarar varios al mismo tiempo. En dicha declaración indicaremos el nombre del parámetro y opcionalmente el valor por defecto y un descriptor, que es un objeto del tipo ParameterDescriptor que permite indicar un texto de descripción del parámetro así como restricciones tales como hacerlo de solo lectura o especificar un rango.

A continuación se recoge de forma más detallada cada función y los parámetro que admiten.

declare_parameter (name, value=None, descriptor, ignore_override=False)

Parametros:

  • name (str): nombre del parámetro, incluyendo su espacio de nombres (namespace.param_name).

  • value (opcional, [Any]): valor del parámetro.

  • descriptor (opcional, ParameterDescriptor): objeto con la descripción del parámetro.

  • ignore_override (opcional, bool): si es True ignora el parámetro parameter_overrides del constructor del nodo.

declare_parameters (namespace, parameters, ignore_override=False)

Parametros:

  • namespace (str): indica el espacio de nombres.

  • parameters ([ ]): lista que contiene una tupla por cada parámetro, donde cada una una contiene el nombre y opcionalmente el valor y el descriptor.

  • descriptor (ParameterDescriptor): objeto con la descripción del parámetro. Cada tupla debe contener el nombre del parámetro y opcionalmente el valor y el descriptor.

  • ignore_override (bool): si es True ignora el parámetro parameter_overrides del constructor del nodo.

# Describe el parámetro. No es obligatorio pero si conveniente.
from rcl_interfaces.msg import ParameterDescriptor

param_descriptor = ParameterDescriptor(
    description='Sets the velocity (in m/s) of the robot.')

# Declara un parámetro, asignándole un valor por defecto y la descripción.
self.declare_parameter('velocity', 0.0, param_descriptor)

# Declara varios parámetros al mismo tiempo
self.declare_parameters(
    namespace='',
    parameters=[
        ('my_str', 'Hello'),
        ('my_int', 3),
        ('my_double_array', [2.4, 5.1])
    ]
)

EL siguiente código es un ejemplo de cómo se realiza la declaración de parámetros.

Obtener los valores de los parámetros

Una vez declarados los parámetros podemos obtener sus valores como se indica en el siguiente código, accediendo a los mismos de forma individual o a varios al mismo tiempo.

# Obtiene el valor del parámetro
self.get_parameter('param_name').value

# Obtiene el valor de varios parámetros
(param_str, param_int, param_double_array) = self.get_parameters(
            ['my_str', 'my_int', 'my_double_array'])

Modificar el valor de los parámetros

El valor de un parámetro será el que le hallamos asignado desde la línea de comando o el valor por defecto que se haya indicado en su declaración. No obstante es posible modificar dicho valor desde el propio código del nodo.

Para ello es necesario crear un objeto Parameter pasando a su constructor el nombre del parámetro, su tipo y el valor que deseamos asignarle. Luego pasaremos dicho objeto a la función set_parameters( ) para cambiar el valor. Esta función recibe un array de objetos Parameter, por lo que podemos modificar el valor de varios parámetros a la vez. 

from rclpy.parameter import Parameter

# Creamos objetos de tipo Parameter con los nuevos valores
param_str = Parameter('my_str', Parameter.Type.STRING, 'Set from code')
param_int = Parameter('my_int', Parameter.Type.INTEGER, 12)
param_double_array = Parameter('my_double_array', Parameter.Type.DOUBLE_ARRAY, [1.1, 2.2])

# Modificamos los valores de los parámetros
self.set_parameters([param_str, param_int, param_double_array])

# Los tipos posibles son los siguientes:
"""
Parameter.Type.BOOL
Parameter.Type.BOOL_ARRAY
Parameter.Type.BYTE_ARRAY
Parameter.Type.DOUBLE
Parameter.Type.DOUBLE_ARRAY
Parameter.Type.INTEGER
Parameter.Type.INTEGER_ARRAY
Parameter.Type.NOT_SET
Parameter.Type.STRING
Parameter.Type.STRING_ARRAY
"""

Eliminar parámetros

Desde código también podemos eliminar parámetros. Para ello usamos la función undeclare_parameter( ) pasándole el nombre del parámetro.

self.undeclare_parameter('param_name')

Usando una función de callback

Es posible definir una función de callback que se ejecute cada vez que un parámetro es modificado, lo cual nos permite reconfigurar de forma dinámica nuestro nodo. Para ello, tal como se muestra en el siguiente ejemplo, usamos la función add_on_set_parameters_callback( ) pasándole como parámetro la función que queremos que se ejecute. 

La función de callback que definamos recibe como parámetro params, que es un objeto iterable con los parámetros que han cambiado y debe devolver un objeto SetParameterResult.

from rclpy.parameter import Parameter
from rcl_interfaces.msg import SetParametersResult

# Indica la función que se ejecuta cuando se modifica un parámetro
self.add_on_set_parameters_callback(self.parameter_callback)

# Función de callback
def parameter_callback(self, params):
    for param in params:
        if param.name == 'max_speed' and param.type_ == Parameter.Type.INTEGER:
            self.max_speed = param.value
    return SetParametersResult(successful=True)

Guardando la configuración de nuestro nodo en un archivo YAML

A medida que nuestra aplicación se vaya complicando nos encontraremos con la situación de tener que manejar un número elevado de parámetros. Lo cual hace inviable declararlos uno a uno desde la línea de comando.

La solución a esto es guardar los parámetros en un archivo YAML ejecutando desde la línea de comando ros2 param dump /<node_name> <yaml_file>, de forma que luego podamos cargarlos todos a la vez usando ros2 param load /<node_name> <yaml_file> o desde un archivo launch.

Estos archivos de configuración es habitual guardarlos en una carpeta /config dentro de nuestro paquete.

Estructura del archivo YAML

En el mismo archivo podemos almacenar los parámetros de configuración de distintos nodos y es posible definir parámetros anidados.

La estructura básica que debe tener el archivo YAML es la siguiente:

<node_name>:
  ros__parameters:
    <param_name_1>: <value>
    <param_name_n>: <value>

A continuación se muestra un ejemplo de archivo de configuración YAML.

my_node_name_1:
  ros__parameters:
    bool_value: True
    int_number: 5
    float_number: 3.14
    str_text: "My text"
    bool_array: [True, False, False]
    int_array: [1, 82, 15, 43]
    float_array: [72.5, 30.6]
    str_array: ['Hello', 'World']
    bytes_array: [0x03, 0xA1, 0xFF]
    nested_param:
      another_int: 3

my_node_name_2:
  ros__parameters:
    bool_value: True
    int_number: 5

Cargando los parámetros desde un archivo LAUNCH

Al crear en nuestro archivo launch la acción correspondiente para cargar un nodo también podemos indicar sus parámetros de configuración, bien indicándolos uno a uno o cargándolos desde un archivo YAML.

import os
from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch_ros.actions import Node

def generate_launch_description():

    config = os.path.join(
        get_package_share_directory('my_package_name'),
        'config',
        'params.yaml'
        )

    return LaunchDescription(
        Node(
        package = 'my_package_name',
        name = 'my_node1_name',
        executable = 'my_node1_name',
        parameters = [config]
        ),

        Node(
        package = 'my_package_name',
        name = 'my_node2_name',
        executable = 'my_node2_name',
        parameters = [config]
        ),
    )

Para que funcione correctamente no debemos olvidar modificar nuestro archivo setup.py  para incluir  los directorios /config y /launch en la instalación del paquete. Para ello añadimos a data_files las dos últimas líneas que se muestran a continuación y compilamos el paquete.

    data_files=[
        ('share/ament_index/resource_index/packages',
            ['resource/' + package_name]),
        ('share/' + package_name, ['package.xml']),
        (os.path.join('share', package_name, 'launch'), glob('launch/*.launch.py')),
        (os.path.join('share', package_name, 'config'), glob('config/*.yaml'))
    ],
Comparte este artículo:

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *