Skip to content

Commit 4f9c462

Browse files
OscarMrZdestoglguihomeworkchristophfroehlichbmagyar
authored
Add filtering capability to ft_broadcaster (#1814)
Co-authored-by: Denis Štogl <[email protected]> Co-authored-by: Guillaume Walck <[email protected]> Co-authored-by: Christoph Froehlich <[email protected]> Co-authored-by: Bence Magyar <[email protected]> Co-authored-by: Christoph Fröhlich <[email protected]> Co-authored-by: Xavier Guay <[email protected]> Co-authored-by: Aarav Gupta <[email protected]> Co-authored-by: Oscar Lima <[email protected]> Co-authored-by: Hilary Luo <[email protected]> Co-authored-by: Sai Kishor Kothakota <[email protected]> Co-authored-by: Sai Kishor Kothakota <[email protected]>
1 parent cb5b183 commit 4f9c462

12 files changed

+356
-58
lines changed

doc/release_notes.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ chained_filter_controller
1111
force_torque_sensor_broadcaster
1212
*******************************
1313
* Multiplier support was added. Users can now specify per–axis scaling factors for both force and torque readings, applied after the existing offset logic. (`#1647 <https://github.com/ros-controls/ros2_controllers/pull/1647/files>`__).
14+
* Added support for filter chains, allowing users to configure a sequence of filter plugins with their parameters. The force/torque sensor readings are filtered sequentially and published on a separate topic.
1415

1516
imu_sensor_broadcaster
1617
*******************************

force_torque_sensor_broadcaster/CMakeLists.txt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export_windows_symbols()
77

88
set(THIS_PACKAGE_INCLUDE_DEPENDS
99
controller_interface
10+
filters
1011
generate_parameter_library
1112
geometry_msgs
1213
hardware_interface
@@ -37,6 +38,7 @@ target_include_directories(force_torque_sensor_broadcaster PUBLIC
3738
target_link_libraries(force_torque_sensor_broadcaster PUBLIC
3839
force_torque_sensor_broadcaster_parameters
3940
controller_interface::controller_interface
41+
filters::filter_chain
4042
hardware_interface::hardware_interface
4143
pluginlib::pluginlib
4244
rclcpp::rclcpp
@@ -70,6 +72,31 @@ if(BUILD_TESTING)
7072
target_link_libraries(test_force_torque_sensor_broadcaster
7173
force_torque_sensor_broadcaster
7274
)
75+
76+
add_library(dummy_filter SHARED
77+
test/dummy_filter.cpp
78+
)
79+
target_compile_features(dummy_filter PUBLIC cxx_std_17)
80+
target_include_directories(dummy_filter PUBLIC
81+
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
82+
$<INSTALL_INTERFACE:include/force_torque_sensor_broadcaster>
83+
)
84+
target_link_libraries(dummy_filter PUBLIC
85+
filters::increment
86+
${geometry_msgs_TARGETS}
87+
pluginlib::pluginlib
88+
rclcpp::rclcpp
89+
)
90+
install(
91+
TARGETS
92+
dummy_filter
93+
EXPORT export_force_torque_sensor_broadcaster
94+
RUNTIME DESTINATION bin
95+
ARCHIVE DESTINATION lib
96+
LIBRARY DESTINATION lib
97+
)
98+
99+
pluginlib_export_plugin_description_file(filters "test/dummy_filter_plugin.xml")
73100
endif()
74101

75102
install(

force_torque_sensor_broadcaster/doc/userdoc.rst

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@ Force Torque Sensor Broadcaster
77
Broadcaster of messages from force/torque state interfaces of a robot or sensor.
88
The published message type is ``geometry_msgs/msg/WrenchStamped``.
99

10-
The controller is a wrapper around ``ForceTorqueSensor`` semantic component (see ``controller_interface`` package).
10+
The broadcaster also supports filtering the force/torque readings using a filter chain, enabling the sequential application of multiple filters. In this case, an additional topic with the
11+
``_filtered`` suffix will be published containing the final result. For more details on filters, refer to the `filters package repository <https://github.com/ros/filters>`_.
12+
See the parameter section for instructions on configuring a filter chain with an arbitrary number of filters.
1113

14+
The controller is a wrapper around ``ForceTorqueSensor`` semantic component (see ``controller_interface`` package).
1215

1316
Parameters
1417
^^^^^^^^^^^
@@ -17,6 +20,12 @@ This controller uses the `generate_parameter_library <https://github.com/PickNik
1720
The interfaces can be defined in two ways, using the ``sensor_name`` or the ``interface_names`` parameter:
1821
Those two parameters cannot be defined at the same time.
1922

23+
The filter chain is configured as a sequential list of filters under the ``sensor_filter_chain`` parameter, where each filter is identified by the key ``filterN``, with ``N`` representing its position in the chain (e.g., ``filter1``, ``filter2``, etc.).
24+
Each filter entry must specify the ``name`` and ``type`` of the plugin, along with any additional parameters required by that specific filter plugin. The chain will use the `pluginlib <https://github.com/ros/pluginlib>`_ library to load each filter at runtime,
25+
passing the specified parameters.
26+
27+
The chain processes the data sequentially, passing the output of one filter as the input to the next.
28+
2029
Full list of parameters:
2130

2231
.. generate_parameter_library_details:: ../src/force_torque_sensor_broadcaster_parameters.yaml

force_torque_sensor_broadcaster/include/force_torque_sensor_broadcaster/force_torque_sensor_broadcaster.hpp

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,23 @@
2020
#define FORCE_TORQUE_SENSOR_BROADCASTER__FORCE_TORQUE_SENSOR_BROADCASTER_HPP_
2121

2222
#include <memory>
23+
#include <string>
2324
#include <vector>
2425

2526
#include "controller_interface/chainable_controller_interface.hpp"
26-
// auto-generated by generate_parameter_library
27-
#include "force_torque_sensor_broadcaster/force_torque_sensor_broadcaster_parameters.hpp"
27+
#include "filters/filter_chain.hpp"
2828
#include "geometry_msgs/msg/wrench_stamped.hpp"
2929
#include "rclcpp_lifecycle/state.hpp"
3030
#include "realtime_tools/realtime_publisher.hpp"
3131
#include "semantic_components/force_torque_sensor.hpp"
3232

33+
// auto-generated by generate_parameter_library
34+
#include "force_torque_sensor_broadcaster/force_torque_sensor_broadcaster_parameters.hpp"
35+
3336
namespace force_torque_sensor_broadcaster
3437
{
38+
using WrenchMsgType = geometry_msgs::msg::WrenchStamped;
39+
3540
class ForceTorqueSensorBroadcaster : public controller_interface::ChainableControllerInterface
3641
{
3742
public:
@@ -67,10 +72,18 @@ class ForceTorqueSensorBroadcaster : public controller_interface::ChainableContr
6772
Params params_;
6873

6974
std::unique_ptr<semantic_components::ForceTorqueSensor> force_torque_sensor_;
70-
71-
using StatePublisher = realtime_tools::RealtimePublisher<geometry_msgs::msg::WrenchStamped>;
72-
rclcpp::Publisher<geometry_msgs::msg::WrenchStamped>::SharedPtr sensor_state_publisher_;
73-
std::unique_ptr<StatePublisher> realtime_publisher_;
75+
bool has_filter_chain_ = false;
76+
77+
WrenchMsgType wrench_raw_;
78+
WrenchMsgType wrench_filtered_;
79+
std::unique_ptr<filters::FilterChain<WrenchMsgType>> filter_chain_;
80+
81+
using StatePublisher = rclcpp::Publisher<WrenchMsgType>::SharedPtr;
82+
using StateRTPublisher = realtime_tools::RealtimePublisher<WrenchMsgType>;
83+
StatePublisher sensor_raw_state_publisher_;
84+
StatePublisher sensor_filtered_state_publisher_;
85+
std::unique_ptr<StateRTPublisher> realtime_raw_publisher_;
86+
std::unique_ptr<StateRTPublisher> realtime_filtered_publisher_;
7487
};
7588

7689
} // namespace force_torque_sensor_broadcaster

force_torque_sensor_broadcaster/package.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
<depend>backward_ros</depend>
2727
<depend>controller_interface</depend>
28+
<depend>filters</depend>
2829
<depend>geometry_msgs</depend>
2930
<depend>hardware_interface</depend>
3031
<depend>pluginlib</depend>

force_torque_sensor_broadcaster/src/force_torque_sensor_broadcaster.cpp

Lines changed: 76 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,12 @@
1818

1919
#include "force_torque_sensor_broadcaster/force_torque_sensor_broadcaster.hpp"
2020

21+
#include <limits>
2122
#include <memory>
2223
#include <string>
2324

25+
#include "rclcpp/logging.hpp"
26+
2427
namespace force_torque_sensor_broadcaster
2528
{
2629
ForceTorqueSensorBroadcaster::ForceTorqueSensorBroadcaster()
@@ -87,12 +90,43 @@ controller_interface::CallbackReturn ForceTorqueSensorBroadcaster::on_configure(
8790
torque_names.z));
8891
}
8992

93+
try
94+
{
95+
filter_chain_ =
96+
std::make_unique<filters::FilterChain<WrenchMsgType>>("geometry_msgs::msg::WrenchStamped");
97+
}
98+
catch (const std::exception & e)
99+
{
100+
fprintf(stderr, "Exception thrown during filter chain creation with message : %s \n", e.what());
101+
return CallbackReturn::ERROR;
102+
}
103+
104+
// As the sensor_filter_chain parameter is of type 'none', we cannot directly check if it is
105+
// present. Even if the sensor_filter_chain parameter is not specified, the filter chain will be
106+
// correctly configured with an empty list of filters.
107+
bool filter_chain_configured = filter_chain_->configure(
108+
"sensor_filter_chain", get_node()->get_node_logging_interface(),
109+
get_node()->get_node_parameters_interface());
110+
111+
// Even on successful configure, if empty, the chain won't be used
112+
has_filter_chain_ = filter_chain_configured && filter_chain_->get_length() > 0;
113+
114+
RCLCPP_INFO_EXPRESSION(
115+
get_node()->get_logger(), has_filter_chain_, "Filter active with %zu filters!",
116+
filter_chain_->get_length());
117+
90118
try
91119
{
92120
// register ft sensor data publisher
93-
sensor_state_publisher_ = get_node()->create_publisher<geometry_msgs::msg::WrenchStamped>(
121+
sensor_raw_state_publisher_ = get_node()->create_publisher<geometry_msgs::msg::WrenchStamped>(
94122
"~/wrench", rclcpp::SystemDefaultsQoS());
95-
realtime_publisher_ = std::make_unique<StatePublisher>(sensor_state_publisher_);
123+
realtime_raw_publisher_ = std::make_unique<StateRTPublisher>(sensor_raw_state_publisher_);
124+
125+
sensor_filtered_state_publisher_ =
126+
get_node()->create_publisher<geometry_msgs::msg::WrenchStamped>(
127+
"~/wrench_filtered", rclcpp::SystemDefaultsQoS());
128+
realtime_filtered_publisher_ =
129+
std::make_unique<StateRTPublisher>(sensor_filtered_state_publisher_);
96130
}
97131
catch (const std::exception & e)
98132
{
@@ -102,9 +136,19 @@ controller_interface::CallbackReturn ForceTorqueSensorBroadcaster::on_configure(
102136
return controller_interface::CallbackReturn::ERROR;
103137
}
104138

105-
realtime_publisher_->lock();
106-
realtime_publisher_->msg_.header.frame_id = params_.frame_id;
107-
realtime_publisher_->unlock();
139+
wrench_raw_.header.frame_id = params_.frame_id;
140+
wrench_filtered_.header.frame_id = params_.frame_id;
141+
142+
realtime_raw_publisher_->lock();
143+
realtime_raw_publisher_->msg_.header.frame_id = params_.frame_id;
144+
realtime_raw_publisher_->unlock();
145+
146+
if (has_filter_chain_)
147+
{
148+
realtime_filtered_publisher_->lock();
149+
realtime_filtered_publisher_->msg_.header.frame_id = params_.frame_id;
150+
realtime_filtered_publisher_->unlock();
151+
}
108152

109153
RCLCPP_INFO(get_node()->get_logger(), "configure successful");
110154
return controller_interface::CallbackReturn::SUCCESS;
@@ -146,13 +190,28 @@ controller_interface::return_type ForceTorqueSensorBroadcaster::update_and_write
146190
{
147191
param_listener_->try_get_params(params_);
148192

149-
if (realtime_publisher_ && realtime_publisher_->trylock())
193+
wrench_raw_.header.stamp = time;
194+
force_torque_sensor_->get_values_as_message(wrench_raw_.wrench);
195+
this->apply_sensor_offset(params_, wrench_raw_);
196+
this->apply_sensor_multiplier(params_, wrench_raw_);
197+
198+
if (realtime_raw_publisher_ && realtime_raw_publisher_->trylock())
199+
{
200+
realtime_raw_publisher_->msg_.header.stamp = time;
201+
realtime_raw_publisher_->msg_.wrench = wrench_raw_.wrench;
202+
realtime_raw_publisher_->unlockAndPublish();
203+
}
204+
205+
if (has_filter_chain_)
150206
{
151-
realtime_publisher_->msg_.header.stamp = time;
152-
force_torque_sensor_->get_values_as_message(realtime_publisher_->msg_.wrench);
153-
this->apply_sensor_offset(params_, realtime_publisher_->msg_);
154-
this->apply_sensor_multiplier(params_, realtime_publisher_->msg_);
155-
realtime_publisher_->unlockAndPublish();
207+
// Filter sensor data, if no filter chain config was specified, wrench_filtered_ = wrench_raw_
208+
auto filtered = filter_chain_->update(wrench_raw_, wrench_filtered_);
209+
if (filtered && realtime_filtered_publisher_ && realtime_filtered_publisher_->trylock())
210+
{
211+
realtime_filtered_publisher_->msg_.header.stamp = time;
212+
realtime_filtered_publisher_->msg_.wrench = wrench_filtered_.wrench;
213+
realtime_filtered_publisher_->unlockAndPublish();
214+
}
156215
}
157216

158217
return controller_interface::return_type::OK;
@@ -198,38 +257,32 @@ ForceTorqueSensorBroadcaster::on_export_state_interfaces()
198257
if (!force_names[0].empty())
199258
{
200259
exported_state_interfaces.emplace_back(
201-
hardware_interface::StateInterface(
202-
export_prefix, force_names[0], &realtime_publisher_->msg_.wrench.force.x));
260+
export_prefix, force_names[0], &realtime_raw_publisher_->msg_.wrench.force.x);
203261
}
204262
if (!force_names[1].empty())
205263
{
206264
exported_state_interfaces.emplace_back(
207-
hardware_interface::StateInterface(
208-
export_prefix, force_names[1], &realtime_publisher_->msg_.wrench.force.y));
265+
export_prefix, force_names[1], &realtime_raw_publisher_->msg_.wrench.force.y);
209266
}
210267
if (!force_names[2].empty())
211268
{
212269
exported_state_interfaces.emplace_back(
213-
hardware_interface::StateInterface(
214-
export_prefix, force_names[2], &realtime_publisher_->msg_.wrench.force.z));
270+
export_prefix, force_names[2], &realtime_raw_publisher_->msg_.wrench.force.z);
215271
}
216272
if (!torque_names[0].empty())
217273
{
218274
exported_state_interfaces.emplace_back(
219-
hardware_interface::StateInterface(
220-
export_prefix, torque_names[0], &realtime_publisher_->msg_.wrench.torque.x));
275+
export_prefix, torque_names[0], &realtime_raw_publisher_->msg_.wrench.torque.x);
221276
}
222277
if (!torque_names[1].empty())
223278
{
224279
exported_state_interfaces.emplace_back(
225-
hardware_interface::StateInterface(
226-
export_prefix, torque_names[1], &realtime_publisher_->msg_.wrench.torque.y));
280+
export_prefix, torque_names[1], &realtime_raw_publisher_->msg_.wrench.torque.y);
227281
}
228282
if (!torque_names[2].empty())
229283
{
230284
exported_state_interfaces.emplace_back(
231-
hardware_interface::StateInterface(
232-
export_prefix, torque_names[2], &realtime_publisher_->msg_.wrench.torque.z));
285+
export_prefix, torque_names[2], &realtime_raw_publisher_->msg_.wrench.torque.z);
233286
}
234287
return exported_state_interfaces;
235288
}

