Skip to content

Commit f920e6d

Browse files
Add calibration possibility to IMU broadcaster (ros-controls#1833)
1 parent 3a3e69f commit f920e6d

12 files changed

+543
-26
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ repos:
134134
rev: v2.4.1
135135
hooks:
136136
- id: codespell
137-
args: ['--write-changes', '--uri-ignore-words-list=ist', '-L manuel']
137+
args: ['--write-changes', '--uri-ignore-words-list=ist', '-L manuel,NED']
138138
exclude: CHANGELOG\.rst|\.(svg|pyc|drawio)$
139139

140140
- repo: https://github.com/python-jsonschema/check-jsonschema

doc/release_notes.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ 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>`__).
1414

15+
imu_sensor_broadcaster
16+
*******************************
17+
* IMU sensor broadcaster is now a chainable controller. It supports a calibration by means of a rotation, defined as euler angles, to its target frame. (`#1833 <https://github.com/ros-controls/ros2_controllers/pull/1833/files>`__).
18+
1519
joint_trajectory_controller
1620
*******************************
1721
* The controller now supports the new anti-windup strategy of the PID class, which allows for more flexible control of the anti-windup behavior. (`#1759 <https://github.com/ros-controls/ros2_controllers/pull/1759>`__).

imu_sensor_broadcaster/CMakeLists.txt

Lines changed: 13 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+
Eigen3
1011
generate_parameter_library
1112
hardware_interface
1213
pluginlib
@@ -37,6 +38,7 @@ target_include_directories(imu_sensor_broadcaster PUBLIC
3738
target_link_libraries(imu_sensor_broadcaster PUBLIC
3839
imu_sensor_broadcaster_parameters
3940
controller_interface::controller_interface
41+
Eigen3::Eigen
4042
hardware_interface::hardware_interface
4143
pluginlib::pluginlib
4244
rclcpp::rclcpp
@@ -68,6 +70,17 @@ if(BUILD_TESTING)
6870
target_include_directories(test_imu_sensor_broadcaster PRIVATE include)
6971
target_link_libraries(test_imu_sensor_broadcaster
7072
imu_sensor_broadcaster
73+
tf2::tf2
74+
tf2_geometry_msgs::tf2_geometry_msgs
75+
)
76+
77+
find_package(tf2 REQUIRED)
78+
find_package(tf2_geometry_msgs REQUIRED)
79+
ament_add_gmock(test_imu_transform test/test_imu_transform.cpp)
80+
target_link_libraries(test_imu_transform
81+
imu_sensor_broadcaster
82+
tf2::tf2
83+
tf2_geometry_msgs::tf2_geometry_msgs
7184
)
7285
endif()
7386

imu_sensor_broadcaster/imu_sensor_broadcaster.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<library path="imu_sensor_broadcaster">
22
<class name="imu_sensor_broadcaster/IMUSensorBroadcaster"
3-
type="imu_sensor_broadcaster::IMUSensorBroadcaster" base_class_type="controller_interface::ControllerInterface">
3+
type="imu_sensor_broadcaster::IMUSensorBroadcaster" base_class_type="controller_interface::ChainableControllerInterface">
44
<description>
55
This controller publishes the readings of an IMU sensor as sensor_msgs/Imu message.
66
</description>

imu_sensor_broadcaster/include/imu_sensor_broadcaster/imu_sensor_broadcaster.hpp

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,21 @@
2020
#define IMU_SENSOR_BROADCASTER__IMU_SENSOR_BROADCASTER_HPP_
2121

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

25+
#include "Eigen/Dense"
2426
#include "rclcpp_lifecycle/state.hpp"
2527
#include "realtime_tools/realtime_publisher.hpp"
2628
#include "semantic_components/imu_sensor.hpp"
2729
#include "sensor_msgs/msg/imu.hpp"
2830

29-
#include "controller_interface/controller_interface.hpp"
31+
#include "controller_interface/chainable_controller_interface.hpp"
3032
// auto-generated by generate_parameter_library
3133
#include "imu_sensor_broadcaster/imu_sensor_broadcaster_parameters.hpp"
3234

3335
namespace imu_sensor_broadcaster
3436
{
35-
class IMUSensorBroadcaster : public controller_interface::ControllerInterface
37+
class IMUSensorBroadcaster : public controller_interface::ChainableControllerInterface
3638
{
3739
public:
3840
controller_interface::InterfaceConfiguration command_interface_configuration() const override;
@@ -49,18 +51,25 @@ class IMUSensorBroadcaster : public controller_interface::ControllerInterface
4951
controller_interface::CallbackReturn on_deactivate(
5052
const rclcpp_lifecycle::State & previous_state) override;
5153

52-
controller_interface::return_type update(
54+
controller_interface::return_type update_and_write_commands(
5355
const rclcpp::Time & time, const rclcpp::Duration & period) override;
5456

57+
controller_interface::return_type update_reference_from_subscribers(
58+
const rclcpp::Time & time, const rclcpp::Duration & period) override;
59+
60+
std::vector<hardware_interface::StateInterface> on_export_state_interfaces() override;
61+
5562
protected:
5663
std::shared_ptr<ParamListener> param_listener_;
5764
Params params_;
65+
Eigen::Quaterniond r_;
5866

5967
std::unique_ptr<semantic_components::IMUSensor> imu_sensor_;
6068

6169
using StatePublisher = realtime_tools::RealtimePublisher<sensor_msgs::msg::Imu>;
6270
rclcpp::Publisher<sensor_msgs::msg::Imu>::SharedPtr sensor_state_publisher_;
6371
std::unique_ptr<StatePublisher> realtime_publisher_;
72+
sensor_msgs::msg::Imu state_message_;
6473
};
6574

6675
} // namespace imu_sensor_broadcaster
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// Copyright 2025 AIT Austrian Institute of Technology GmbH
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+
/*
16+
* Authors: Christoph Froehlich
17+
* Adapted from
18+
* https://github.com/ros-perception/imu_pipeline/blob/jazzy/imu_transformer/include/imu_transformer/tf2_sensor_msgs.h
19+
*/
20+
21+
#ifndef IMU_SENSOR_BROADCASTER__IMU_TRANSFORM_HPP_
22+
#define IMU_SENSOR_BROADCASTER__IMU_TRANSFORM_HPP_
23+
24+
#include <Eigen/Dense>
25+
#include "sensor_msgs/msg/imu.hpp"
26+
27+
namespace imu_sensor_broadcaster
28+
{
29+
/**
30+
* @brief Euler to quaternion Z-Y'-X'' convention
31+
* https://eigen.tuxfamily.org/dox-devel/group__TutorialGeometry.html
32+
*/
33+
Eigen::Quaterniond quat_from_euler(double roll, double pitch, double yaw)
34+
{
35+
return Eigen::Quaterniond(
36+
Eigen::AngleAxisd(yaw, Eigen::Vector3d::UnitZ()) *
37+
Eigen::AngleAxisd(pitch, Eigen::Vector3d::UnitY()) *
38+
Eigen::AngleAxisd(roll, Eigen::Vector3d::UnitX()));
39+
}
40+
41+
/**
42+
* @brief Transforms a covariance array from one frame to another
43+
*/
44+
inline void transform_covariance(
45+
std::array<double, 9> & out, const std::array<double, 9> & in, Eigen::Quaterniond r)
46+
{
47+
Eigen::Map<const Eigen::Matrix<double, 3, 3, Eigen::RowMajor> > cov_in(in.data());
48+
Eigen::Map<Eigen::Matrix<double, 3, 3, Eigen::RowMajor> > cov_out(out.data());
49+
cov_out = r * cov_in * r.inverse();
50+
}
51+
52+
/**
53+
* @brief Transforms sensor_msgs::Imu data from one frame to another
54+
*/
55+
inline void do_transform(
56+
sensor_msgs::msg::Imu & imu_out, const sensor_msgs::msg::Imu & imu_in, const Eigen::Quaterniond r)
57+
{
58+
imu_out.header = imu_in.header;
59+
60+
Eigen::Vector3d vel =
61+
r * Eigen::Vector3d(
62+
imu_in.angular_velocity.x, imu_in.angular_velocity.y, imu_in.angular_velocity.z);
63+
64+
imu_out.angular_velocity.x = vel.x();
65+
imu_out.angular_velocity.y = vel.y();
66+
imu_out.angular_velocity.z = vel.z();
67+
68+
transform_covariance(imu_out.angular_velocity_covariance, imu_in.angular_velocity_covariance, r);
69+
70+
Eigen::Vector3d accel =
71+
r * Eigen::Vector3d(
72+
imu_in.linear_acceleration.x, imu_in.linear_acceleration.y, imu_in.linear_acceleration.z);
73+
74+
imu_out.linear_acceleration.x = accel.x();
75+
imu_out.linear_acceleration.y = accel.y();
76+
imu_out.linear_acceleration.z = accel.z();
77+
78+
transform_covariance(
79+
imu_out.linear_acceleration_covariance, imu_in.linear_acceleration_covariance, r);
80+
81+
// Orientation expresses attitude of the new frame_id in a fixed world frame. This is why the
82+
// transform here applies in the opposite direction.
83+
Eigen::Quaterniond orientation =
84+
Eigen::Quaterniond(
85+
imu_in.orientation.w, imu_in.orientation.x, imu_in.orientation.y, imu_in.orientation.z) *
86+
r.inverse();
87+
88+
imu_out.orientation.w = orientation.w();
89+
imu_out.orientation.x = orientation.x();
90+
imu_out.orientation.y = orientation.y();
91+
imu_out.orientation.z = orientation.z();
92+
93+
// Orientation is measured relative to the fixed world frame, so it doesn't change when applying a
94+
// static transform to the sensor frame.
95+
imu_out.orientation_covariance = imu_in.orientation_covariance;
96+
}
97+
} // namespace imu_sensor_broadcaster
98+
#endif // IMU_SENSOR_BROADCASTER__IMU_TRANSFORM_HPP_

imu_sensor_broadcaster/package.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
<depend>backward_ros</depend>
2626
<depend>controller_interface</depend>
27+
<depend>eigen</depend>
2728
<depend>generate_parameter_library</depend>
2829
<depend>hardware_interface</depend>
2930
<depend>pluginlib</depend>
@@ -38,6 +39,8 @@
3839
<test_depend>controller_manager</test_depend>
3940
<test_depend>hardware_interface_testing</test_depend>
4041
<test_depend>ros2_control_test_assets</test_depend>
42+
<test_depend>tf2</test_depend>
43+
<test_depend>tf2_geometry_msgs</test_depend>
4144

4245
<export>
4346
<build_type>ament_cmake</build_type>

imu_sensor_broadcaster/src/imu_sensor_broadcaster.cpp

Lines changed: 80 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,15 @@
2121
#include <memory>
2222
#include <string>
2323

24+
#include "imu_sensor_broadcaster/imu_transform.hpp"
25+
2426
namespace imu_sensor_broadcaster
2527
{
2628
controller_interface::CallbackReturn IMUSensorBroadcaster::on_init()
2729
{
2830
try
2931
{
3032
param_listener_ = std::make_shared<ParamListener>(get_node());
31-
params_ = param_listener_->get_params();
3233
}
3334
catch (const std::exception & e)
3435
{
@@ -43,7 +44,20 @@ controller_interface::CallbackReturn IMUSensorBroadcaster::on_init()
4344
controller_interface::CallbackReturn IMUSensorBroadcaster::on_configure(
4445
const rclcpp_lifecycle::State & /*previous_state*/)
4546
{
46-
params_ = param_listener_->get_params();
47+
try
48+
{
49+
params_ = param_listener_->get_params();
50+
r_ = quat_from_euler(
51+
params_.rotation_offset.roll, params_.rotation_offset.pitch, params_.rotation_offset.yaw);
52+
r_.normalize();
53+
}
54+
catch (const std::exception & e)
55+
{
56+
RCLCPP_ERROR(
57+
get_node()->get_logger(), "Exception thrown during config stage with message: %s \n",
58+
e.what());
59+
return CallbackReturn::ERROR;
60+
}
4761

4862
imu_sensor_ = std::make_unique<semantic_components::IMUSensor>(
4963
semantic_components::IMUSensor(params_.sensor_name));
@@ -62,18 +76,15 @@ controller_interface::CallbackReturn IMUSensorBroadcaster::on_configure(
6276
return CallbackReturn::ERROR;
6377
}
6478

65-
realtime_publisher_->lock();
66-
realtime_publisher_->msg_.header.frame_id = params_.frame_id;
79+
state_message_.header.frame_id = params_.frame_id;
6780
// convert double vector to fixed-size array in the message
6881
for (size_t i = 0; i < 9; ++i)
6982
{
70-
realtime_publisher_->msg_.orientation_covariance[i] = params_.static_covariance_orientation[i];
71-
realtime_publisher_->msg_.angular_velocity_covariance[i] =
72-
params_.static_covariance_angular_velocity[i];
73-
realtime_publisher_->msg_.linear_acceleration_covariance[i] =
83+
state_message_.orientation_covariance[i] = params_.static_covariance_orientation[i];
84+
state_message_.angular_velocity_covariance[i] = params_.static_covariance_angular_velocity[i];
85+
state_message_.linear_acceleration_covariance[i] =
7486
params_.static_covariance_linear_acceleration[i];
7587
}
76-
realtime_publisher_->unlock();
7788

7889
RCLCPP_DEBUG(get_node()->get_logger(), "configure successful");
7990
return CallbackReturn::SUCCESS;
@@ -110,22 +121,76 @@ controller_interface::CallbackReturn IMUSensorBroadcaster::on_deactivate(
110121
return CallbackReturn::SUCCESS;
111122
}
112123

113-
controller_interface::return_type IMUSensorBroadcaster::update(
124+
controller_interface::return_type IMUSensorBroadcaster::update_and_write_commands(
114125
const rclcpp::Time & time, const rclcpp::Duration & /*period*/)
115126
{
116-
if (realtime_publisher_ && realtime_publisher_->trylock())
127+
sensor_msgs::msg::Imu input_imu{state_message_};
128+
imu_sensor_->get_values_as_message(input_imu);
129+
do_transform(state_message_, input_imu, r_);
130+
131+
if (realtime_publisher_)
117132
{
118-
realtime_publisher_->msg_.header.stamp = time;
119-
imu_sensor_->get_values_as_message(realtime_publisher_->msg_);
120-
realtime_publisher_->unlockAndPublish();
133+
state_message_.header.stamp = time;
134+
realtime_publisher_->try_publish(state_message_);
121135
}
122136

123137
return controller_interface::return_type::OK;
124138
}
125139

140+
controller_interface::return_type IMUSensorBroadcaster::update_reference_from_subscribers(
141+
const rclcpp::Time & /*time*/, const rclcpp::Duration & /*period*/)
142+
{
143+
return controller_interface::return_type::OK;
144+
}
145+
146+
std::vector<hardware_interface::StateInterface> IMUSensorBroadcaster::on_export_state_interfaces()
147+
{
148+
std::vector<hardware_interface::StateInterface> exported_state_interfaces;
149+
150+
std::string export_prefix = get_node()->get_name();
151+
if (!params_.sensor_name.empty())
152+
{
153+
// Update the prefix and get the proper IMU sensor naming
154+
export_prefix = export_prefix + "/" + params_.sensor_name;
155+
}
156+
157+
exported_state_interfaces.emplace_back(
158+
hardware_interface::StateInterface(
159+
export_prefix, "orientation.x", &state_message_.orientation.x));
160+
exported_state_interfaces.emplace_back(
161+
hardware_interface::StateInterface(
162+
export_prefix, "orientation.y", &state_message_.orientation.y));
163+
exported_state_interfaces.emplace_back(
164+
hardware_interface::StateInterface(
165+
export_prefix, "orientation.z", &state_message_.orientation.z));
166+
exported_state_interfaces.emplace_back(
167+
hardware_interface::StateInterface(
168+
export_prefix, "orientation.w", &state_message_.orientation.w));
169+
exported_state_interfaces.emplace_back(
170+
hardware_interface::StateInterface(
171+
export_prefix, "angular_velocity.x", &state_message_.angular_velocity.x));
172+
exported_state_interfaces.emplace_back(
173+
hardware_interface::StateInterface(
174+
export_prefix, "angular_velocity.y", &state_message_.angular_velocity.y));
175+
exported_state_interfaces.emplace_back(
176+
hardware_interface::StateInterface(
177+
export_prefix, "angular_velocity.z", &state_message_.angular_velocity.z));
178+
exported_state_interfaces.emplace_back(
179+
hardware_interface::StateInterface(
180+
export_prefix, "linear_acceleration.x", &state_message_.linear_acceleration.x));
181+
exported_state_interfaces.emplace_back(
182+
hardware_interface::StateInterface(
183+
export_prefix, "linear_acceleration.y", &state_message_.linear_acceleration.y));
184+
exported_state_interfaces.emplace_back(
185+
hardware_interface::StateInterface(
186+
export_prefix, "linear_acceleration.z", &state_message_.linear_acceleration.z));
187+
188+
return exported_state_interfaces;
189+
}
190+
126191
} // namespace imu_sensor_broadcaster
127192

128193
#include "pluginlib/class_list_macros.hpp"
129194

130195
PLUGINLIB_EXPORT_CLASS(
131-
imu_sensor_broadcaster::IMUSensorBroadcaster, controller_interface::ControllerInterface)
196+
imu_sensor_broadcaster::IMUSensorBroadcaster, controller_interface::ChainableControllerInterface)

0 commit comments

Comments
 (0)