Skip to content

Commit 03fa731

Browse files
author
Chen Lihui
authored
add content-filtered-topic interfaces (#1561)
* to support a feature of content filtered topic Signed-off-by: Chen Lihui <[email protected]> * update for checking memory allocated Signed-off-by: Chen Lihui <[email protected]> * set expression parameter only if filter is valid Signed-off-by: Chen Lihui <[email protected]> * add test Signed-off-by: Chen Lihui <[email protected]> * use a default empty value for expresion parameters Signed-off-by: Chen Lihui <[email protected]> * remove std_msgs dependency Signed-off-by: Chen Lihui <[email protected]> * use new rcl interface Signed-off-by: Chen Lihui <[email protected]> * remove unused include header files and fix unscrutify Signed-off-by: Chen Lihui <[email protected]> * update comment Signed-off-by: Chen Lihui <[email protected]> * update comment Signed-off-by: Chen Lihui <[email protected]> * rename Signed-off-by: Chen Lihui <[email protected]> * refactor test Signed-off-by: Chen Lihui <[email protected]> * relate to `rcutils_string_array_t expression_parameters` changed in rmw Signed-off-by: Chen Lihui <[email protected]> * remove the implementation temporary, add them with fallback in the feature 1. add DEFINE_CONTENT_FILTER with default(ON) to build content filter interfaces 2. user need a compile option(CONTENT_FILTER_ENABLE) to turn on the feature to set the filter or call the interfaces Signed-off-by: Chen Lihui <[email protected]> * update comments and check the option to build content filter test Signed-off-by: Chen Lihui <[email protected]> * add DDS content filter implementation Signed-off-by: Chen Lihui <[email protected]> * address review Signed-off-by: Chen Lihui <[email protected]> * rcl api changed Signed-off-by: Chen Lihui <[email protected]> * increase the filter propagation time Signed-off-by: Chen Lihui <[email protected]> * add rclcpp::ContentFilterOptions and use it as the return type of get_content_filter Signed-off-by: Chen Lihui <[email protected]> * increase the maximun time to wait message event and content filter propagation Signed-off-by: Chen Lihui <[email protected]> * cft member should be defined in the structure. to check the macro for interfaces is enough. Signed-off-by: Chen Lihui <[email protected]> * set test timeout to 120 Signed-off-by: Chen Lihui <[email protected]> * update doc Signed-off-by: Chen Lihui <[email protected]>
1 parent 0699aeb commit 03fa731

File tree

9 files changed

+545
-0
lines changed

9 files changed

+545
-0
lines changed

rclcpp/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@ cmake_minimum_required(VERSION 3.12)
22

33
project(rclcpp)
44

5+
option(DEFINE_CONTENT_FILTER "Content filter feature support" ON)
6+
if(DEFINE_CONTENT_FILTER)
7+
add_definitions(-DCONTENT_FILTER_ENABLE)
8+
endif()
9+
510
find_package(Threads REQUIRED)
611

712
find_package(ament_cmake_ros REQUIRED)

rclcpp/include/rclcpp/subscription_base.hpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
#include "rclcpp/qos.hpp"
4040
#include "rclcpp/qos_event.hpp"
4141
#include "rclcpp/serialized_message.hpp"
42+
#include "rclcpp/subscription_content_filter_options.hpp"
4243
#include "rclcpp/type_support_decl.hpp"
4344
#include "rclcpp/visibility_control.hpp"
4445

@@ -491,6 +492,42 @@ class SubscriptionBase : public std::enable_shared_from_this<SubscriptionBase>
491492
event_handlers_[event_type]->clear_on_ready_callback();
492493
}
493494

495+
#ifdef CONTENT_FILTER_ENABLE
496+
/// Check if content filtered topic feature of the subscription instance is enabled.
497+
/**
498+
* \return boolean flag indicating if the content filtered topic of this subscription is enabled.
499+
*/
500+
RCLCPP_PUBLIC
501+
bool
502+
is_cft_enabled() const;
503+
504+
/// Set the filter expression and expression parameters for the subscription.
505+
/**
506+
* \param[in] filter_expression A filter expression to set.
507+
* \sa ContentFilterOptions::filter_expression
508+
* An empty string ("") will clear the content filter setting of the subscription.
509+
* \param[in] expression_parameters Array of expression parameters to set.
510+
* \sa ContentFilterOptions::expression_parameters
511+
* \throws RCLBadAlloc if memory cannot be allocated
512+
* \throws RCLError if an unexpect error occurs
513+
*/
514+
RCLCPP_PUBLIC
515+
void
516+
set_content_filter(
517+
const std::string & filter_expression,
518+
const std::vector<std::string> & expression_parameters = {});
519+
520+
/// Get the filter expression and expression parameters for the subscription.
521+
/**
522+
* \return rclcpp::ContentFilterOptions The content filter options to get.
523+
* \throws RCLBadAlloc if memory cannot be allocated
524+
* \throws RCLError if an unexpect error occurs
525+
*/
526+
RCLCPP_PUBLIC
527+
rclcpp::ContentFilterOptions
528+
get_content_filter() const;
529+
#endif // CONTENT_FILTER_ENABLE
530+
494531
protected:
495532
template<typename EventCallbackT>
496533
void
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright 2022 Open Source Robotics Foundation, Inc.
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+
#ifndef RCLCPP__SUBSCRIPTION_CONTENT_FILTER_OPTIONS_HPP_
16+
#define RCLCPP__SUBSCRIPTION_CONTENT_FILTER_OPTIONS_HPP_
17+
18+
#include <string>
19+
#include <vector>
20+
21+
namespace rclcpp
22+
{
23+
24+
/// Options to configure content filtered topic in the subscription.
25+
struct ContentFilterOptions
26+
{
27+
/// Filter expression is similar to the WHERE part of an SQL clause.
28+
std::string filter_expression;
29+
/**
30+
* Expression parameters is the tokens placeholder ‘parameters’ (i.e., "%n" tokens begin from 0)
31+
* in the filter_expression. The maximum expression_parameters size is 100.
32+
*/
33+
std::vector<std::string> expression_parameters;
34+
};
35+
36+
} // namespace rclcpp
37+
38+
#endif // RCLCPP__SUBSCRIPTION_CONTENT_FILTER_OPTIONS_HPP_