force_torque_sensor_broadcaster/src/force_torque_sensor_broadcaster_parameters.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,3 +112,10 @@ force_torque_sensor_broadcaster:
112112
default_value: 1.0,
113113
description: "The multiplier of torque value around 'z' axis.",
114114
}
115+
sensor_filter_chain: {
116+
type: none,
117+
description: "Map of parameters that defines a filter chain, containing filterN as key. The fields for each filter are:
118+
type: The filter plugin to be loaded
119+
name: Actual name semantically describing the filter, e.g., low_pass_filter
120+
params: And underlying map of parameters needed for a specific filter, refer to the specific filter documentation."
121+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
2+
// Copyright (c) 2025, PAL Robotics
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
/*
17+
* Authors: Oscar Martinez
18+
*/
19+
20+
#include <rclcpp/logger.hpp>
21+
#include <rclcpp/logging.hpp>
22+
23+
#include "geometry_msgs/msg/wrench_stamped.hpp"
24+
25+
#include "filters/increment.hpp"
26+
27+
namespace filters
28+
{
29+
30+
template <>
31+
bool IncrementFilter<geometry_msgs::msg::WrenchStamped>::update(
32+
const geometry_msgs::msg::WrenchStamped & data_in, geometry_msgs::msg::WrenchStamped & data_out)
33+
{
34+
if (!this->configured_)
35+
{
36+
throw std::runtime_error("Filter is not configured");
37+
}
38+
39+
// Just increment every value
40+
data_out.wrench.force.x = data_in.wrench.force.x + 1;
41+
data_out.wrench.force.y = data_in.wrench.force.y + 1;
42+
data_out.wrench.force.z = data_in.wrench.force.z + 1;
43+
data_out.wrench.torque.x = data_in.wrench.torque.x + 1;
44+
data_out.wrench.torque.y = data_in.wrench.torque.y + 1;
45+
data_out.wrench.torque.z = data_in.wrench.torque.z + 1;
46+
47+
return true;
48+
}
49+
50+
} // namespace filters
51+
52+
#include "pluginlib/class_list_macros.hpp"
53+
54+
PLUGINLIB_EXPORT_CLASS(
55+
filters::IncrementFilter<geometry_msgs::msg::WrenchStamped>,
56+
filters::FilterBase<geometry_msgs::msg::WrenchStamped>)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<class_libraries>
2+
<library path="dummy_filter">
3+
<class name="filters/IncrementFilterWrench" type="filters::IncrementFilter&lt;geometry_msgs::msg::WrenchStamped&gt;" base_class_type="filters::FilterBase&lt;geometry_msgs::msg::WrenchStamped&gt;">
4+
<description>
5+
This is a increment filter which works on a wrench message.
6+
</description>
7+
</class>
8+
</library>
9+
</class_libraries>
Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
11
test_force_torque_sensor_broadcaster:
22
ros__parameters:
3-
43
frame_id: "fts_sensor_frame"
4+
test_force_torque_sensor_broadcaster_with_chain:
5+
ros__parameters:
6+
frame_id: "fts_sensor_frame"
7+
sensor_name: "fts_sensor"
8+
sensor_filter_chain:
9+
filter1:
10+
name: dummy
11+
type: filters/IncrementFilterWrench

0 commit comments

Comments
 (0)