-
Notifications
You must be signed in to change notification settings - Fork 414
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
External wrench estimator: first-order momentum observer (#311)
External wrench estimator: first-order momentum observer
- Loading branch information
Showing
4 changed files
with
622 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,214 @@ | ||
// Copyright (C) 2021 Djordje Vukcevic <djordje dot vukcevic at h-brs dot de> | ||
|
||
// Version: 1.0 | ||
// Author: Djordje Vukcevic <djordje dot vukcevic at h-brs dot de> | ||
// URL: http://www.orocos.org/kdl | ||
|
||
// This library is free software; you can redistribute it and/or | ||
// modify it under the terms of the GNU Lesser General Public | ||
// License as published by the Free Software Foundation; either | ||
// version 2.1 of the License, or (at your option) any later version. | ||
|
||
// This library is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
// Lesser General Public License for more details. | ||
|
||
// You should have received a copy of the GNU Lesser General Public | ||
// License along with this library; if not, write to the Free Software | ||
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | ||
|
||
#include "chainexternalwrenchestimator.hpp" | ||
|
||
namespace KDL { | ||
|
||
ChainExternalWrenchEstimator::ChainExternalWrenchEstimator(const Chain &chain, const Vector &gravity, const double sample_frequency, const double estimation_gain, const double filter_constant, const double eps, const int maxiter) : | ||
CHAIN(chain), | ||
DT_SEC(1.0 / sample_frequency), FILTER_CONST(filter_constant), | ||
svd_eps(eps), | ||
svd_maxiter(maxiter), | ||
nj(CHAIN.getNrOfJoints()), ns(CHAIN.getNrOfSegments()), | ||
jnt_mass_matrix(nj), previous_jnt_mass_matrix(nj), jnt_mass_matrix_dot(nj), | ||
initial_jnt_momentum(nj), estimated_momentum_integral(nj), filtered_estimated_ext_torque(nj), | ||
gravity_torque(nj), coriolis_torque(nj), total_torque(nj), estimated_ext_torque(nj), | ||
jacobian_end_eff(nj), | ||
jacobian_end_eff_transpose(Eigen::MatrixXd::Zero(nj, 6)), jacobian_end_eff_transpose_inv(Eigen::MatrixXd::Zero(6, nj)), | ||
U(Eigen::MatrixXd::Zero(nj, 6)), V(Eigen::MatrixXd::Zero(6, 6)), | ||
S(Eigen::VectorXd::Zero(6)), S_inv(Eigen::VectorXd::Zero(6)), tmp(Eigen::VectorXd::Zero(6)), | ||
ESTIMATION_GAIN(Eigen::VectorXd::Constant(nj, estimation_gain)), | ||
dynparam_solver(CHAIN, gravity), | ||
jacobian_solver(CHAIN), | ||
fk_pos_solver(CHAIN) | ||
{ | ||
} | ||
|
||
void ChainExternalWrenchEstimator::updateInternalDataStructures() | ||
{ | ||
nj = CHAIN.getNrOfJoints(); | ||
ns = CHAIN.getNrOfSegments(); | ||
jnt_mass_matrix.resize(nj); | ||
previous_jnt_mass_matrix.resize(nj); | ||
jnt_mass_matrix_dot.resize(nj); | ||
initial_jnt_momentum.resize(nj); | ||
estimated_momentum_integral.resize(nj); | ||
filtered_estimated_ext_torque.resize(nj); | ||
gravity_torque.resize(nj); | ||
coriolis_torque.resize(nj); | ||
total_torque.resize(nj); | ||
estimated_ext_torque.resize(nj); | ||
jacobian_end_eff.resize(nj); | ||
jacobian_end_eff_transpose.conservativeResizeLike(MatrixXd::Zero(nj, 6)); | ||
jacobian_end_eff_transpose_inv.conservativeResizeLike(MatrixXd::Zero(6, nj)); | ||
U.conservativeResizeLike(MatrixXd::Zero(nj, 6)); | ||
V.conservativeResizeLike(MatrixXd::Zero(6, 6)); | ||
S.conservativeResizeLike(VectorXd::Zero(6)); | ||
S_inv.conservativeResizeLike(VectorXd::Zero(6)); | ||
tmp.conservativeResizeLike(VectorXd::Zero(6)); | ||
ESTIMATION_GAIN.conservativeResizeLike(Eigen::VectorXd::Constant(nj, ESTIMATION_GAIN(0))); | ||
dynparam_solver.updateInternalDataStructures(); | ||
jacobian_solver.updateInternalDataStructures(); | ||
fk_pos_solver.updateInternalDataStructures(); | ||
} | ||
|
||
// Calculates robot's initial momentum in the joint space. If this method is not called by the user, zero values will be taken for the initial momentum. | ||
int ChainExternalWrenchEstimator::setInitialMomentum(const JntArray &joint_position, const JntArray &joint_velocity) | ||
{ | ||
// Check sizes first | ||
if (joint_position.rows() != nj || joint_velocity.rows() != nj) | ||
return (error = E_SIZE_MISMATCH); | ||
|
||
// Calculate robot's inertia and momentum in the joint space | ||
if (E_NOERROR != dynparam_solver.JntToMass(joint_position, jnt_mass_matrix)) | ||
return (error = E_DYNPARAMSOLVERMASS_FAILED); | ||
|
||
initial_jnt_momentum.data = jnt_mass_matrix.data * joint_velocity.data; | ||
|
||
// Reset data because of the new momentum offset | ||
SetToZero(estimated_momentum_integral); | ||
SetToZero(filtered_estimated_ext_torque); | ||
|
||
return (error = E_NOERROR); | ||
} | ||
|
||
// Sets singular-value eps parameter for the SVD calculation | ||
void ChainExternalWrenchEstimator::setSVDEps(const double eps_in) | ||
{ | ||
svd_eps = eps_in; | ||
} | ||
|
||
// Sets maximum iteration parameter for the SVD calculation | ||
void ChainExternalWrenchEstimator::setSVDMaxIter(const int maxiter_in) | ||
{ | ||
svd_maxiter = maxiter_in; | ||
} | ||
|
||
// This method calculates the external wrench that is applied on the robot's end-effector. | ||
int ChainExternalWrenchEstimator::JntToExtWrench(const JntArray &joint_position, const JntArray &joint_velocity, const JntArray &joint_torque, Wrench &external_wrench) | ||
{ | ||
/** | ||
* ========================================================================== | ||
* First-order momentum observer, an implementation based on: | ||
* S. Haddadin, A. De Luca and A. Albu-Schäffer, | ||
* "Robot Collisions: A Survey on Detection, Isolation, and Identification," | ||
* in IEEE Transactions on Robotics, vol. 33(6), pp. 1292-1312, 2017. | ||
* ========================================================================== | ||
*/ | ||
|
||
// Check sizes first | ||
if (nj != CHAIN.getNrOfJoints() || ns != CHAIN.getNrOfSegments()) | ||
return (error = E_NOT_UP_TO_DATE); | ||
if (joint_position.rows() != nj || joint_velocity.rows() != nj || joint_torque.rows() != nj) | ||
return (error = E_SIZE_MISMATCH); | ||
|
||
/** | ||
* ================================================================================================================ | ||
* Part I: Estimation of the torques felt in joints as a result of the external wrench being applied on the robot | ||
* ================================================================================================================ | ||
*/ | ||
|
||
// Calculate decomposed robot's dynamics | ||
if (E_NOERROR != dynparam_solver.JntToMass(joint_position, jnt_mass_matrix)) | ||
return (error = E_DYNPARAMSOLVERMASS_FAILED); | ||
|
||
if (E_NOERROR != dynparam_solver.JntToCoriolis(joint_position, joint_velocity, coriolis_torque)) | ||
return (error = E_DYNPARAMSOLVERCORIOLIS_FAILED); | ||
|
||
if (E_NOERROR != dynparam_solver.JntToGravity(joint_position, gravity_torque)) | ||
return (error = E_DYNPARAMSOLVERGRAVITY_FAILED); | ||
|
||
// Calculate the change of robot's inertia in the joint space | ||
jnt_mass_matrix_dot.data = (jnt_mass_matrix.data - previous_jnt_mass_matrix.data) / DT_SEC; | ||
|
||
// Save data for the next iteration | ||
previous_jnt_mass_matrix.data = jnt_mass_matrix.data; | ||
|
||
// Calculate total torque exerted on the joint | ||
total_torque.data = joint_torque.data - gravity_torque.data - coriolis_torque.data + jnt_mass_matrix_dot.data * joint_velocity.data; | ||
|
||
// Accumulate main integral | ||
estimated_momentum_integral.data += (total_torque.data + filtered_estimated_ext_torque.data) * DT_SEC; | ||
|
||
// Estimate external joint torque | ||
estimated_ext_torque.data = ESTIMATION_GAIN.asDiagonal() * (jnt_mass_matrix.data * joint_velocity.data - estimated_momentum_integral.data - initial_jnt_momentum.data); | ||
|
||
// First order low-pass filter: filter out the noise from the estimated signal | ||
// This filter can be turned off by setting FILTER_CONST value to 0 | ||
filtered_estimated_ext_torque.data = FILTER_CONST * filtered_estimated_ext_torque.data + (1.0 - FILTER_CONST) * estimated_ext_torque.data; | ||
|
||
/** | ||
* ================================================================================================================== | ||
* Part II: Propagate above-estimated joint torques to Cartesian wrench using a pseudo-inverse of Jacobian-Transpose | ||
* ================================================================================================================== | ||
*/ | ||
|
||
// Compute robot's end-effector frame, expressed in the base frame | ||
Frame end_eff_frame; | ||
if (E_NOERROR != fk_pos_solver.JntToCart(joint_position, end_eff_frame)) | ||
return (error = E_FKSOLVERPOS_FAILED); | ||
|
||
// Compute robot's jacobian for the end-effector frame, expressed in the base frame | ||
if (E_NOERROR != jacobian_solver.JntToJac(joint_position, jacobian_end_eff)) | ||
return (error = E_JACSOLVER_FAILED); | ||
|
||
// Transform the jacobian from the base frame to the end-effector frame. | ||
// This part can be commented out if the user wants estimated wrench to be expressed w.r.t. base frame | ||
jacobian_end_eff.changeBase(end_eff_frame.M.Inverse()); // Jacobian is now expressed w.r.t. end-effector frame | ||
|
||
// SVD of "Jac^T" with maximum iterations "maxiter": Jac^T = U * S^-1 * V^T | ||
jacobian_end_eff_transpose = jacobian_end_eff.data.transpose(); | ||
if (E_NOERROR != svd_eigen_HH(jacobian_end_eff_transpose, U, S, V, tmp, svd_maxiter)) | ||
return (error = E_SVD_FAILED); | ||
|
||
// Invert singular values: S^-1 | ||
for (int i = 0; i < S.size(); ++i) | ||
S_inv(i) = (std::fabs(S(i)) < svd_eps) ? 0.0 : 1.0 / S(i); | ||
|
||
// Compose the inverse: (Jac^T)^-1 = V * S^-1 * U^T | ||
jacobian_end_eff_transpose_inv = V * S_inv.asDiagonal() * U.adjoint(); | ||
|
||
// Compute end-effector's Cartesian wrench from the estimated joint torques: (Jac^T)^-1 * ext_tau | ||
Vector6d estimated_wrench = jacobian_end_eff_transpose_inv * filtered_estimated_ext_torque.data; | ||
for (int i = 0; i < 6; i++) | ||
external_wrench(i) = estimated_wrench(i); | ||
|
||
return (error = E_NOERROR); | ||
} | ||
|
||
// Getter for the torques felt in the robot's joints due to the external wrench being applied on the robot | ||
void ChainExternalWrenchEstimator::getEstimatedJntTorque(JntArray &external_joint_torque) | ||
{ | ||
assert(external_joint_torque.rows() == filtered_estimated_ext_torque.rows()); | ||
external_joint_torque = filtered_estimated_ext_torque; | ||
} | ||
|
||
const char* ChainExternalWrenchEstimator::strError(const int error) const | ||
{ | ||
if (E_FKSOLVERPOS_FAILED == error) return "Internally-used Forward Position Kinematics (Recursive) solver failed"; | ||
else if (E_JACSOLVER_FAILED == error) return "Internally-used Jacobian solver failed"; | ||
else if (E_DYNPARAMSOLVERMASS_FAILED == error) return "Internally-used Dynamics Parameters (Mass) solver failed"; | ||
else if (E_DYNPARAMSOLVERCORIOLIS_FAILED == error) return "Internally-used Dynamics Parameters (Coriolis) solver failed"; | ||
else if (E_DYNPARAMSOLVERGRAVITY_FAILED == error) return "Internally-used Dynamics Parameters (Gravity) solver failed"; | ||
else return SolverI::strError(error); | ||
} | ||
|
||
} // namespace |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
// Copyright (C) 2021 Djordje Vukcevic <djordje dot vukcevic at h-brs dot de> | ||
|
||
// Version: 1.0 | ||
// Author: Djordje Vukcevic <djordje dot vukcevic at h-brs dot de> | ||
// URL: http://www.orocos.org/kdl | ||
|
||
// This library is free software; you can redistribute it and/or | ||
// modify it under the terms of the GNU Lesser General Public | ||
// License as published by the Free Software Foundation; either | ||
// version 2.1 of the License, or (at your option) any later version. | ||
|
||
// This library is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
// Lesser General Public License for more details. | ||
|
||
// You should have received a copy of the GNU Lesser General Public | ||
// License along with this library; if not, write to the Free Software | ||
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | ||
|
||
#ifndef KDL_CHAIN_EXTERNAL_WRENCH_ESTIMATOR_HPP | ||
#define KDL_CHAIN_EXTERNAL_WRENCH_ESTIMATOR_HPP | ||
|
||
#include <Eigen/Core> | ||
#include "utilities/svd_eigen_HH.hpp" | ||
#include "chaindynparam.hpp" | ||
#include "chainjnttojacsolver.hpp" | ||
#include "chainfksolverpos_recursive.hpp" | ||
#include <iostream> | ||
|
||
namespace KDL { | ||
|
||
/** | ||
* \brief First-order momentum observer for the estimation of external wrenches applied on the robot's end-effector. | ||
* | ||
* Implementation based on: | ||
* S. Haddadin, A. De Luca and A. Albu-Schäffer, | ||
* "Robot Collisions: A Survey on Detection, Isolation, and Identification," | ||
* in IEEE Transactions on Robotics, vol. 33(6), pp. 1292-1312, 2017. | ||
* | ||
* Note: This component assumes that the external wrench is applied on the end-effector (last) link of the robot's chain. | ||
*/ | ||
class ChainExternalWrenchEstimator : public SolverI | ||
{ | ||
typedef Eigen::Matrix<double, 6, 1 > Vector6d; | ||
|
||
public: | ||
|
||
static const int E_FKSOLVERPOS_FAILED = -100; //! Internally-used Forward Position Kinematics (Recursive) solver failed | ||
static const int E_JACSOLVER_FAILED = -101; //! Internally-used Jacobian solver failed | ||
static const int E_DYNPARAMSOLVERMASS_FAILED = -102; //! Internally-used Dynamics Parameters (Mass) solver failed | ||
static const int E_DYNPARAMSOLVERCORIOLIS_FAILED = -103; //! Internally-used Dynamics Parameters (Coriolis) solver failed | ||
static const int E_DYNPARAMSOLVERGRAVITY_FAILED = -104; //! Internally-used Dynamics Parameters (Gravity) solver failed | ||
|
||
/** | ||
* Constructor for the estimator, it will allocate all the necessary memory | ||
* \param chain The kinematic chain of the robot, an internal copy will be made. | ||
* \param gravity The gravity-acceleration vector to use during the calculation. | ||
* \param sample_frequency Frequency at which users updates it estimation loop (in Hz). | ||
* \param estimation_gain Parameter used to control the estimator's convergence | ||
* \param filter_constant Parameter defining how much the estimated signal should be filtered by the low-pass filter. | ||
* This input value should be between 0 and 1. Higher the number means more noise needs to be filtered-out. | ||
* The filter can be turned off by setting this value to 0. | ||
* \param eps If a SVD-singular value is below this value, its inverse is set to zero. Default: 0.00001 | ||
* \param maxiter Maximum iterations for the SVD computations. Default: 150. | ||
*/ | ||
ChainExternalWrenchEstimator(const Chain &chain, const Vector &gravity, const double sample_frequency, const double estimation_gain, const double filter_constant, const double eps = 0.00001, const int maxiter = 150); | ||
~ChainExternalWrenchEstimator(){}; | ||
|
||
/** | ||
* Calculates robot's initial momentum in the joint space. | ||
* Bassically, sets the offset for future estimation (momentum calculation). | ||
* If this method is not called by the user, zero values will be taken for the initial momentum. | ||
*/ | ||
int setInitialMomentum(const JntArray &joint_position, const JntArray &joint_velocity); | ||
|
||
// Sets singular-value eps parameter for the SVD calculation | ||
void setSVDEps(const double eps_in); | ||
|
||
// Sets maximum iteration parameter for the SVD calculation | ||
void setSVDMaxIter(const int maxiter_in); | ||
|
||
/** | ||
* This method calculates the external wrench that is applied on the robot's end-effector. | ||
* Input parameters: | ||
* \param joint_position The current (measured) joint positions. | ||
* \param joint_velocity The current (measured) joint velocities. | ||
* \param joint_torque The joint space torques. | ||
* Depending on the user's choice, this array can represent commanded or measured joint torques. | ||
* A particular choice depends on the available sensors in robot's joint. | ||
* For more details see the above-referenced article. | ||
* | ||
* Output parameters: | ||
* \param external_wrench The estimated external wrench applied on the robot's end-effector. | ||
* The wrench will be expressed w.r.t. end-effector's frame. | ||
* | ||
* @return error/success code | ||
*/ | ||
int JntToExtWrench(const JntArray &joint_position, const JntArray &joint_velocity, const JntArray &joint_torque, Wrench &external_wrench); | ||
|
||
// Returns the torques felt in the robot's joints as a result of the external wrench being applied on the robot. | ||
void getEstimatedJntTorque(JntArray &external_joint_torque); | ||
|
||
/// @copydoc KDL::SolverI::updateInternalDataStructures() | ||
virtual void updateInternalDataStructures(); | ||
|
||
/// @copydoc KDL::SolverI::strError() | ||
virtual const char* strError(const int error) const; | ||
|
||
private: | ||
const Chain &CHAIN; | ||
const double DT_SEC, FILTER_CONST; | ||
double svd_eps; | ||
int svd_maxiter; | ||
unsigned int nj, ns; | ||
JntSpaceInertiaMatrix jnt_mass_matrix, previous_jnt_mass_matrix, jnt_mass_matrix_dot; | ||
JntArray initial_jnt_momentum, estimated_momentum_integral, filtered_estimated_ext_torque, | ||
gravity_torque, coriolis_torque, total_torque, estimated_ext_torque; | ||
Jacobian jacobian_end_eff; | ||
Eigen::MatrixXd jacobian_end_eff_transpose, jacobian_end_eff_transpose_inv, U, V; | ||
Eigen::VectorXd S, S_inv, tmp, ESTIMATION_GAIN; | ||
ChainDynParam dynparam_solver; | ||
ChainJntToJacSolver jacobian_solver; | ||
ChainFkSolverPos_recursive fk_pos_solver; | ||
}; | ||
} | ||
|
||
#endif |
Oops, something went wrong.