rclcpp/include/rclcpp/subscription_options.hpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include "rclcpp/qos.hpp"
2929
#include "rclcpp/qos_event.hpp"
3030
#include "rclcpp/qos_overriding_options.hpp"
31+
#include "rclcpp/subscription_content_filter_options.hpp"
3132
#include "rclcpp/topic_statistics_state.hpp"
3233
#include "rclcpp/visibility_control.hpp"
3334

@@ -81,6 +82,8 @@ struct SubscriptionOptionsBase
8182
TopicStatisticsOptions topic_stats_options;
8283

8384
QosOverridingOptions qos_overriding_options;
85+
86+
ContentFilterOptions content_filter_options;
8487
};
8588

8689
/// Structure containing optional configuration for Subscriptions.
@@ -123,6 +126,22 @@ struct SubscriptionOptionsWithAllocator : public SubscriptionOptionsBase
123126
rmw_implementation_payload->modify_rmw_subscription_options(result.rmw_subscription_options);
124127
}
125128

129+
#ifdef CONTENT_FILTER_ENABLE
130+
// Copy content_filter_options into rcl_subscription_options.
131+
if (!content_filter_options.filter_expression.empty()) {
132+
std::vector<const char *> cstrings =
133+
get_c_vector_string(content_filter_options.expression_parameters);
134+
rcl_ret_t ret = rcl_subscription_options_set_content_filter_options(
135+
get_c_string(content_filter_options.filter_expression),
136+
cstrings.size(),
137+
cstrings.data(),
138+
&result);
139+
if (RCL_RET_OK != ret) {
140+
rclcpp::exceptions::throw_from_rcl_error(
141+
ret, "failed to set content_filter_options");
142+
}
143+
}
144+
#endif // CONTENT_FILTER_ENABLE
126145
return result;
127146
}
128147

rclcpp/include/rclcpp/utilities.hpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,15 @@ RCLCPP_PUBLIC
321321
const char *
322322
get_c_string(const std::string & string_in);
323323

324+
/// Return the std::vector of C string from the given std::vector<std::string>.
325+
/**
326+
* \param[in] strings_in is a std::vector of std::string
327+
* \return the std::vector of C string from the std::vector<std::string>
328+
*/
329+
RCLCPP_PUBLIC
330+
std::vector<const char *>
331+
get_c_vector_string(const std::vector<std::string> & strings_in);
332+
324333
} // namespace rclcpp
325334

326335
#endif // RCLCPP__UTILITIES_HPP_

rclcpp/src/rclcpp/subscription_base.cpp

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
#include <unordered_map>
2121
#include <vector>
2222

