Skip to content

Commit d682c9a

Browse files
feat: refactor controller state update + add regularization configurable parameter for inverse mass matrix computation (#40)
* Bump down python requirement * Fixed library expoert issues * Minor controller changes * Minor controller changes * Format CMakeLists * Revert parameters header inclusion * chore: add regularization to operational space pseudoinverse * fix: targets exported before dependencies (incorrecttly) * chore: revert back to nullspace regularization parameter * fix: dq assignment and num_joint usage * chore: Update docstrings for updateCurrentState --------- Co-authored-by: Daniel San José Pro <42489409+danielsanjosepro@users.noreply.github.com>
1 parent 36de0ac commit d682c9a

5 files changed

Lines changed: 79 additions & 89 deletions

File tree

CMakeLists.txt

Lines changed: 27 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -121,46 +121,45 @@ target_link_libraries(${PROJECT_NAME}
121121

122122
ament_target_dependencies(${PROJECT_NAME}
123123
PUBLIC
124-
controller_interface
125-
hardware_interface
126-
pluginlib
127-
rclcpp
128-
rclcpp_lifecycle
129-
pinocchio
130-
generate_parameter_library
131-
realtime_tools
124+
controller_interface
125+
hardware_interface
126+
pluginlib
127+
rclcpp
128+
rclcpp_lifecycle
129+
pinocchio
130+
generate_parameter_library
131+
realtime_tools
132132
)
133133

134134
pluginlib_export_plugin_description_file(
135135
controller_interface crisp_controllers.xml)
136136

137137
install(
138-
TARGETS
139-
${PROJECT_NAME}
140-
RUNTIME DESTINATION bin
141-
ARCHIVE DESTINATION lib
142-
LIBRARY DESTINATION lib
138+
TARGETS ${PROJECT_NAME}
139+
EXPORT export_${PROJECT_NAME}
140+
RUNTIME DESTINATION bin
141+
ARCHIVE DESTINATION lib
142+
LIBRARY DESTINATION lib
143143
)
144144

145145
install(
146-
DIRECTORY include/
147-
DESTINATION include
146+
DIRECTORY include/
147+
DESTINATION include
148148
)
149149

150-
ament_export_include_directories(
151-
include
152-
)
153-
ament_export_libraries(
154-
${PROJECT_NAME}
155-
)
156150
ament_export_dependencies(
157-
controller_interface
158-
pluginlib
159-
rclcpp
160-
rclcpp_lifecycle
161-
hardware_interface
162-
Eigen3
163-
)
151+
controller_interface
152+
pluginlib
153+
rclcpp
154+
rclcpp_lifecycle
155+
hardware_interface
156+
Eigen3
157+
pinocchio
158+
realtime_tools
159+
generate_parameter_library
160+
)
161+
162+
ament_export_targets(export_${PROJECT_NAME} HAS_LIBRARY_TARGET)
164163

165164
if(BUILD_TESTING)
166165
find_package(ament_cmake_gtest REQUIRED)

include/crisp_controllers/cartesian_controller.hpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,12 @@ class CartesianController : public controller_interface::ControllerInterface {
114114
*/
115115
void setStiffnessAndDamping();
116116

117+
/**
118+
* @brief Get the current state of the robot from hardware interfaces and update internal variables
119+
* @param initialize If set to true, initialize the exponential moving average filter with the current state
120+
*/
121+
void updateCurrentState(bool initialize = false);
122+
117123
/**
118124
* @brief Reads the target pose in realtime loop from the buffer and parses it to be used in the controller.
119125
*/
@@ -199,6 +205,10 @@ class CartesianController : public controller_interface::ControllerInterface {
199205
pinocchio::SE3 end_effector_pose;
200206
/** @brief End effector Jacobian matrix */
201207
pinocchio::Data::Matrix6x J;
208+
/** @brief End effector Jacobian matrix pseudoinverse */
209+
Eigen::MatrixXd J_pinv;
210+
/** @brief Joint-space identity matrix */
211+
Eigen::MatrixXd Id_nv;
202212

203213
/** @brief Friction parameters 1 of size nv */
204214
Eigen::VectorXd fp1;

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ name = "crisp-controllers"
33
version = "0.1.0"
44
description = "A collection of ROS2 controllers for the CRISP project."
55
readme = "README.md"
6-
requires-python = ">=3.11"
6+
requires-python = ">=3.10"
77
dependencies = [
88
"mkdocs>=1.6.1",
99
"mkdocs-material>=9.6.15",

src/cartesian_controller.cpp

Lines changed: 40 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -62,41 +62,11 @@ CartesianController::state_interface_configuration() const {
6262

6363
controller_interface::return_type
6464
CartesianController::update(const rclcpp::Time & time, const rclcpp::Duration & /*period*/) {
65-
size_t num_joints = params_.joints.size();
66-
for (size_t i = 0; i < num_joints; i++) {
67-
// TODO(placeholder): later it might be better to get this thing prepared in the
68-
// configuration part (not in the control loop)
69-
auto joint_name = params_.joints[i];
70-
auto joint_id = model_.getJointId(joint_name); // pinocchio joind id might be different
71-
auto joint = model_.joints[joint_id];
72-
73-
q_ref[i] = exponential_moving_average(q_ref[i], q_target[i], params_.filter.q_ref);
74-
75-
// Filtering joint position measurement 1 uses previous q, 0 uses new q from measurement.
76-
// q[i] = exponential_moving_average(q[i], state_interfaces_[i].get_value(), params_.filter.q);
77-
#if ROS2_VERSION_ABOVE_HUMBLE
78-
q[i] = exponential_moving_average(
79-
q[i], state_interfaces_[i].get_optional().value_or(q[i]), params_.filter.q);
80-
#else
81-
q[i] = exponential_moving_average(q[i], state_interfaces_[i].get_value(), params_.filter.q);
82-
#endif
83-
84-
if (continous_joint_types.count(joint.shortname())) { // Then we are handling a continous
85-
// joint that is SO(2)
86-
q_pin[joint.idx_q()] = std::cos(q[i]);
87-
q_pin[joint.idx_q() + 1] = std::sin(q[i]);
88-
} else { // simple revolute joint case
89-
q_pin[joint.idx_q()] = q[i];
90-
}
91-
#if ROS2_VERSION_ABOVE_HUMBLE
92-
dq[i] = exponential_moving_average(
93-
dq[i], state_interfaces_[num_joints + i].get_optional().value_or(dq[i]), params_.filter.dq);
94-
#else
95-
dq[i] = exponential_moving_average(
96-
dq[i], state_interfaces_[num_joints + i].get_value(), params_.filter.dq);
97-
#endif
98-
}
65+
66+
// Update current state information with EMA filtered values
67+
updateCurrentState();
9968

69+
// Check if new targets available
10070
if (new_target_pose_) {
10171
parse_target_pose_();
10272
new_target_pose_ = false;
@@ -147,15 +117,13 @@ CartesianController::update(const rclcpp::Time & time, const rclcpp::Duration &
147117
: pinocchio::ReferenceFrame::LOCAL_WORLD_ALIGNED;
148118
pinocchio::computeFrameJacobian(model_, data_, q_pin, end_effector_frame_id, reference_frame, J);
149119

150-
Eigen::MatrixXd J_pinv(model_.nv, 6);
151120
J_pinv = pseudo_inverse(J, params_.nullspace.regularization);
152-
Eigen::MatrixXd Id_nv = Eigen::MatrixXd::Identity(model_.nv, model_.nv);
153121
if (params_.nullspace.projector_type == "dynamic" || params_.use_operational_space) {
154122
pinocchio::computeMinverse(model_, data_, q_pin);
155123
data_.Minv.triangularView<Eigen::StrictlyLower>() =
156124
data_.Minv.transpose().triangularView<Eigen::StrictlyLower>();
157125
Mx_inv = J * data_.Minv * J.transpose();
158-
Mx = pseudo_inverse(Mx_inv);
126+
Mx = pseudo_inverse(Mx_inv, params_.operational_space_regularization);
159127
}
160128

161129
if (params_.nullspace.projector_type == "dynamic") {
@@ -221,7 +189,7 @@ CartesianController::update(const rclcpp::Time & time, const rclcpp::Duration &
221189
tau_d = exponential_moving_average(tau_d, tau_previous, params_.filter.output_torque);
222190

223191
if (!params_.stop_commands) {
224-
for (size_t i = 0; i < num_joints; ++i) {
192+
for (size_t i = 0; i < params_.joints.size(); ++i) {
225193
#if ROS2_VERSION_ABOVE_HUMBLE
226194
(void)command_interfaces_[i].set_value(tau_d[i]);
227195
#else
@@ -323,7 +291,8 @@ CartesianController::on_configure(const rclcpp_lifecycle::State & /*previous_sta
323291
return CallbackReturn::ERROR;
324292
}
325293
}
326-
294+
295+
// Preallocate the matrices and vectors that will be used in the control loop
327296
end_effector_frame_id = model_.getFrameId(params_.end_effector_frame);
328297
q = Eigen::VectorXd::Zero(model_.nv);
329298
q_pin = Eigen::VectorXd::Zero(model_.nq);
@@ -333,6 +302,8 @@ CartesianController::on_configure(const rclcpp_lifecycle::State & /*previous_sta
333302
dq_ref = Eigen::VectorXd::Zero(model_.nv);
334303
tau_previous = Eigen::VectorXd::Zero(model_.nv);
335304
J = Eigen::MatrixXd::Zero(6, model_.nv);
305+
J_pinv = Eigen::MatrixXd::Zero(model_.nv, 6);
306+
Id_nv = Eigen::MatrixXd::Identity(model_.nv, model_.nv);
336307

337308
// Map the friction parameters to Eigen vectors
338309
fp1 = Eigen::Map<Eigen::VectorXd>(params_.friction.fp1.data(), model_.nv);
@@ -491,41 +462,51 @@ void CartesianController::setStiffnessAndDamping() {
491462
}
492463
}
493464

494-
CallbackReturn
495-
CartesianController::on_activate(const rclcpp_lifecycle::State & /*previous_state*/) {
465+
void CartesianController::updateCurrentState(bool initialize) {
496466
auto num_joints = params_.joints.size();
497467
for (size_t i = 0; i < num_joints; i++) {
498-
// TODO(placeholder): later it might be better to get this thing prepared in the
499-
// configuration part (not in the control loop)
500468
auto joint_name = params_.joints[i];
501469
auto joint_id = model_.getJointId(joint_name); // pinocchio joind id might be different
502470
auto joint = model_.joints[joint_id];
503471

504472
#if ROS2_VERSION_ABOVE_HUMBLE
505-
q[i] = state_interfaces_[i].get_optional().value_or(q[i]);
473+
double q_meas = state_interfaces_[i].get_optional().value_or(q[i]);
474+
double dq_meas = state_interfaces_[num_joints + i].get_optional().value_or(dq[i]);
506475
#else
507-
q[i] = state_interfaces_[i].get_value();
476+
double q_meas = state_interfaces_[i].get_value();
477+
double dq_meas = state_interfaces_[num_joints + i].get_value();
508478
#endif
509-
if (joint.shortname() == "JointModelRZ") { // simple revolute joint case
510-
q_pin[joint.idx_q()] = q[i];
511-
} else if (continous_joint_types.count(
512-
joint.shortname())) { // Then we are handling a continous
513-
// joint that is SO(2)
479+
480+
q_ref[i] = initialize
481+
? q_meas
482+
: exponential_moving_average(q_ref[i], q_target[i], params_.filter.q_ref);
483+
484+
q[i] = initialize
485+
? q_meas
486+
: exponential_moving_average(q[i], q_meas, params_.filter.q);
487+
488+
if (continous_joint_types.count(joint.shortname())) { // Then we are handling a continous
489+
// joint that is SO(2)
514490
q_pin[joint.idx_q()] = std::cos(q[i]);
515491
q_pin[joint.idx_q() + 1] = std::sin(q[i]);
492+
} else { // simple revolute joint case
493+
q_pin[joint.idx_q()] = q[i];
516494
}
517495

518-
q_ref[i] = q[i];
519-
q_target[i] = q[i];
496+
dq[i] = initialize
497+
? dq_meas
498+
: exponential_moving_average(dq[i], dq_meas, params_.filter.dq);
520499

521-
#if ROS2_VERSION_ABOVE_HUMBLE
522-
dq[i] = state_interfaces_[num_joints + i].get_optional().value_or(dq[i]);
523-
dq_ref[i] = dq[i];
524-
#else
525-
dq[i] = state_interfaces_[num_joints + i].get_value();
526-
dq_ref[i] = state_interfaces_[num_joints + i].get_value();
527-
#endif
500+
q_target[i] = initialize ? q_meas : q_target[i];
528501
}
502+
}
503+
504+
CallbackReturn
505+
CartesianController::on_activate(const rclcpp_lifecycle::State & /*previous_state*/) {
506+
507+
// Update the current state with initial measurements (no EMA filtering)
508+
// to avoid large initial errors
509+
updateCurrentState(true);
529510

530511
pinocchio::forwardKinematics(model_, data_, q_pin, dq);
531512
pinocchio::updateFramePlacements(model_, data_);

src/cartesian_controller.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ cartesian_controller:
1919

2020
operational_space_regularization:
2121
type: double
22-
default_value: 0.1
22+
default_value: 0.01
2323
description: "Regularization (damping) for Mx pseudo-inverse. Higher values improve stability near singularities but reduce accuracy. Valid range is 0.001-1.0; typical useful values are in the 0.01-0.5 range."
2424
validation:
2525
bounds<>: [0.001, 1.0]

0 commit comments

Comments
 (0)