|
| 1 | +// Copyright (C) 2021 Djordje Vukcevic <djordje dot vukcevic at h-brs dot de> |
| 2 | + |
| 3 | +// Version: 1.0 |
| 4 | +// Author: Djordje Vukcevic <djordje dot vukcevic at h-brs dot de> |
| 5 | +// URL: http://www.orocos.org/kdl |
| 6 | + |
| 7 | +// This library is free software; you can redistribute it and/or |
| 8 | +// modify it under the terms of the GNU Lesser General Public |
| 9 | +// License as published by the Free Software Foundation; either |
| 10 | +// version 2.1 of the License, or (at your option) any later version. |
| 11 | + |
| 12 | +// This library is distributed in the hope that it will be useful, |
| 13 | +// but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 14 | +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 15 | +// Lesser General Public License for more details. |
| 16 | + |
| 17 | +// You should have received a copy of the GNU Lesser General Public |
| 18 | +// License along with this library; if not, write to the Free Software |
| 19 | +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| 20 | + |
| 21 | +#include "chainexternalwrenchestimator.hpp" |
| 22 | + |
| 23 | +namespace KDL { |
| 24 | + |
| 25 | +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) : |
| 26 | + CHAIN(chain), |
| 27 | + DT_SEC(1.0 / sample_frequency), FILTER_CONST(filter_constant), |
| 28 | + svd_eps(eps), |
| 29 | + svd_maxiter(maxiter), |
| 30 | + nj(CHAIN.getNrOfJoints()), ns(CHAIN.getNrOfSegments()), |
| 31 | + jnt_mass_matrix(nj), previous_jnt_mass_matrix(nj), jnt_mass_matrix_dot(nj), |
| 32 | + initial_jnt_momentum(nj), estimated_momentum_integral(nj), filtered_estimated_ext_torque(nj), |
| 33 | + gravity_torque(nj), coriolis_torque(nj), total_torque(nj), estimated_ext_torque(nj), |
| 34 | + jacobian_end_eff(nj), |
| 35 | + jacobian_end_eff_transpose(Eigen::MatrixXd::Zero(nj, 6)), jacobian_end_eff_transpose_inv(Eigen::MatrixXd::Zero(6, nj)), |
| 36 | + U(Eigen::MatrixXd::Zero(nj, 6)), V(Eigen::MatrixXd::Zero(6, 6)), |
| 37 | + S(Eigen::VectorXd::Zero(6)), S_inv(Eigen::VectorXd::Zero(6)), tmp(Eigen::VectorXd::Zero(6)), |
| 38 | + ESTIMATION_GAIN(Eigen::VectorXd::Constant(nj, estimation_gain)), |
| 39 | + dynparam_solver(CHAIN, gravity), |
| 40 | + jacobian_solver(CHAIN), |
| 41 | + fk_pos_solver(CHAIN) |
| 42 | +{ |
| 43 | +} |
| 44 | + |
| 45 | +void ChainExternalWrenchEstimator::updateInternalDataStructures() |
| 46 | +{ |
| 47 | + nj = CHAIN.getNrOfJoints(); |
| 48 | + ns = CHAIN.getNrOfSegments(); |
| 49 | + jnt_mass_matrix.resize(nj); |
| 50 | + previous_jnt_mass_matrix.resize(nj); |
| 51 | + jnt_mass_matrix_dot.resize(nj); |
| 52 | + initial_jnt_momentum.resize(nj); |
| 53 | + estimated_momentum_integral.resize(nj); |
| 54 | + filtered_estimated_ext_torque.resize(nj); |
| 55 | + gravity_torque.resize(nj); |
| 56 | + coriolis_torque.resize(nj); |
| 57 | + total_torque.resize(nj); |
| 58 | + estimated_ext_torque.resize(nj); |
| 59 | + jacobian_end_eff.resize(nj); |
| 60 | + jacobian_end_eff_transpose.conservativeResizeLike(MatrixXd::Zero(nj, 6)); |
| 61 | + jacobian_end_eff_transpose_inv.conservativeResizeLike(MatrixXd::Zero(6, nj)); |
| 62 | + U.conservativeResizeLike(MatrixXd::Zero(nj, 6)); |
| 63 | + V.conservativeResizeLike(MatrixXd::Zero(6, 6)); |
| 64 | + S.conservativeResizeLike(VectorXd::Zero(6)); |
| 65 | + S_inv.conservativeResizeLike(VectorXd::Zero(6)); |
| 66 | + tmp.conservativeResizeLike(VectorXd::Zero(6)); |
| 67 | + ESTIMATION_GAIN.conservativeResizeLike(Eigen::VectorXd::Constant(nj, ESTIMATION_GAIN(0))); |
| 68 | + dynparam_solver.updateInternalDataStructures(); |
| 69 | + jacobian_solver.updateInternalDataStructures(); |
| 70 | + fk_pos_solver.updateInternalDataStructures(); |
| 71 | +} |
| 72 | + |
| 73 | +// 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. |
| 74 | +int ChainExternalWrenchEstimator::setInitialMomentum(const JntArray &joint_position, const JntArray &joint_velocity) |
| 75 | +{ |
| 76 | + // Check sizes first |
| 77 | + if (joint_position.rows() != nj || joint_velocity.rows() != nj) |
| 78 | + return (error = E_SIZE_MISMATCH); |
| 79 | + |
| 80 | + // Calculate robot's inertia and momentum in the joint space |
| 81 | + if (E_NOERROR != dynparam_solver.JntToMass(joint_position, jnt_mass_matrix)) |
| 82 | + return (error = E_DYNPARAMSOLVERMASS_FAILED); |
| 83 | + |
| 84 | + initial_jnt_momentum.data = jnt_mass_matrix.data * joint_velocity.data; |
| 85 | + |
| 86 | + // Reset data because of the new momentum offset |
| 87 | + SetToZero(estimated_momentum_integral); |
| 88 | + SetToZero(filtered_estimated_ext_torque); |
| 89 | + |
| 90 | + return (error = E_NOERROR); |
| 91 | +} |
| 92 | + |
| 93 | +// Sets singular-value eps parameter for the SVD calculation |
| 94 | +void ChainExternalWrenchEstimator::setSVDEps(const double eps_in) |
| 95 | +{ |
| 96 | + svd_eps = eps_in; |
| 97 | +} |
| 98 | + |
| 99 | +// Sets maximum iteration parameter for the SVD calculation |
| 100 | +void ChainExternalWrenchEstimator::setSVDMaxIter(const int maxiter_in) |
| 101 | +{ |
| 102 | + svd_maxiter = maxiter_in; |
| 103 | +} |
| 104 | + |
| 105 | +// This method calculates the external wrench that is applied on the robot's end-effector. |
| 106 | +int ChainExternalWrenchEstimator::JntToExtWrench(const JntArray &joint_position, const JntArray &joint_velocity, const JntArray &joint_torque, Wrench &external_wrench) |
| 107 | +{ |
| 108 | + /** |
| 109 | + * ========================================================================== |
| 110 | + * First-order momentum observer, an implementation based on: |
| 111 | + * S. Haddadin, A. De Luca and A. Albu-Schäffer, |
| 112 | + * "Robot Collisions: A Survey on Detection, Isolation, and Identification," |
| 113 | + * in IEEE Transactions on Robotics, vol. 33(6), pp. 1292-1312, 2017. |
| 114 | + * ========================================================================== |
| 115 | + */ |
| 116 | + |
| 117 | + // Check sizes first |
| 118 | + if (nj != CHAIN.getNrOfJoints() || ns != CHAIN.getNrOfSegments()) |
| 119 | + return (error = E_NOT_UP_TO_DATE); |
| 120 | + if (joint_position.rows() != nj || joint_velocity.rows() != nj || joint_torque.rows() != nj) |
| 121 | + return (error = E_SIZE_MISMATCH); |
| 122 | + |
| 123 | + /** |
| 124 | + * ================================================================================================================ |
| 125 | + * Part I: Estimation of the torques felt in joints as a result of the external wrench being applied on the robot |
| 126 | + * ================================================================================================================ |
| 127 | + */ |
| 128 | + |
| 129 | + // Calculate decomposed robot's dynamics |
| 130 | + if (E_NOERROR != dynparam_solver.JntToMass(joint_position, jnt_mass_matrix)) |
| 131 | + return (error = E_DYNPARAMSOLVERMASS_FAILED); |
| 132 | + |
| 133 | + if (E_NOERROR != dynparam_solver.JntToCoriolis(joint_position, joint_velocity, coriolis_torque)) |
| 134 | + return (error = E_DYNPARAMSOLVERCORIOLIS_FAILED); |
| 135 | + |
| 136 | + if (E_NOERROR != dynparam_solver.JntToGravity(joint_position, gravity_torque)) |
| 137 | + return (error = E_DYNPARAMSOLVERGRAVITY_FAILED); |
| 138 | + |
| 139 | + // Calculate the change of robot's inertia in the joint space |
| 140 | + jnt_mass_matrix_dot.data = (jnt_mass_matrix.data - previous_jnt_mass_matrix.data) / DT_SEC; |
| 141 | + |
| 142 | + // Save data for the next iteration |
| 143 | + previous_jnt_mass_matrix.data = jnt_mass_matrix.data; |
| 144 | + |
| 145 | + // Calculate total torque exerted on the joint |
| 146 | + total_torque.data = joint_torque.data - gravity_torque.data - coriolis_torque.data + jnt_mass_matrix_dot.data * joint_velocity.data; |
| 147 | + |
| 148 | + // Accumulate main integral |
| 149 | + estimated_momentum_integral.data += (total_torque.data + filtered_estimated_ext_torque.data) * DT_SEC; |
| 150 | + |
| 151 | + // Estimate external joint torque |
| 152 | + estimated_ext_torque.data = ESTIMATION_GAIN.asDiagonal() * (jnt_mass_matrix.data * joint_velocity.data - estimated_momentum_integral.data - initial_jnt_momentum.data); |
| 153 | + |
| 154 | + // First order low-pass filter: filter out the noise from the estimated signal |
| 155 | + // This filter can be turned off by setting FILTER_CONST value to 0 |
| 156 | + filtered_estimated_ext_torque.data = FILTER_CONST * filtered_estimated_ext_torque.data + (1.0 - FILTER_CONST) * estimated_ext_torque.data; |
| 157 | + |
| 158 | + /** |
| 159 | + * ================================================================================================================== |
| 160 | + * Part II: Propagate above-estimated joint torques to Cartesian wrench using a pseudo-inverse of Jacobian-Transpose |
| 161 | + * ================================================================================================================== |
| 162 | + */ |
| 163 | + |
| 164 | + // Compute robot's end-effector frame, expressed in the base frame |
| 165 | + Frame end_eff_frame; |
| 166 | + if (E_NOERROR != fk_pos_solver.JntToCart(joint_position, end_eff_frame)) |
| 167 | + return (error = E_FKSOLVERPOS_FAILED); |
| 168 | + |
| 169 | + // Compute robot's jacobian for the end-effector frame, expressed in the base frame |
| 170 | + if (E_NOERROR != jacobian_solver.JntToJac(joint_position, jacobian_end_eff)) |
| 171 | + return (error = E_JACSOLVER_FAILED); |
| 172 | + |
| 173 | + // Transform the jacobian from the base frame to the end-effector frame. |
| 174 | + // This part can be commented out if the user wants estimated wrench to be expressed w.r.t. base frame |
| 175 | + jacobian_end_eff.changeBase(end_eff_frame.M.Inverse()); // Jacobian is now expressed w.r.t. end-effector frame |
| 176 | + |
| 177 | + // SVD of "Jac^T" with maximum iterations "maxiter": Jac^T = U * S^-1 * V^T |
| 178 | + jacobian_end_eff_transpose = jacobian_end_eff.data.transpose(); |
| 179 | + if (E_NOERROR != svd_eigen_HH(jacobian_end_eff_transpose, U, S, V, tmp, svd_maxiter)) |
| 180 | + return (error = E_SVD_FAILED); |
| 181 | + |
| 182 | + // Invert singular values: S^-1 |
| 183 | + for (int i = 0; i < S.size(); ++i) |
| 184 | + S_inv(i) = (std::fabs(S(i)) < svd_eps) ? 0.0 : 1.0 / S(i); |
| 185 | + |
| 186 | + // Compose the inverse: (Jac^T)^-1 = V * S^-1 * U^T |
| 187 | + jacobian_end_eff_transpose_inv = V * S_inv.asDiagonal() * U.adjoint(); |
| 188 | + |
| 189 | + // Compute end-effector's Cartesian wrench from the estimated joint torques: (Jac^T)^-1 * ext_tau |
| 190 | + Vector6d estimated_wrench = jacobian_end_eff_transpose_inv * filtered_estimated_ext_torque.data; |
| 191 | + for (int i = 0; i < 6; i++) |
| 192 | + external_wrench(i) = estimated_wrench(i); |
| 193 | + |
| 194 | + return (error = E_NOERROR); |
| 195 | +} |
| 196 | + |
| 197 | +// Getter for the torques felt in the robot's joints due to the external wrench being applied on the robot |
| 198 | +void ChainExternalWrenchEstimator::getEstimatedJntTorque(JntArray &external_joint_torque) |
| 199 | +{ |
| 200 | + assert(external_joint_torque.rows() == filtered_estimated_ext_torque.rows()); |
| 201 | + external_joint_torque = filtered_estimated_ext_torque; |
| 202 | +} |
| 203 | + |
| 204 | +const char* ChainExternalWrenchEstimator::strError(const int error) const |
| 205 | +{ |
| 206 | + if (E_FKSOLVERPOS_FAILED == error) return "Internally-used Forward Position Kinematics (Recursive) solver failed"; |
| 207 | + else if (E_JACSOLVER_FAILED == error) return "Internally-used Jacobian solver failed"; |
| 208 | + else if (E_DYNPARAMSOLVERMASS_FAILED == error) return "Internally-used Dynamics Parameters (Mass) solver failed"; |
| 209 | + else if (E_DYNPARAMSOLVERCORIOLIS_FAILED == error) return "Internally-used Dynamics Parameters (Coriolis) solver failed"; |
| 210 | + else if (E_DYNPARAMSOLVERGRAVITY_FAILED == error) return "Internally-used Dynamics Parameters (Gravity) solver failed"; |
| 211 | + else return SolverI::strError(error); |
| 212 | +} |
| 213 | + |
| 214 | +} // namespace |
0 commit comments