Skip to content

Commit

Permalink
More cleanups in the Parameters tutorials.
Browse files Browse the repository at this point in the history
These cleanups make the two tutorials act just like each other.

Signed-off-by: Chris Lalancette <clalancette@openrobotics.org>
  • Loading branch information
clalancette committed Oct 18, 2022
1 parent c7be0d1 commit f93706b
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 93 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -70,37 +70,45 @@ Inside the ``ros2_ws/src/cpp_parameters/src`` directory, create a new file calle

.. code-block:: C++

#include <rclcpp/rclcpp.hpp>
#include <chrono>
#include <string>
#include <functional>
#include <string>

#include <rclcpp/rclcpp.hpp>

using namespace std::chrono_literals;

class ParametersClass: public rclcpp::Node
class MinimalParam : public rclcpp::Node
{
public:
ParametersClass()
: Node("minimal_param_node")
{
this->declare_parameter<std::string>("my_parameter", "world");
timer_ = this->create_wall_timer(
1000ms, std::bind(&ParametersClass::respond, this));
}
void respond()
{
this->get_parameter("my_parameter", parameter_string_);
RCLCPP_INFO(this->get_logger(), "Hello %s", parameter_string_.c_str());
}
private:
std::string parameter_string_;
rclcpp::TimerBase::SharedPtr timer_;
public:
MinimalParam()
: Node("minimal_param_node")
{
this->declare_parameter("my_parameter", "world");

timer_ = this->create_wall_timer(
1000ms, std::bind(&MinimalParam::timer_callback, this));
}

void timer_callback()
{
std::string my_param =
this->get_parameter("my_parameter").get_parameter_value().get<std::string>();

RCLCPP_INFO(this->get_logger(), "Hello %s!", my_param.c_str());

std::vector<rclcpp::Parameter> all_new_parameters{rclcpp::Parameter("my_parameter", "world")};
this->set_parameters(all_new_parameters);
}

private:
rclcpp::TimerBase::SharedPtr timer_;
};

int main(int argc, char** argv)
int main(int argc, char ** argv)
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<ParametersClass>());
rclcpp::spin(std::make_shared<MinimalParam>());
rclcpp::shutdown();
return 0;
}
Expand All @@ -110,51 +118,58 @@ Inside the ``ros2_ws/src/cpp_parameters/src`` directory, create a new file calle
The ``#include`` statements at the top are the package dependencies.

The next piece of code creates the class and the constructor.
The first line of this constructor creates our parameter.
Our parameter has the name ``my_parameter`` and is assigned the default value ``world``.
Next, ``timer_`` is initialized, which causes the ``respond`` function to be executed once a second.
The first line of this constructor creates a parameter with the name ``my_parameter`` and a default value of ``world``.
The parameter type is inferred from the default value, so in this case it would be set to a string type.
Next the ``timer_`` is initialized with a period of 1000ms, which causes the ``timer_callback`` function to be executed once a second.

.. code-block:: C++

class ParametersClass: public rclcpp::Node
class MinimalParam : public rclcpp::Node
{
public:
ParametersClass()
: Node("minimal_param_node")
{
this->declare_parameter<std::string>("my_parameter", "world");
timer_ = this->create_wall_timer(
1000ms, std::bind(&ParametersClass::respond, this));
}

The first line of our ``respond`` function gets the parameter ``my_parameter`` from the node, and stores it in ``parameter_string_``.
The ``RCLCPP_INFO`` function ensures the message is logged.
public:
MinimalParam()
: Node("minimal_param_node")
{
this->declare_parameter("my_parameter", "world");

timer_ = this->create_wall_timer(
1000ms, std::bind(&MinimalParam::timer_callback, this));
}

The first line of our ``timer_callback`` function gets the parameter ``my_parameter`` from the node, and stores it in ``my_param``.
Next the ``RCLCPP_INFO`` function ensures the message is logged.
The ``set_parameters`` function then sets the parameter ``my_parameter`` back to the default string value ``world``.
In the case that the user changed the parameter externally, this ensures it is always reset back to the original.

.. code-block:: C++

void respond()
void timer_callback()
{
this->get_parameter("my_parameter", parameter_string_);
RCLCPP_INFO(this->get_logger(), "Hello %s", parameter_string_.c_str());
std::string my_param =
this->get_parameter("my_parameter").get_parameter_value().get<std::string>();

RCLCPP_INFO(this->get_logger(), "Hello %s!", my_param.c_str());

std::vector<rclcpp::Parameter> all_new_parameters{rclcpp::Parameter("my_parameter", "world")};
this->set_parameters(all_new_parameters);
}

Last is the declaration of ``timer_`` and ``parameter_string_``
Last is the declaration of ``timer_``

.. code-block:: C++

private:
std::string parameter_string_;
rclcpp::TimerBase::SharedPtr timer_;

Following our ``ParametersClass`` is our ``main``.
Here ROS 2 is initialized, and ``rclcpp::spin`` starts processing data from the node.
Following our ``MinimalParam`` is our ``main``.
Here ROS 2 is initialized, an instance of the ``MinimalParam`` class is constructed, and ``rclcpp::spin`` starts processing data from the node.

.. code-block:: C++

int main(int argc, char** argv)
int main(int argc, char ** argv)
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<ParametersClass>());
rclcpp::spin(std::make_shared<MinimalParam>());
rclcpp::shutdown();
return 0;
}
Expand All @@ -169,19 +184,20 @@ For that to work, the code in the constructor has to be changed to:

