Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add gravity compensation filter #153

Open
wants to merge 29 commits into
base: ros2-master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
5471301
Added initial files of control filters
destogl Jun 12, 2021
7532c93
Apply suggestions from code review
dzumkeller Sep 30, 2021
d61437a
Added initial files of control filters
destogl Jun 12, 2021
e63a7a1
Update version of gravity compensation to use parameter_handler and o…
destogl Oct 29, 2021
d5a55a7
Apply formatting to filters.
destogl Oct 29, 2021
3817947
Update filters to use full impl of parameter hanlder.
destogl Oct 30, 2021
c046598
Fixed PR115 especially for humble
guihomework Mar 6, 2023
e6d3e27
Switched to generate_parameter_library
guihomework Mar 7, 2023
9cee59e
Reworked and activated control filter loading test
guihomework Mar 9, 2023
5715e8f
Improved parameter_handler usage when re-configure
guihomework Mar 9, 2023
fcb9cca
Applied code-review suggestions and fixed includes
guihomework Mar 11, 2023
b18ffbd
Fixed missing namespace forwarding
guihomework Mar 13, 2023
ebd7481
Switched to wrench transforms
guihomework Mar 15, 2023
b7ffc66
Fail update at failed lookupTransform
guihomework Mar 15, 2023
94ed150
Allow data_out frame of reference selection
guihomework Mar 15, 2023
fc47ee0
Renaming test nodes for consistency
guihomework Mar 15, 2023
c910033
Made computation clearer & for generic const force
guihomework Mar 16, 2023
8fec166
Clarified error msg of GravityCompensation update
guihomework Mar 16, 2023
73072fb
Fixed, simplified & clarified Gravity computations
guihomework Mar 18, 2023
fdf43db
Cleaned-up
guihomework Mar 20, 2023
b74a1ee
Improved understandability of the filter
guihomework Mar 20, 2023
88d6b8a
Switched to yaml-based param in filter test
guihomework Mar 25, 2023
b22a754
Applied suggested formatting and typo fixes
guihomework Apr 3, 2023
f181fe7
Merge branch 'ros2-master' into add-gravity-compensation-filter-rolling
christophfroehlich Apr 18, 2023
819941c
Cleaned up TODO
guihomework May 9, 2023
1c960dd
Fixed build fail in symlink mode
guihomework May 10, 2023
7b59f3c
Merge remote-tracking branch 'upstream/ros2-master' into add-gravity-…
guihomework Aug 4, 2023
5915fe1
Merge branch 'ros2-master' into add-gravity-compensation-filter-rolling
destogl Aug 8, 2023
1e7d9dc
Merge branch 'ros2-master' into add-gravity-compensation-filter-rolling
christophfroehlich Dec 5, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ endif()
set(THIS_PACKAGE_INCLUDE_DEPENDS
control_msgs
rclcpp
rcl_interfaces
rcutils
realtime_tools
)
Expand All @@ -41,6 +42,55 @@ target_include_directories(control_toolbox PUBLIC
ament_target_dependencies(control_toolbox PUBLIC ${THIS_PACKAGE_INCLUDE_DEPENDS})
target_compile_definitions(control_toolbox PRIVATE "CONTROL_TOOLBOX_BUILDING_LIBRARY")

########################
# Control filters
########################
find_package(filters REQUIRED)
find_package(geometry_msgs REQUIRED)
find_package(pluginlib REQUIRED)
find_package(generate_parameter_library REQUIRED)
find_package(rclcpp REQUIRED)
find_package(tf2_geometry_msgs REQUIRED)
find_package(tf2_ros REQUIRED)

set(CONTROL_FILTERS_INCLUDE_DEPENDS
filters
geometry_msgs
pluginlib
generate_parameter_library
rclcpp
tf2_geometry_msgs
tf2
tf2_ros
)

foreach(Dependency IN ITEMS ${CONTROL_FILTERS_INCLUDE_DEPENDS})
find_package(${Dependency} REQUIRED)
endforeach()

generate_parameter_library(
gravity_compensation_filter_parameters
src/control_filters/gravity_compensation_filter_parameters.yaml
)

add_library(gravity_compensation SHARED
src/control_filters/gravity_compensation.cpp
)
target_compile_features(gravity_compensation PUBLIC cxx_std_17)
target_include_directories(gravity_compensation PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include/control_toolbox>
)

target_link_libraries(gravity_compensation PUBLIC gravity_compensation_filter_parameters)
ament_target_dependencies(gravity_compensation PUBLIC ${CONTROL_FILTERS_INCLUDE_DEPENDS})

# Install pluginlib xml
pluginlib_export_plugin_description_file(filters control_filters.xml)

##########
# Testing
##########
if(BUILD_TESTING)
find_package(ament_cmake_gmock REQUIRED)
find_package(ament_cmake_gtest REQUIRED)
Expand All @@ -55,14 +105,32 @@ if(BUILD_TESTING)
ament_add_gtest(pid_publisher_tests test/pid_publisher_tests.cpp)
target_link_libraries(pid_publisher_tests control_toolbox)
ament_target_dependencies(pid_publisher_tests rclcpp_lifecycle)

## Control Filters
add_rostest_with_parameters_gmock(test_gravity_compensation test/control_filters/test_gravity_compensation.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test/control_filters/test_gravity_compensation_parameters.yaml
)
target_link_libraries(test_gravity_compensation gravity_compensation gravity_compensation_filter_parameters)
ament_target_dependencies(test_gravity_compensation ${CONTROL_FILTERS_INCLUDE_DEPENDS})

ament_add_gmock(test_load_gravity_compensation test/control_filters/test_load_gravity_compensation.cpp)
target_link_libraries(test_load_gravity_compensation gravity_compensation gravity_compensation_filter_parameters)
ament_target_dependencies(test_load_gravity_compensation ${CONTROL_FILTERS_INCLUDE_DEPENDS})

endif()

# Install
install(
DIRECTORY include/
DESTINATION include/control_toolbox
)
install(TARGETS control_toolbox
EXPORT export_control_toolbox
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
RUNTIME DESTINATION bin)

install(TARGETS gravity_compensation gravity_compensation_filter_parameters
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
RUNTIME DESTINATION bin
Expand Down
22 changes: 22 additions & 0 deletions README.md
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those changes should be removed.

Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,25 @@ doi = {10.21105/joss.00456},
URL = {http://www.theoj.org/joss-papers/joss.00456/10.21105.joss.00456.pdf}
}
```


## Code Formatting

This repository uses `pre-commit` tool for code formatting.
The tool checks formatting each time you commit to a repository.
To install it locally use:
```
pip3 install pre-commit # (prepend `sudo` if you want to install it system wide)
```

To run it initially over the whole repo you can use:
```
pre-commit run -a
```

If you get error that something is missing on your computer, do the following for:

- `clang-format-14`
```
sudo apt install clang-format-14
```
13 changes: 13 additions & 0 deletions control_filters.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<class_libraries>
<library path="gravity_compensation">
<class name="control_filters/GravityCompensationWrench"
type="control_filters::GravityCompensation&lt;geometry_msgs::msg::WrenchStamped&gt;"
base_class_type="filters::FilterBase&lt;geometry_msgs::msg::WrenchStamped&gt;">
<description>
This is a gravity compensation filter working with geometry_msgs::WrenchStamped.
It subtracts the influence of a force caused by the gravitational force from force
measurements.
</description>
</class>
</library>
</class_libraries>
130 changes: 130 additions & 0 deletions include/control_filters/gravity_compensation.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// Copyright (c) 2023, Stogl Robotics Consulting UG (haftungsbeschränkt)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef CONTROL_FILTERS__GRAVITY_COMPENSATION_HPP_
#define CONTROL_FILTERS__GRAVITY_COMPENSATION_HPP_

#include <memory>
#include <string>
#include <vector>

#include "gravity_compensation_filter_parameters.hpp"
#include "filters/filter_base.hpp"
#include "geometry_msgs/msg/vector3_stamped.hpp"
#include "tf2_ros/buffer.h"
#include "tf2_ros/transform_listener.h"

namespace control_filters
{


template <typename T>
class GravityCompensation : public filters::FilterBase<T>
{
public:
/** \brief Constructor */
GravityCompensation();

/** \brief Destructor */
~GravityCompensation();

/** @brief Configure filter parameters */
bool configure() override;

/** \brief Update the filter and return the data separately
* \param data_in T array with length width
* \param data_out T array with length width
*/
bool update(const T & data_in, T & data_out) override;

protected:
void compute_internal_params()
{
cog_.vector.x = parameters_.CoG.pos[0];
cog_.vector.y = parameters_.CoG.pos[1];
cog_.vector.z = parameters_.CoG.pos[2];
cst_ext_force_.vector.x = parameters_.CoG.force[0];
cst_ext_force_.vector.y = parameters_.CoG.force[1];
cst_ext_force_.vector.z = parameters_.CoG.force[2];
};

private:
rclcpp::Clock::SharedPtr clock_;
std::shared_ptr<rclcpp::Logger> logger_;
std::shared_ptr<gravity_compensation_filter::ParamListener> parameter_handler_;
gravity_compensation_filter::Params parameters_;

// Frames for Transformation of Gravity / CoG Vector
std::string world_frame_; // frame in which gravity is given
std::string sensor_frame_; // frame in which Cog is given and compution occur
// Storage for Calibration Values
geometry_msgs::msg::Vector3Stamped cog_; // Center of Gravity Vector (wrt sensor frame)
geometry_msgs::msg::Vector3Stamped cst_ext_force_; // Gravity Force Vector (wrt world frame)

// Filter objects
std::unique_ptr<tf2_ros::Buffer> p_tf_Buffer_;
std::unique_ptr<tf2_ros::TransformListener> p_tf_Listener_;
geometry_msgs::msg::TransformStamped transform_sensor_datain_, transform_world_dataout_,
transform_data_out_sensor_, transform_sensor_world_;
};

template <typename T>
GravityCompensation<T>::GravityCompensation()
{
}

template <typename T>
GravityCompensation<T>::~GravityCompensation()
{
}

template <typename T>
bool GravityCompensation<T>::configure()
{
clock_ = std::make_shared<rclcpp::Clock>(RCL_SYSTEM_TIME);
p_tf_Buffer_.reset(new tf2_ros::Buffer(clock_));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would it be possible to get somehow clock from the node? This could lead to wrong definition of clocks and thus not getting correct transformations.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless the filter_base is changed, I don't think the filter has any access to the node (only its logger and parameter interfaces). Maybe the filter should get a time and dt passed when updated (as controllers do)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe for a follow-up PR, you could augment the filterbase to also store a clock interface

p_tf_Listener_.reset(new tf2_ros::TransformListener(*p_tf_Buffer_.get(), true));

logger_.reset(
new rclcpp::Logger(this->logging_interface_->get_logger().get_child(this->filter_name_)));

// Initialize the parameter_listener once
if (!parameter_handler_)
{
try
{
parameter_handler_ =
std::make_shared<gravity_compensation_filter::ParamListener>(this->params_interface_,
this->param_prefix_);
}
catch (rclcpp::exceptions::ParameterUninitializedException & ex) {
RCLCPP_ERROR((*logger_), "GravityCompensation filter cannot be configured: %s", ex.what());
parameter_handler_.reset();
return false;
}
catch (rclcpp::exceptions::InvalidParameterValueException & ex) {
RCLCPP_ERROR((*logger_), "GravityCompensation filter cannot be configured: %s", ex.what());
parameter_handler_.reset();
return false;
}
}
parameters_ = parameter_handler_->get_params();
compute_internal_params();

return true;
}

} // namespace control_filters

#endif // CONTROL_FILTERS__GRAVITY_COMPENSATION_HPP_
7 changes: 7 additions & 0 deletions package.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,16 @@
<buildtool_depend>ament_cmake</buildtool_depend>

<depend>control_msgs</depend>
<depend>filters</depend>
<depend>geometry_msgs</depend>
<depend>pluginlib</depend>
<depend>generate_parameter_library</depend>
<depend>rclcpp</depend>
<depend>rcutils</depend>
<depend>realtime_tools</depend>
<depend>tf2</depend>
<depend>tf2_ros</depend>
<depend>tf2_geometry_msgs</depend>

<test_depend>ament_cmake_gmock</test_depend>
<test_depend>ament_cmake_gtest</test_depend>
Expand Down
57 changes: 57 additions & 0 deletions src/control_filters/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Control filters

## Available filters

* Gravity Compensation: implements a gravity compensation algorithm, removing the gravity component from the incoming data (Wrench).

## Gravity compensation filter

This filter implements a gravity compensation algorithm, applied to an `data_in` wrench, computed at a `sensor_frame` in which the center of gravity (CoG) of the to-be-compensated mass is known.

The filter relies on tf2, and might fail if transforms are missing.

Note that, for convenience, the filter can perform additional frame changes if data_out frame id is given.

### Required parameters

* `world_frame` (&Rscr;<sub>w</sub>): frame in which the `CoG.force` is represented.
* `sensor_frame` (&Rscr;<sub>s</sub>): frame in which the `CoG.pos` is defined
* `CoG.pos` (p<sub>s</sub>): position of the CoG of the mass the filter should compensate for
* `CoG.force` (g<sub>w</sub>): constant (but updatable) force of gravity at the Cog (typically m.G), defined along axes of the `world_frame`

### Algorithm

Given
* above-required parameters, &Rscr;<sub>w</sub>, &Rscr;<sub>s</sub>, p<sub>s</sub>, g<sub>w</sub>
* `data_in`, a wrench &Fscr;<sub>i</sub> = {f<sub>i</sub>, &tau;<sub>i</sub>} represented in the `data_in` frame &Rscr;<sub>i</sub>
* access to tf2 homogeneous transforms:
* T<sub>si</sub> from &Rscr;<sub>i</sub> to &Rscr;<sub>s</sub>
* T<sub>sw</sub> from &Rscr;<sub>w</sub> to &Rscr;<sub>s</sub>
* T<sub>os</sub> from &Rscr;<sub>s</sub> to &Rscr;<sub>o</sub>

Compute `data_out` compensated wrench &Fscr;c<sub>o</sub> = {fc<sub>o</sub>, &tau;c<sub>o</sub>} represented in the `data_out` frame &Rscr;<sub>o</sub> if given, or the `data_in` frame &Rscr;<sub>i</sub> otherwise, with equations:

&Fscr;c<sub>o</sub> = T<sub>os</sub>.&Fscr;c<sub>s</sub>,


with &Fscr;c<sub>s</sub> = {fc<sub>s</sub>, &tau;c<sub>s</sub>} the compensated wrench in `sensor_frame` (common frame for computation)

and,

fc<sub>s</sub> = f<sub>s</sub> - T<sub>sw</sub>g<sub>w</sub>

its force and,

&tau;c<sub>s</sub> = &tau;<sub>s</sub> - p<sub>s</sub> x (T<sub>sw</sub>g<sub>w</sub>)

its torque, and

&Fscr;<sub>s</sub> = T<sub>si</sub>.&Fscr;<sub>i</sub> = {f<sub>s</sub>, &tau;<sub>s</sub>}

the full transform of the input wrench &Fscr;<sub>i</sub> to sensor frame &Rscr;<sub>s</sub>

Remarks :
* a full vector is used for gravity force, to not impose gravity to be only along z of `world_frame`.
* `data_in` frame is usually equal to `sensor_frame`, but could be different since measurement of wrench might occur in another frame. E.g.: measurements are at the **FT sensor flange** = `data_in` frame, but CoG is given in **FT sensor base** = `sensor_frame` (=frame to which it is mounted on the robot), introducing an offset (thickness of the sensor) to be accounted for.
* `data_out` frame is usually `data_in` frame, but for convenience, can be set to any other useful frame. E.g.: wrench expressed in a `control_frame` like the center of a gripper.
* T<sub>sw</sub> will only rotate the g<sub>w</sub> vector, because gravity is a field applied everywhere, and not a wrench (no torque should be induced by transforming from &Rscr;<sub>w</sub> to &Rscr;<sub>s</sub>).
Loading