23+
#include "rcpputils/scope_exit.hpp"
24+
2325
#include "rclcpp/exceptions.hpp"
2426
#include "rclcpp/expand_topic_or_service_name.hpp"
2527
#include "rclcpp/experimental/intra_process_manager.hpp"
@@ -354,3 +356,93 @@ SubscriptionBase::set_on_new_message_callback(
354356
throw_from_rcl_error(ret, "failed to set the on new message callback for subscription");
355357
}
356358
}
359+
360+
#ifdef CONTENT_FILTER_ENABLE
361+
bool
362+
SubscriptionBase::is_cft_enabled() const
363+
{
364+
return rcl_subscription_is_cft_enabled(subscription_handle_.get());
365+
}
366+
367+
void
368+
SubscriptionBase::set_content_filter(
369+
const std::string & filter_expression,
370+
const std::vector<std::string> & expression_parameters)
371+
{
372+
rcl_subscription_content_filter_options_t options =
373+
rcl_get_zero_initialized_subscription_content_filter_options();
374+
375+
std::vector<const char *> cstrings =
376+
get_c_vector_string(expression_parameters);
377+
rcl_ret_t ret = rcl_subscription_content_filter_options_init(
378+
subscription_handle_.get(),
379+
get_c_string(filter_expression),
380+
cstrings.size(),
381+
cstrings.data(),
382+
&options);
383+
if (RCL_RET_OK != ret) {
384+
rclcpp::exceptions::throw_from_rcl_error(
385+
ret, "failed to init subscription content_filtered_topic option");
386+
}
387+
RCPPUTILS_SCOPE_EXIT(
388+
{
389+
rcl_ret_t ret = rcl_subscription_content_filter_options_fini(
390+
subscription_handle_.get(), &options);
391+
if (RCL_RET_OK != ret) {
392+
RCLCPP_ERROR(
393+
rclcpp::get_logger("rclcpp"),
394+
"Failed to fini subscription content_filtered_topic option: %s",
395+
rcl_get_error_string().str);
396+
rcl_reset_error();
397+
}
398+
});
399+
400+
ret = rcl_subscription_set_content_filter(
401+
subscription_handle_.get(),
402+
&options);
403+
404+
if (RCL_RET_OK != ret) {
405+
rclcpp::exceptions::throw_from_rcl_error(ret, "failed to set cft expression parameters");
406+
}
407+
}
408+
409+
rclcpp::ContentFilterOptions
410+
SubscriptionBase::get_content_filter() const
411+
{
412+
rclcpp::ContentFilterOptions ret_options;
413+
rcl_subscription_content_filter_options_t options =
414+
rcl_get_zero_initialized_subscription_content_filter_options();
415+
416+
rcl_ret_t ret = rcl_subscription_get_content_filter(
417+
subscription_handle_.get(),
418+
&options);
419+
420+
if (RCL_RET_OK != ret) {
421+
rclcpp::exceptions::throw_from_rcl_error(ret, "failed to get cft expression parameters");
422+
}
423+
424+
RCPPUTILS_SCOPE_EXIT(
425+
{
426+
rcl_ret_t ret = rcl_subscription_content_filter_options_fini(
427+
subscription_handle_.get(), &options);
428+
if (RCL_RET_OK != ret) {
429+
RCLCPP_ERROR(
430+
rclcpp::get_logger("rclcpp"),
431+
"Failed to fini subscription content_filtered_topic option: %s",
432+
rcl_get_error_string().str);
433+
rcl_reset_error();
434+
}
435+
});
436+
437+
rmw_subscription_content_filter_options_t & content_filter_options =
438+
options.rmw_subscription_content_filter_options;
439+
ret_options.filter_expression = content_filter_options.filter_expression;
440+
441+
for (size_t i = 0; i < content_filter_options.expression_parameters.size; ++i) {
442+
ret_options.expression_parameters.push_back(
443+
content_filter_options.expression_parameters.data[i]);
444+
}
445+
446+
return ret_options;
447+
}
448+
#endif // CONTENT_FILTER_ENABLE

rclcpp/src/rclcpp/utilities.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,4 +214,17 @@ get_c_string(const std::string & string_in)
214214
return string_in.c_str();
215215
}
216216

217+
std::vector<const char *>
218+
get_c_vector_string(const std::vector<std::string> & strings_in)
219+
{
220+
std::vector<const char *> cstrings;
221+
cstrings.reserve(strings_in.size());
222+
223+
for (size_t i = 0; i < strings_in.size(); ++i) {
224+
cstrings.push_back(strings_in[i].c_str());
225+
}
226+
227+
return cstrings;
228+
}
229+
217230
} // namespace rclcpp

rclcpp/test/rclcpp/CMakeLists.txt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -733,3 +733,23 @@ ament_add_gtest(test_graph_listener test_graph_listener.cpp)
733733
if(TARGET test_graph_listener)
734734
target_link_libraries(test_graph_listener ${PROJECT_NAME} mimick)
735735
endif()
736+
737+
if(DEFINE_CONTENT_FILTER)
738+
function(test_subscription_content_filter_for_rmw_implementation)
739+
set(rmw_implementation_env_var RMW_IMPLEMENTATION=${rmw_implementation})
740+
ament_add_gmock(test_subscription_content_filter${target_suffix}
741+
test_subscription_content_filter.cpp
742+
ENV ${rmw_implementation_env_var}
743+
TIMEOUT 120
744+
)
745+
if(TARGET test_subscription_content_filter${target_suffix})
746+
target_link_libraries(test_subscription_content_filter${target_suffix} ${PROJECT_NAME} mimick)
747+
ament_target_dependencies(test_subscription_content_filter${target_suffix}
748+
"rcpputils"
749+
"rosidl_typesupport_cpp"
750+
"test_msgs"
751+
)
752+
endif()
753+
endfunction()
754+
call_for_each_rmw_implementation(test_subscription_content_filter_for_rmw_implementation)
755+
endif()

0 commit comments

Comments
 (0)