// ...

class ParametersClass: public rclcpp::Node
class MinimalParam : public rclcpp::Node
{
public:
ParametersClass()
: Node("minimal_param_node")
{
auto param_desc = rcl_interfaces::msg::ParameterDescriptor{};
param_desc.description = "This parameter is mine!";

this->declare_parameter<std::string>("my_parameter", "world", param_desc);
timer_ = this->create_wall_timer(
1000ms, std::bind(&ParametersClass::respond, this));
}
public:
MinimalParam()
: Node("minimal_param_node")
{
auto param_desc = rcl_interfaces::msg::ParameterDescriptor{};
param_desc.description = "This parameter is mine!";

this->declare_parameter("my_parameter", "world", param_desc);

timer_ = this->create_wall_timer(
1000ms, std::bind(&MinimalParam::timer_callback, this));
}

The rest of the code remains the same.
Once you run the node, you can then run ``ros2 param describe /minimal_param_node my_parameter`` to see the type and description.
Expand All @@ -192,7 +208,7 @@ Once you run the node, you can then run ``ros2 param describe /minimal_param_nod

Now open the ``CMakeLists.txt`` file. Below the dependency ``find_package(rclcpp REQUIRED)`` add the following lines of code.

.. code-block:: console
.. code-block:: cmake
add_executable(minimal_param_node src/cpp_parameters_node.cpp)
ament_target_dependencies(minimal_param_node rclcpp)
Expand Down Expand Up @@ -278,7 +294,7 @@ The terminal should return the following message every second:

.. code-block:: console
[INFO] [minimal_param_node]: Hello world
[INFO] [minimal_param_node]: Hello world!
Now you can see the default value of your parameter, but you want to be able to set it yourself.
There are two ways to accomplish this.
Expand Down Expand Up @@ -308,7 +324,7 @@ To change it simply run the following line in the console:
ros2 param set /minimal_param_node my_parameter earth
You know it went well if you get the output ``Set parameter successful``.
If you look at the other terminal, you should see the output change to ``[INFO] [minimal_param_node]: Hello earth``
If you look at the other terminal, you should see the output change to ``[INFO] [minimal_param_node]: Hello earth!``

3.2 Change via a launch file
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down Expand Up @@ -408,7 +424,7 @@ The terminal should return the following message every second:

.. code-block:: console
[minimal_param_node-1] [INFO] [custom_minimal_param_node]: Hello earth
[INFO] [custom_minimal_param_node]: Hello earth!
Summary
-------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
Using parameters in a class (Python)
====================================

**Goal:** Create and run a class with ROS parameters using Python (rclpy).
**Goal:** Create and run a class with ROS parameters using Python.

**Tutorial level:** Beginner

Expand Down Expand Up @@ -76,11 +76,11 @@ Inside the ``ros2_ws/src/python_parameters/python_parameters`` directory, create
class MinimalParam(rclpy.node.Node):
def __init__(self):
super().__init__('minimal_param_node')
timer_period = 2 # seconds
self.timer = self.create_timer(timer_period, self.timer_callback)
self.declare_parameter('my_parameter', 'world')
self.timer = self.create_timer(1, self.timer_callback)
def timer_callback(self):
my_param = self.get_parameter('my_parameter').get_parameter_value().string_value
Expand All @@ -106,32 +106,25 @@ Inside the ``ros2_ws/src/python_parameters/python_parameters`` directory, create
2.1 Examine the code
~~~~~~~~~~~~~~~~~~~~
Note: Declaring a parameter before getting or setting it is compulsory, or a ``ParameterNotDeclaredException`` exception will be raised.

The ``import`` statements below are used to import the package dependencies.

.. code-block:: Python
import rclpy
import rclpy.node
The ``import`` statements at the top are used to import the package dependencies.

The next piece of code creates the class and the constructor.
``timer`` is initialized (with timer_period set as 2 seconds), which causes the ``timer_callback`` function to be executed once every two seconds.
The line ``self.declare_parameter('my_parameter', 'world')`` of the constructor creates a parameter with the name ``my_parameter`` and a default value of ``world``.
The parameter type is inferred from the default value, so in this case it would be set to a string type.
Next the ``timer`` is initialized with a period of 1, which causes the ``timer_callback`` function to be executed once every second.

.. code-block:: Python
class MinimalParam(rclpy.node.Node):
def __init__(self):
super().__init__('minimal_param_node')
timer_period = 2 # seconds
self.timer = self.create_timer(timer_period, self.timer_callback)
self.declare_parameter('my_parameter', 'world')
self.timer = self.create_timer(1, self.timer_callback)
The first line of our ``timer_callback`` function gets the parameter ``my_parameter`` from the node, and stores it in ``my_param``.
Next,the ``get_logger`` function ensures the message is logged.
Next the ``get_logger`` function ensures the message is logged.
The ``set_parameters`` function then sets the parameter ``my_parameter`` back to the default string value ``world``.
In the case that the user changed the parameter externally, this ensures it is always reset back to the original.

Expand All @@ -150,9 +143,8 @@ In the case that the user changed the parameter externally, this ensures it is a
all_new_parameters = [my_new_param]
self.set_parameters(all_new_parameters)
Following the ``timer_callback`` is the ``main`` function where ROS 2 is initialized.
Then an instance of the class ``MinimalParam`` named ``node`` is defined.
Finally, ``rclpy.spin`` starts processing data from the node.
Following the ``timer_callback`` is our ``main``.
Here ROS 2 is initialized, an instance of the ``MinimalParam`` class is constructed, and ``rclpy.spin`` starts processing data from the node.

.. code-block:: Python
Expand All @@ -178,8 +170,6 @@ For that to work, the ``__init__`` code has to be changed to:
class MinimalParam(rclpy.node.Node):
def __init__(self):
super().__init__('minimal_param_node')
timer_period = 2 # seconds
self.timer = self.create_timer(timer_period, self.timer_callback)
from rcl_interfaces.msg import ParameterDescriptor
my_parameter_descriptor = ParameterDescriptor(description='This parameter is mine!')
Expand All @@ -188,6 +178,9 @@ For that to work, the ``__init__`` code has to be changed to:
'world',
my_parameter_descriptor)
self.timer = self.create_timer(1, self.timer_callback)
The rest of the code remains the same.
Once you run the node, you can then run ``ros2 param describe /minimal_param_node my_parameter`` to see the type and description.

