Skip to content

Commit 1d46bd0

Browse files
committed
Add Topic Keys Subscription Filtering tutorial
Signed-off-by: Mario Dominguez <[email protected]>
1 parent 693b82c commit 1d46bd0

12 files changed

+1572
-0
lines changed

source/Tutorials/Advanced.rst

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Advanced
66

77
Advanced/Topic-Statistics-Tutorial/Topic-Statistics-Tutorial
88
Advanced/Topic-Keys/Topic-Keys-Tutorial
9+
Advanced/Topic-Keys/Filtered-Topic-Keys-Tutorial
910
Advanced/Discovery-Server/Discovery-Server
1011
Advanced/Allocator-Template-Tutorial
1112
Advanced/Ament-Lint-For-Clean-Code
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
.. _filtered_topic_keys_tutorial:
2+
3+
Topic Keys Subscription Filtering Tutorial
4+
==========================================
5+
6+
This tutorial aims to demonstrate how to receive data only from certain topic instances by combining the use of topic keys and topic content filtering.
7+
8+
.. contents::
9+
:depth: 2
10+
:local:
11+
:backlinks: none
12+
13+
Background
14+
----------
15+
16+
In ROS 2, topics are a mean for representing the state of an object.
17+
Keyed topics are special topics where each data sample represent an update of the state of a specific object (known as *instance*) among all those objects represented in the topic.
18+
This allows the user to reduce the number of required resources (topics, along with its associated publisher and subscriber) by multiplexing updates of several objects of the same kind into a single resource.
19+
20+
The :doc:`Content Filter Topic <../../Demos/Content-Filtering-Subscription>` facilitates efficient data distribution by allowing the subscription (reader-side) to specify criteria for the types of data they wish to receive.
21+
By defining this criteria, irrelevant data can be filtered out and applications can focus only on the information that is relevant to their needs.
22+
This functionality not only reduces the amount of data transmitted over the network but also minimizes processing overhead on the receiving end, leading to improved system performance and scalability.
23+
24+
When combined with topic instances, the benefits of the Content Filter are further enhanced.
25+
By associating specific filter criteria with each topic instance, it is possible fine-tune the data selection process and tailor it to their precise requirements.
26+
This granular level of filtering enables applications to optimally manage data while ensuring that they exchange only the information that is pertinent to their individual use cases.
27+
28+
.. image:: figures/keyed-topics-cft.gif
29+
:align: center
30+
:width: 70%
31+
32+
RMW Support
33+
-----------
34+
35+
Keyed topics require RMW implementation support.
36+
37+
.. list-table:: Keyed Topics Support Status
38+
:widths: 25 25
39+
40+
* - rmw_fastrtps
41+
- supported
42+
* - rmw_connextdds
43+
- supported
44+
* - rmw_cyclonedds
45+
- not supported
46+
47+
Implementations not supporting the feature will treat keyed topics as standard topics.
48+
The implications are explained throughout the tutorial.
49+
In addition, endpoints using `Cyclone DDS <https://index.ros.org/p/rmw_cyclonedds_cpp/>`_ will not even match with Fast DDS or Connext DDS endpoints for this kind of topics.
50+
51+
Prerequisites
52+
-------------
53+
54+
* An up-to-date ROS 2 installation and setup.
55+
Either installed in local host, or using Docker images.
56+
57+
Preparing the demo package
58+
--------------------------
59+
60+
Lets start by setting up the ROS 2 environment.
61+
For this, there are two possible options:
62+
63+
#. Running a ROS 2 Docker image.
64+
65+
.. code-block:: bash
66+
67+
docker run -it --rm osrt/ros:{DISTRO}-desktop
68+
69+
Then, within the container, source the ROS 2 installation with:
70+
71+
.. code-block:: bash
72+
73+
source /opt/ros/{DISTRO}/setup.bash
74+
75+
#. Running the tutorial on the local host.
76+
Please, follow the :doc:`installation instructions <../../../Installation>` for details on installing ROS 2.
77+
78+
Source the following file to setup the ROS 2 environment:
79+
80+
.. code-block:: bash
81+
82+
source /opt/ros/{DISTRO}/setup.bash
83+
84+
85+
Retrieving the sources
86+
^^^^^^^^^^^^^^^^^^^^^^
87+
88+
Create a new workspace and download the demo package sources as indicated below:
89+
90+
.. code-block:: bash
91+
92+
# Create directory structure
93+
mkdir -p ~/tutorial_ws/src/demo_keys_filtering_cpp
94+
mkdir ~/tutorial_ws/src/demo_keys_filtering_cpp/msg
95+
mkdir ~/tutorial_ws/src/demo_keys_filtering_cpp/src
96+
mkdir ~/tutorial_ws/src/demo_keys_filtering_cpp/launch
97+
cd ~/tutorial_ws/src/demo_keys_filtering_cpp
98+
99+
# Download demo package source code
100+
wget -O CMakeLists.txt https://raw.githubusercontent.com/ros2/ros2_documentation/{DISTRO}/source/Tutorials/Advanced/Topic-Keys/resources/Filtered/CMakeLists.txt
101+
wget -O package.xml https://raw.githubusercontent.com/ros2/ros2_documentation/{DISTRO}/source/Tutorials/Advanced/Topic-Keys/resources/Filtered/package.xml
102+
wget -O README.md https://raw.githubusercontent.com/ros2/ros2_documentation/{DISTRO}/source/Tutorials/Advanced/Topic-Keys/resources/Filtered/README.md
103+
wget -O msg/KeyedSensorDataMsg.msg https://raw.githubusercontent.com/ros2/ros2_documentation/{DISTRO}/source/Tutorials/Advanced/Topic-Keys/resources/Filtered/msg/KeyedSensorDataMsg.msg
104+
wget -O src/filtered_keyed_sensor.cpp https://raw.githubusercontent.com/ros2/ros2_documentation/{DISTRO}/source/Tutorials/Advanced/Topic-Keys/resources/Filtered/src/filtered_keyed_sensor.cpp
105+
wget -O src/filtered_keyed_controller.cpp https://raw.githubusercontent.com/ros2/ros2_documentation/{DISTRO}/source/Tutorials/Advanced/Topic-Keys/resources/Filtered/src/filtered_keyed_controller.cpp
106+
wget -O launch/keyed_sensors_launch.py https://raw.githubusercontent.com/ros2/ros2_documentation/{DISTRO}/source/Tutorials/Advanced/Topic-Keys/resources/Filtered/launch/keyed_sensors_launch.py
107+
108+
The resulting directory structure should be:
109+
110+
.. code-block::
111+
112+
~/tutorial_ws
113+
├──src
114+
├── demo_keys_filtering_cpp
115+
├── CMakeLists.txt
116+
├── README.md
117+
├── launch
118+
│ └── keyed_sensors_launch.py
119+
├── msg
120+
│ └── KeyedSensorDataMsg.msg
121+
├── package.xml
122+
└── src
123+
├── filtered_keyed_controller.cpp
124+
└── filtered_keyed_sensor.cpp
125+
126+
A brief analysis on the provided files is explained below:
127+
128+
* *demo_keys_filtering_cpp* : This directory contains the main source code and configuration files for the demonstration.
129+
* ``CMakeLists.txt``: This file is used with CMake to specify build configurations and dependencies.
130+
* ``README.md``: This is a markdown file providing instructions or information about the demonstration.
131+
* *launch*: This directory contains launch configuration files for launching ROS nodes.
132+
133+
* ``keyed_sensors_launch.py``: This Python script is used to launch the demonstration nodes.
134+
135+
* *msg*: This directory contains message definition files.
136+
137+
* KeyedSensorDataMsg.msg: This is the file defining the message structure for keyed sensor data used in the tutorial.
138+
139+
* ``package.xml``: This is an XML file containing metadata about the ROS package.
140+
* *src*: This directory contains the source code files for the demonstration.
141+
142+
* ``filtered_keyed_controller.cpp``: This is the source code for a controller node that filters keyed sensor data in reception, being the most relevant lines the ones that define the filter expression and Quality of Service settings:
143+
144+
.. code-block:: bash
145+
146+
// Initialize a subscription with a content filter to receive data from sensors 2 to 4
147+
rclcpp::SubscriptionOptions sub_options;
148+
sub_options.content_filter_options.filter_expression =
149+
"sensor_id >= 2 AND sensor_id <= 4 AND measurement > %0";
150+
sub_options.content_filter_options.expression_parameters = {
151+
std::to_string(SENSOR_TRIGGER)
152+
};
153+
154+
// Create the subscription with the content filter options
155+
sub_ = create_subscription<demo_keys_filtering_cpp::msg::KeyedSensorDataMsg>("/robot/sensors",
156+
rclcpp::QoS(rclcpp::KeepLast(1)).reliable().transient_local(),
157+
callback,
158+
sub_options);
159+
160+
* ``filtered_keyed_sensor.cpp``: This is the source code for a sensor node that publishes keyed sensor data.
161+
The most relevant lines are the ones that create the publication with a particular Quality of Service settings that enables the controller to late join the application but still receiving the latest update for every instance with the use of topic keys.
162+
163+
.. code-block:: bash
164+
165+
pub_ = this->create_publisher<demo_keys_filtering_cpp::msg::KeyedSensorDataMsg>(
166+
"/robot/sensors",
167+
rclcpp::QoS(rclcpp::KeepLast(1)).reliable().transient_local());
168+
169+
Generating the IDL files
170+
^^^^^^^^^^^^^^^^^^^^^^^^
171+
172+
Generate the corresponding IDL definition from the provided ``KeyedSensorDataMsg.msg`` file, using the ``msg2idl.py`` script from the ``rosidl_adapter`` package.
173+
174+
.. code-block:: bash
175+
176+
source /opt/ros/{DISTRO}/setup.bash
177+
cd ~/tutorial_ws/src/demo_keys_filtering_cpp/msg
178+
ros2 run rosidl_adapter msg2idl.py KeyedSensorDataMsg.msg
179+
rm KeyedSensorDataMsg.msg
180+
181+
Next, annotate the ``sensor_id`` field as ``@key`` in the generated ``KeyedSensorDataMsg.idl``.
182+
Its content should look like the following:
183+
184+
.. code-block:: bash
185+
186+
# KeyedSensorDataMsg.idl
187+
module demo_keys_filtering_cpp {
188+
module msg {
189+
struct KeyedSensorDataMsg {
190+
@key int16 sensor_id;
191+
double measurement;
192+
string data;
193+
};
194+
};
195+
};
196+
197+
Building the demo package
198+
^^^^^^^^^^^^^^^^^^^^^^^^^
199+
200+
Once the environment has been setup and the demo package sources are available, the demo package can be built.
201+
Get into the root of the workspace and build it with the following commands:
202+
203+
.. code-block:: bash
204+
205+
source /opt/ros/{DISTRO}/setup.bash
206+
cd ~/tutorial_ws
207+
colcon build
208+
209+
210+
Running the demo
211+
----------------
212+
213+
In the demo, different sensors are publishing data to a controller node using a keyed topic as exemplified below:
214+
215+
.. image:: figures/cft_tutorial_diagram.svg
216+
:align: center
217+
:width: 80%
218+
219+
Run the demo by executing the following commands in separate terminals:
220+
221+
.. note::
222+
223+
If a docker deployment was preferred, it would be necessary to attach the other two terminals to the running docker container before executing the above commands.
224+
This can be done by running ``docker exec -it <container_name> /bin/bash``.
225+
226+
.. tabs::
227+
228+
.. tab:: Shell 1 (Sensors)
229+
230+
.. code-block:: bash
231+
232+
source ~/tutorial_ws/install/setup.bash
233+
ros2 launch demo_keys_filtering_cpp keyed_sensors_launch.py
234+
235+
.. tab:: Shell 2 (Controller)
236+
237+
.. code-block:: bash
238+
239+
source ~/tutorial_ws/install/setup.bash
240+
ros2 run demo_keys_filtering_cpp filtered_keyed_controller
241+
242+
243+
The resulting output should be similar to the following, in which the controller node is only receiving data from the specified sensors, i.e. sensors which sensor_id is in the range [2, 4].
244+
In addition, only when the measurement is greater than 60, the controller node will receive data.
245+
That is specified in the filter expression ``sensor_id >= 2 AND sensor_id <= 4 AND measurement > %0``:
246+
247+
.. image:: figures/filtered_keyed_topic.gif
248+
:align: center
249+
:width: 100%
250+
251+
Even in a late-joining scenario, the controller node will receive the latest data of the sensors that meet the filtering criteria at the moment it joins the application, which could be crucial depending on the type of the real application.
252+
253+
Overall, the combination of topic instances with a content filter topic offers significant benefits in terms of data efficiency, scalability, adaptability and resource optimization.
254+
By leveraging these capabilities, ROS 2 applications can efficiently manage and distribute data in complex distributed environments.

