Skip to content

Commit 48f92c1

Browse files
committed
moved the FT estimation into an asynchronous thread (#213)
1 parent ebe74f6 commit 48f92c1

13 files changed

Lines changed: 202 additions & 96 deletions

File tree

lbr_description/ros2_control/lbr_system_config.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ hardware:
1919
open_loop: true # KUKA works the best in open_loop control mode
2020

2121
estimated_ft_sensor: # estimates the external force-torque from the external joint torque values
22+
enabled: true # whether to enable the force-torque estimation
23+
update_rate: 100 # update rate for the force-torque estimation [Hz] (less or equal to controller manager update rate)
24+
rt_prio: 30 # real-time priority for the force-torque estimation
2225
chain_root: lbr_link_0
2326
chain_tip: lbr_link_ee
2427
damping: 0.2 # damping factor for the pseudo-inverse of the Jacobian

lbr_description/ros2_control/lbr_system_interface.xacro

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@
6161

6262
<sensor
6363
name="estimated_ft_sensor">
64+
<param name="enabled">${system_config['estimated_ft_sensor']['enabled']}</param>
65+
<param name="update_rate">${system_config['estimated_ft_sensor']['update_rate']}</param>
66+
<param name="rt_prio">${system_config['estimated_ft_sensor']['rt_prio']}</param>
6467
<param name="chain_root">${system_config['estimated_ft_sensor']['chain_root']}</param>
6568
<param name="chain_tip">${system_config['estimated_ft_sensor']['chain_tip']}</param>
6669
<param name="damping">${system_config['estimated_ft_sensor']['damping']}</param>
@@ -70,12 +73,14 @@
7073
<param name="torque_x_th">${system_config['estimated_ft_sensor']['torque_x_th']}</param>
7174
<param name="torque_y_th">${system_config['estimated_ft_sensor']['torque_y_th']}</param>
7275
<param name="torque_z_th">${system_config['estimated_ft_sensor']['torque_z_th']}</param>
73-
<state_interface name="force.x" />
74-
<state_interface name="force.y" />
75-
<state_interface name="force.z" />
76-
<state_interface name="torque.x" />
77-
<state_interface name="torque.y" />
78-
<state_interface name="torque.z" />
76+
<xacro:if value="${system_config['estimated_ft_sensor']['enabled']}">
77+
<state_interface name="force.x" />
78+
<state_interface name="force.y" />
79+
<state_interface name="force.z" />
80+
<state_interface name="torque.x" />
81+
<state_interface name="torque.y" />
82+
<state_interface name="torque.z" />
83+
</xacro:if>
7984
</sensor>
8085

8186
<!-- FRI Cartesian impedance control mode -->
@@ -114,7 +119,8 @@
114119
<param name="max_position">${max_position}</param>
115120
<param name="max_velocity">${max_velocity}</param>
116121
<param name="max_torque">${max_torque}</param>
117-
<xacro:if value="${system_config['hardware']['fri_client_sdk']['major_version'] == 1}">
122+
<xacro:if
123+
value="${system_config['hardware']['fri_client_sdk']['major_version'] == 1}">
118124
<state_interface name="commanded_joint_position" />
119125
</xacro:if>
120126
<state_interface name="commanded_torque" />

lbr_fri_ros2/include/lbr_fri_ros2/app.hpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#define LBR_FRI_ROS2__APP_HPP_
33

44
#include <memory>
5+
#include <string>
56

67
#include "rclcpp/logger.hpp"
78
#include "rclcpp/logging.hpp"
@@ -22,9 +23,6 @@ class App : public Worker {
2223
* KUKA::FRI::ClientApplication::step() (this is by KUKA's design).
2324
*
2425
*/
25-
protected:
26-
static constexpr char LOGGER_NAME[] = "lbr_fri_ros2::App";
27-
2826
public:
2927
App(const std::shared_ptr<AsyncClient> async_client_ptr);
3028
~App();
@@ -33,6 +31,8 @@ class App : public Worker {
3331
bool close_udp_socket();
3432
void run_async(int rt_prio = 80) override;
3533

34+
inline std::string LOGGER_NAME() const override { return "lbr_fri_ros2::App"; };
35+
3636
protected:
3737
void perform_work_() override;
3838
bool valid_port_(const int &port_id);

lbr_fri_ros2/include/lbr_fri_ros2/ft_estimator.hpp

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include <cmath>
77
#include <memory>
88
#include <string>
9+
#include <thread>
910

1011
#include "eigen3/Eigen/Core"
1112
#include "rclcpp/logger.hpp"
@@ -17,31 +18,74 @@
1718
#include "lbr_fri_ros2/kinematics.hpp"
1819
#include "lbr_fri_ros2/pinv.hpp"
1920
#include "lbr_fri_ros2/types.hpp"
21+
#include "lbr_fri_ros2/worker.hpp"
2022

2123
namespace lbr_fri_ros2 {
22-
class FTEstimator {
24+
class FTEstimatorImpl {
25+
/**
26+
* @brief A class to estimate force-torques from external joint torque readings. Note that only
27+
* forces beyond a specified threshold are returned. The specified threshold is removed from the
28+
* estimated force-torque.
29+
*
30+
*/
2331
protected:
24-
static constexpr char LOGGER_NAME[] = "lbr_fri_ros2::FTEstimator";
32+
static constexpr char LOGGER_NAME[] = "lbr_fri_ros2::FTEstimatorImpl";
2533

2634
public:
27-
FTEstimator(const std::string &robot_description, const std::string &chain_root = "lbr_link_0",
28-
const std::string &chain_tip = "lbr_link_ee",
29-
const_cart_array_t_ref f_ext_th = {2., 2., 2., 0.5, 0.5, 0.5});
30-
void compute(const_jnt_array_t_ref measured_joint_position, const_jnt_array_t_ref external_torque,
31-
cart_array_t_ref f_ext, const double &damping = 0.2);
35+
FTEstimatorImpl(const std::string &robot_description,
36+
const std::string &chain_root = "lbr_link_0",
37+
const std::string &chain_tip = "lbr_link_ee",
38+
const_cart_array_t_ref f_ext_th = {2., 2., 2., 0.5, 0.5, 0.5},
39+
const double &damping = 0.2);
40+
void compute();
3241
void reset();
3342

43+
inline void get_f_ext(cart_array_t_ref f_ext) const {
44+
Eigen::Map<Eigen::Matrix<double, CARTESIAN_DOF, 1>>(f_ext.data()) = f_ext_;
45+
}
46+
inline void get_f_ext_tf(cart_array_t_ref f_ext) const {
47+
Eigen::Map<Eigen::Matrix<double, CARTESIAN_DOF, 1>>(f_ext.data()) = f_ext_tf_;
48+
}
49+
inline void set_tau_ext(const_jnt_array_t_ref tau_ext) {
50+
tau_ext_ = Eigen::Map<const Eigen::Matrix<double, N_JNTS, 1>>(tau_ext.data());
51+
}
52+
inline void set_q(const_jnt_array_t_ref q) { q_ = q; }
53+
3454
protected:
3555
// force threshold
3656
cart_array_t f_ext_th_;
3757

58+
// damping for pseudo-inverse of Jacobian
59+
double damping_;
60+
61+
// joint positions and external joint torques
62+
jnt_array_t q_;
63+
3864
// kinematics
3965
std::unique_ptr<Kinematics> kinematics_ptr_;
4066

4167
// force estimation
4268
Eigen::Matrix<double, N_JNTS, CARTESIAN_DOF> jacobian_inv_;
4369
Eigen::Matrix<double, N_JNTS, 1> tau_ext_;
44-
Eigen::Matrix<double, CARTESIAN_DOF, 1> f_ext_;
70+
Eigen::Matrix<double, CARTESIAN_DOF, 1> f_ext_raw_, f_ext_, f_ext_tf_;
71+
};
72+
73+
class FTEstimator : public Worker {
74+
/**
75+
* @brief A simple class to run the FTEstimatorImpl asynchronously at a specified update rate.
76+
*
77+
*/
78+
public:
79+
FTEstimator(const std::shared_ptr<FTEstimatorImpl> ft_estimator,
80+
const std::uint16_t &update_rate = 100);
81+
82+
inline std::string LOGGER_NAME() const override { return "lbr_fri_ros2::FTEstimator"; };
83+
84+
protected:
85+
void perform_work_() override;
86+
87+
std::shared_ptr<FTEstimatorImpl> ft_estimator_impl_ptr_;
88+
std::uint16_t update_rate_;
4589
};
4690
} // namespace lbr_fri_ros2
4791
#endif // LBR_FRI_ROS2__FT_ESTIMATOR_HPP_

lbr_fri_ros2/include/lbr_fri_ros2/worker.hpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#define LBR_FRI_ROS2__WORKER_HPP_
33

44
#include <atomic>
5+
#include <string>
56
#include <thread>
67

78
#include "rclcpp/logger.hpp"
@@ -12,15 +13,13 @@
1213

1314
namespace lbr_fri_ros2 {
1415
class Worker {
15-
protected:
16-
static constexpr char LOGGER_NAME[] = "lbr_fri_ros2::Worker";
17-
1816
public:
1917
Worker();
2018
~Worker();
2119

2220
virtual void run_async(int rt_prio = 80);
2321
void request_stop();
22+
inline virtual std::string LOGGER_NAME() const = 0;
2423

2524
protected:
2625
virtual void perform_work_() = 0;

lbr_fri_ros2/src/app.cpp

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,70 +15,70 @@ App::~App() {
1515

1616
bool App::open_udp_socket(const int &port_id, const char *const remote_host) {
1717
if (!connection_ptr_) {
18-
RCLCPP_ERROR_STREAM(rclcpp::get_logger(LOGGER_NAME),
18+
RCLCPP_ERROR_STREAM(rclcpp::get_logger(LOGGER_NAME()),
1919
ColorScheme::ERROR << "Connection not configured" << ColorScheme::ENDC);
2020
return false;
2121
}
22-
RCLCPP_INFO_STREAM(rclcpp::get_logger(LOGGER_NAME),
22+
RCLCPP_INFO_STREAM(rclcpp::get_logger(LOGGER_NAME()),
2323
ColorScheme::OKBLUE << "Opening UDP socket with port_id '" << ColorScheme::BOLD
2424
<< port_id << "'" << ColorScheme::ENDC);
2525
if (!valid_port_(port_id)) {
2626
return false;
2727
}
2828
if (connection_ptr_->isOpen()) {
29-
RCLCPP_INFO(rclcpp::get_logger(LOGGER_NAME), "Socket already open");
29+
RCLCPP_INFO(rclcpp::get_logger(LOGGER_NAME()), "Socket already open");
3030
return true;
3131
}
3232
if (!connection_ptr_->open(port_id, remote_host)) {
33-
RCLCPP_ERROR_STREAM(rclcpp::get_logger(LOGGER_NAME),
33+
RCLCPP_ERROR_STREAM(rclcpp::get_logger(LOGGER_NAME()),
3434
ColorScheme::ERROR << "Failed to open socket" << ColorScheme::ENDC);
3535
return false;
3636
}
37-
RCLCPP_INFO_STREAM(rclcpp::get_logger(LOGGER_NAME),
37+
RCLCPP_INFO_STREAM(rclcpp::get_logger(LOGGER_NAME()),
3838
ColorScheme::OKGREEN << "Socket opened successfully" << ColorScheme::ENDC);
3939
return true;
4040
}
4141

4242
bool App::close_udp_socket() {
4343
if (!connection_ptr_) {
44-
RCLCPP_ERROR_STREAM(rclcpp::get_logger(LOGGER_NAME),
44+
RCLCPP_ERROR_STREAM(rclcpp::get_logger(LOGGER_NAME()),
4545
ColorScheme::ERROR << "Connection not configured" << ColorScheme::ENDC);
4646
return false;
4747
}
4848
while (running_) {
49-
RCLCPP_INFO(rclcpp::get_logger(LOGGER_NAME), "Waiting for run thread termination");
49+
RCLCPP_INFO(rclcpp::get_logger(LOGGER_NAME()), "Waiting for run thread termination");
5050
std::this_thread::sleep_for(std::chrono::milliseconds(100));
5151
}
52-
RCLCPP_INFO_STREAM(rclcpp::get_logger(LOGGER_NAME),
52+
RCLCPP_INFO_STREAM(rclcpp::get_logger(LOGGER_NAME()),
5353
ColorScheme::OKBLUE << "Closing UDP socket" << ColorScheme::ENDC);
5454
if (!connection_ptr_->isOpen()) {
55-
RCLCPP_INFO(rclcpp::get_logger(LOGGER_NAME), "Socket already closed");
55+
RCLCPP_INFO(rclcpp::get_logger(LOGGER_NAME()), "Socket already closed");
5656
return true;
5757
}
5858
connection_ptr_->close();
59-
RCLCPP_INFO_STREAM(rclcpp::get_logger(LOGGER_NAME),
59+
RCLCPP_INFO_STREAM(rclcpp::get_logger(LOGGER_NAME()),
6060
ColorScheme::OKGREEN << "Socket closed successfully" << ColorScheme::ENDC);
6161
return true;
6262
}
6363

6464
void App::run_async(int rt_prio) {
6565
if (!async_client_ptr_) {
66-
RCLCPP_ERROR_STREAM(rclcpp::get_logger(LOGGER_NAME),
66+
RCLCPP_ERROR_STREAM(rclcpp::get_logger(LOGGER_NAME()),
6767
ColorScheme::ERROR << "AsyncClient not configured" << ColorScheme::ENDC);
6868
return;
6969
}
7070
if (!connection_ptr_) {
71-
RCLCPP_ERROR_STREAM(rclcpp::get_logger(LOGGER_NAME),
71+
RCLCPP_ERROR_STREAM(rclcpp::get_logger(LOGGER_NAME()),
7272
ColorScheme::ERROR << "Connection not configured" << ColorScheme::ENDC);
7373
return;
7474
}
7575
if (!connection_ptr_->isOpen()) {
76-
RCLCPP_ERROR_STREAM(rclcpp::get_logger(LOGGER_NAME),
76+
RCLCPP_ERROR_STREAM(rclcpp::get_logger(LOGGER_NAME()),
7777
ColorScheme::ERROR << "Connection not open" << ColorScheme::ENDC);
7878
return;
7979
}
8080
if (!app_ptr_) {
81-
RCLCPP_ERROR_STREAM(rclcpp::get_logger(LOGGER_NAME),
81+
RCLCPP_ERROR_STREAM(rclcpp::get_logger(LOGGER_NAME()),
8282
ColorScheme::ERROR << "App not configured" << ColorScheme::ENDC);
8383
return;
8484
}
@@ -95,7 +95,7 @@ void App::perform_work_() {
9595
// may never return
9696
running_ = true;
9797
if (async_client_ptr_->robotState().getSessionState() == KUKA::FRI::ESessionState::IDLE) {
98-
RCLCPP_INFO(rclcpp::get_logger(LOGGER_NAME), "LBR in session state idle, exiting");
98+
RCLCPP_INFO(rclcpp::get_logger(LOGGER_NAME()), "LBR in session state idle, exiting");
9999
break;
100100
}
101101
}
@@ -104,7 +104,7 @@ void App::perform_work_() {
104104

105105
bool App::valid_port_(const int &port_id) {
106106
if (port_id < 30200 || port_id > 30209) {
107-
RCLCPP_ERROR_STREAM(rclcpp::get_logger(LOGGER_NAME),
107+
RCLCPP_ERROR_STREAM(rclcpp::get_logger(LOGGER_NAME()),
108108
ColorScheme::ERROR << "Expected port_id in [30200, 30209], got '"
109109
<< ColorScheme::BOLD << port_id << "'"
110110
<< ColorScheme::ENDC);

lbr_fri_ros2/src/ft_estimator.cpp

Lines changed: 39 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,54 @@
11
#include "lbr_fri_ros2/ft_estimator.hpp"
22

33
namespace lbr_fri_ros2 {
4-
FTEstimator::FTEstimator(const std::string &robot_description, const std::string &chain_root,
5-
const std::string &chain_tip, const_cart_array_t_ref f_ext_th)
6-
: f_ext_th_(f_ext_th) {
4+
FTEstimatorImpl::FTEstimatorImpl(const std::string &robot_description,
5+
const std::string &chain_root, const std::string &chain_tip,
6+
const_cart_array_t_ref f_ext_th, const double &damping)
7+
: f_ext_th_(f_ext_th), damping_(damping) {
78
kinematics_ptr_ = std::make_unique<Kinematics>(robot_description, chain_root, chain_tip);
89
reset();
910
}
1011

11-
void FTEstimator::compute(const_jnt_array_t_ref measured_joint_position,
12-
const_jnt_array_t_ref external_torque, cart_array_t_ref f_ext,
13-
const double &damping) {
14-
tau_ext_ = Eigen::Map<const Eigen::Matrix<double, N_JNTS, 1>>(external_torque.data());
15-
auto jacobian = kinematics_ptr_->compute_jacobian(measured_joint_position);
16-
jacobian_inv_ = pinv(jacobian.data, damping);
17-
f_ext_ = jacobian_inv_.transpose() * tau_ext_;
12+
void FTEstimatorImpl::compute() {
13+
auto jacobian = kinematics_ptr_->compute_jacobian(q_);
14+
jacobian_inv_ = pinv(jacobian.data, damping_);
15+
f_ext_raw_ = jacobian_inv_.transpose() * tau_ext_;
16+
int i = -1;
17+
f_ext_ = f_ext_raw_.unaryExpr([&](double v) {
18+
++i;
19+
if (std::abs(v) < f_ext_th_[i]) {
20+
return 0.;
21+
} else {
22+
return std::copysign(1., v) * (std::abs(v) - f_ext_th_[i]);
23+
}
24+
});
1825

1926
// rotate into chain tip frame
20-
auto chain_tip_frame = kinematics_ptr_->compute_fk(measured_joint_position);
21-
f_ext_.topRows(3) = Eigen::Matrix3d::Map(chain_tip_frame.M.data) * f_ext_.topRows(3);
22-
f_ext_.bottomRows(3) = Eigen::Matrix3d::Map(chain_tip_frame.M.data) * f_ext_.bottomRows(3);
23-
24-
Eigen::Map<Eigen::Matrix<double, CARTESIAN_DOF, 1>>(f_ext.data()) = f_ext_;
25-
26-
// threshold force-torque
27-
std::transform(f_ext.begin(), f_ext.end(), f_ext_th_.begin(), f_ext.begin(),
28-
[](const double &f_ext_i, const double &f_ext_th_i) {
29-
if (std::abs(f_ext_i) < f_ext_th_i) {
30-
return 0.;
31-
} else {
32-
return std::copysign(1., f_ext_i) * (std::abs(f_ext_i) - f_ext_th_i);
33-
}
34-
});
27+
auto chain_tip_frame = kinematics_ptr_->compute_fk(q_);
28+
f_ext_tf_.topRows(3) = Eigen::Matrix3d::Map(chain_tip_frame.M.data) * f_ext_.topRows(3);
29+
f_ext_tf_.bottomRows(3) = Eigen::Matrix3d::Map(chain_tip_frame.M.data) * f_ext_.bottomRows(3);
3530
}
3631

37-
void FTEstimator::reset() {
32+
void FTEstimatorImpl::reset() {
33+
std::for_each(q_.begin(), q_.end(), [](double &q_i) { q_i = 0.; });
3834
tau_ext_.setZero();
35+
f_ext_raw_.setZero();
3936
f_ext_.setZero();
37+
f_ext_tf_.setZero();
38+
jacobian_inv_.setZero();
4039
}
40+
41+
FTEstimator::FTEstimator(const std::shared_ptr<FTEstimatorImpl> ft_estimator_impl_ptr,
42+
const std::uint16_t &update_rate)
43+
: ft_estimator_impl_ptr_(ft_estimator_impl_ptr), update_rate_(update_rate) {}
44+
45+
void FTEstimator::perform_work_() {
46+
running_ = true;
47+
while (rclcpp::ok() && !should_stop_) {
48+
auto start = std::chrono::high_resolution_clock::now();
49+
ft_estimator_impl_ptr_->compute();
50+
std::this_thread::sleep_until(start + std::chrono::nanoseconds(static_cast<int>(
51+
1.e9 / static_cast<double>(update_rate_))));
52+
}
53+
};
4154
} // namespace lbr_fri_ros2

0 commit comments

Comments
 (0)