Expand All @@ -210,7 +203,7 @@ Add the following line within the ``console_scripts`` brackets of the ``entry_po
entry_points={
'console_scripts': [
'param_talker = python_parameters.python_parameters_node:main',
'minimal_param_node = python_parameters.python_parameters_node:main',
],
},
Expand Down Expand Up @@ -286,15 +279,16 @@ Now run the node:

.. code-block:: console
ros2 run python_parameters param_talker
ros2 run python_parameters minimal_param_node
The terminal should return the following message every 2 seconds:

.. code-block:: console
[INFO] [parameter_node]: Hello world!
There are two ways to change the parameter:
Now you can see the default value of your parameter, but you want to be able to set it yourself.
There are two ways to accomplish this.

3.1 Change via the console
~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand All @@ -305,7 +299,7 @@ Make sure the node is running:

.. code-block:: console
ros2 run python_parameters param_talker
ros2 run python_parameters minimal_param_node
Open another terminal, source the setup files from inside ``ros2_ws`` again, and enter the following line:

Expand All @@ -323,7 +317,7 @@ To change it simply run the following line in the console:
You know it went well if you get the output ``Set parameter successful``.
If you look at the other terminal, you should see the output change to ``[INFO] [minimal_param_node]: Hello earth!``

Since the Python talker then set the parameter back to ``world``, further outputs show ``[INFO] [minimal_param_node]: Hello world!``
Since the node then set the parameter back to ``world``, further outputs show ``[INFO] [minimal_param_node]: Hello world!``

3.2 Change via a launch file
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand All @@ -341,8 +335,8 @@ In there, create a new file called ``python_parameters_launch.py``
return LaunchDescription([
Node(
package='python_parameters',
executable='param_talker',
name='custom_parameter_node',
executable='minimal_param_node',
name='custom_minimal_param_node',
output='screen',
emulate_tty=True,
parameters=[
Expand Down Expand Up @@ -373,7 +367,7 @@ Add the ``import`` statements to the top of the file, and the other new statemen
# ...
data_files=[
# ...
(os.path.join('share', package_name), glob('launch/*_launch.py')),
(os.path.join('share', package_name), glob('launch/*launch.[pxy][yma]*')),
]
)
Expand Down Expand Up @@ -431,15 +425,14 @@ The terminal should return the following message:

.. code-block:: console
[parameter_node-1] [INFO] [custom_parameter_node]: Hello earth!
[INFO] [custom_minimal_param_node]: Hello earth!
Summary
-------

You created a node with a custom parameter, that can be set either from the launch file or the command line.
You wrote the code of a parameter talker: a Python node that declares, and then loops getting and setting a string parameter.
You added the entry point so that you could build and run it, and used ``ros2 param`` to interact with the parameter talker.
You created a node with a custom parameter, that can be set either from a launch file or the command line.
You added the dependencies, executables, and a launch file to the package configuration files so that you could build and run them, and see the parameter in action.

Next steps
----------
Expand Down

0 comments on commit f93706b

Please sign in to comment.