source/Tutorials/Advanced/Topic-Keys/figures/cft_tutorial_diagram.svg

+986
Loading
Loading
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# Copyright 2024 Proyectos y Sistemas de Mantenimiento SL (eProsima).
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
cmake_minimum_required(VERSION 3.12)
16+
17+
project(demo_keys_filtering_cpp)
18+
19+
# Default to C++17
20+
if(NOT CMAKE_CXX_STANDARD)
21+
set(CMAKE_CXX_STANDARD 17)
22+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
23+
endif()
24+
25+
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
26+
add_compile_options(-Wall -Wextra -Wpedantic)
27+
endif()
28+
29+
find_package(ament_cmake REQUIRED)
30+
find_package(example_interfaces REQUIRED)
31+
find_package(rcl REQUIRED)
32+
find_package(rcl_interfaces REQUIRED)
33+
find_package(rclcpp REQUIRED)
34+
find_package(rclcpp_components REQUIRED)
35+
find_package(rcpputils REQUIRED)
36+
find_package(rcutils REQUIRED)
37+
find_package(rmw REQUIRED)
38+
find_package(rosidl_default_generators REQUIRED)
39+
40+
# Generate message
41+
rosidl_generate_interfaces(${PROJECT_NAME}
42+
"msg/KeyedSensorDataMsg.idl"
43+
)
44+
45+
ament_export_dependencies(rosidl_default_runtime)
46+
47+
# Prepare liraries
48+
add_library(filtered_keyed_sensor_component src/filtered_keyed_sensor.cpp)
49+
ament_target_dependencies(filtered_keyed_sensor_component rclcpp rclcpp_components)
50+
51+
add_library(filtered_keyed_controller_component src/filtered_keyed_controller.cpp)
52+
ament_target_dependencies(filtered_keyed_controller_component rclcpp rclcpp_components)
53+
54+
# Declare components
55+
rclcpp_components_register_node(
56+
filtered_keyed_sensor_component
57+
PLUGIN "demo_keys_filtering_cpp::FilteredKeyedSensorNode"
58+
EXECUTABLE filtered_keyed_sensor
59+
)
60+
61+
rclcpp_components_register_node(
62+
filtered_keyed_controller_component
63+
PLUGIN "demo_keys_filtering_cpp::FilteredKeyedControllerNode"
64+
EXECUTABLE filtered_keyed_controller
65+
)
66+
67+
# Attach to cpp generation target
68+
rosidl_get_typesupport_target(cpp_typesupport_target
69+
${PROJECT_NAME} rosidl_typesupport_cpp)
70+
71+
# Link against the generated source files
72+
target_link_libraries(filtered_keyed_sensor_component "${cpp_typesupport_target}")
73+
target_link_libraries(filtered_keyed_controller_component "${cpp_typesupport_target}")
74+
75+
# Install
76+
install(TARGETS
77+
filtered_keyed_sensor_component
78+
filtered_keyed_controller_component
79+
ARCHIVE DESTINATION lib
80+
LIBRARY DESTINATION lib
81+
RUNTIME DESTINATION bin)
82+
83+
# Install launch files.
84+
install(DIRECTORY
85+
launch
86+
DESTINATION share/${PROJECT_NAME}/
87+
)
88+
89+
ament_package()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Fast DDS - ROS 2 Filtered Topic Keys Demo
2+
3+
This project contains code for demonstrating the how-to-use topic keys feature in ROS 2 with Fast DDS.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Copyright 2024 Proyectos y Sistemas de Mantenimiento SL (eProsima).
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from launch import LaunchDescription
16+
from launch_ros.actions import Node
17+
18+
def generate_launch_description():
19+
20+
sensor_nodes = []
21+
22+
for id in range(1, 11):
23+
24+
sensor_node = Node(
25+
package="demo_keys_filtering_cpp",
26+
executable="filtered_keyed_sensor",
27+
parameters=[
28+
{"id": id}
29+
])
30+
31+
sensor_nodes.append(sensor_node)
32+
33+
return LaunchDescription(sensor_nodes)
34+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
int16 sensor_id
2+
float64 measurement
3+
string data

0 commit comments

Comments
 (0)