Modulo 5.0.0
Loading...
Searching...
No Matches
RobotControllerInterface.cpp
1#include "modulo_controllers/RobotControllerInterface.hpp"
2
3#include <ament_index_cpp/get_package_share_directory.hpp>
4#include <hardware_interface/types/hardware_interface_type_values.hpp>
5
6#include <state_representation/exceptions/JointNotFoundException.hpp>
7
8using namespace state_representation;
9
10namespace modulo_controllers {
11
12static const std::map<std::string, JointStateVariable> interface_map =// NOLINT(cert-err58-cpp)
13 {{hardware_interface::HW_IF_POSITION, JointStateVariable::POSITIONS},
14 {hardware_interface::HW_IF_VELOCITY, JointStateVariable::VELOCITIES},
15 {hardware_interface::HW_IF_ACCELERATION, JointStateVariable::ACCELERATIONS},
16 {hardware_interface::HW_IF_EFFORT, JointStateVariable::TORQUES}};
17
19
21 bool robot_model_required, const std::string& control_type, bool load_geometries)
22 : ControllerInterface(true),
23 control_type_(control_type),
24 control_type_fixed_(false),
25 robot_model_required_(robot_model_required),
26 load_geometries_(load_geometries),
27 new_joint_command_ready_(false),
28 command_decay_factor_(0.0),
29 command_rate_limit_(std::numeric_limits<double>::infinity()) {
30 if (!control_type.empty() && interface_map.find(control_type) == interface_map.cend()) {
31 RCLCPP_ERROR(get_node()->get_logger(), "Invalid control type: %s", control_type.c_str());
32 throw std::invalid_argument("Invalid control type: " + control_type);
33 }
34}
35
36rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn RobotControllerInterface::add_interfaces() {
38 std::make_shared<Parameter<std::string>>("task_space_frame"),
39 "The frame name in the robot model to use for kinematics calculations (defaults to the last frame in the "
40 "model)");
42 "sort_joints", true,
43 "If true, re-arrange the 'joints' parameter into a physically correct order according to the robot model");
45 std::make_shared<Parameter<std::string>>("ft_sensor_name"),
46 "Optionally, the name of a force-torque sensor in the hardware interface");
48 std::make_shared<Parameter<std::string>>("ft_sensor_reference_frame"),
49 "The reference frame of the force-torque sensor in the robot model");
51 "command_half_life", 0.1,
52 "A time constant for the exponential decay of the commanded velocity, acceleration or torque if no new command "
53 "is set");
55 "command_rate_limit", command_rate_limit_,
56 "The maximum allowable change in command on any interface expressed in command units / second");
57 return CallbackReturn::SUCCESS;
58}
59
60rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn RobotControllerInterface::on_configure() {
61 if (*get_parameter("robot_description")) {
62 std::stringstream timestamp;
63 timestamp << std::time(nullptr);
64 auto urdf_path = "/tmp/" + hardware_name_ + "_" + timestamp.str();
65 try {
66 robot_model::Model::create_urdf_from_string(get_parameter_value<std::string>("robot_description"), urdf_path);
67 if (load_geometries_) {
68 robot_ = std::make_shared<robot_model::Model>(hardware_name_, urdf_path, [](const std::string& package_name) {
69 return ament_index_cpp::get_package_share_directory(package_name) + "/";
70 });
71 RCLCPP_DEBUG(get_node()->get_logger(), "Generated robot model with collision features");
72 } else {
73 robot_ = std::make_shared<robot_model::Model>(hardware_name_, urdf_path);
74 RCLCPP_DEBUG(get_node()->get_logger(), "Generated robot model");
75 }
76 } catch (const std::exception& ex) {
77 RCLCPP_WARN(
78 get_node()->get_logger(),
79 "Could not generate robot model with temporary urdf from string content at path %s: %s", urdf_path.c_str(),
80 ex.what());
81 }
82 }
83 if (robot_model_required_ && robot_ == nullptr) {
84 RCLCPP_ERROR(get_node()->get_logger(), "Robot model is not available even though it's required by the controller.");
85 return CallbackReturn::ERROR;
86 }
87
88 if (robot_) {
89 if (get_parameter("task_space_frame")->is_empty()) {
90 task_space_frame_ = robot_->get_frames().back();
91 } else {
93 if (!robot_->get_pinocchio_model().existFrame(task_space_frame_)) {
94 RCLCPP_ERROR(
95 get_node()->get_logger(), "Provided task space frame %s does not exist in the robot model!",
96 task_space_frame_.c_str());
97 return CallbackReturn::ERROR;
98 }
99 }
100 }
101
102 auto joints = get_parameter("joints");
103 if (joints->is_empty() && !control_type_.empty()) {
104 RCLCPP_ERROR(
105 get_node()->get_logger(), "The 'joints' parameter is required for a non-zero control type %s!",
106 control_type_.c_str());
107 return CallbackReturn::ERROR;
108 }
109 joints_ = joints->get_parameter_value<std::vector<std::string>>();
110
111 // sort the interface joint names using the robot model if necessary
112 if (get_parameter_value<bool>("sort_joints") && robot_) {
113 if (joints_.size() != robot_->get_number_of_joints()) {
114 RCLCPP_WARN(
115 get_node()->get_logger(),
116 "The number of interface joints (%zu) does not match the number of joints in the robot model (%u)",
117 joints_.size(), robot_->get_number_of_joints());
118 }
119 std::vector<std::string> ordered_joints;
120 for (const auto& ordered_name : robot_->get_joint_frames()) {
121 for (const auto& joint_name : joints_) {
122 if (joint_name == ordered_name) {
123 ordered_joints.push_back(joint_name);
124 break;
125 }
126 }
127 }
128 if (ordered_joints.size() < joints_.size()) {
129 RCLCPP_ERROR(
130 get_node()->get_logger(), "%zu interface joints were not found in the robot model",
131 joints_.size() - ordered_joints.size());
132 return CallbackReturn::ERROR;
133 }
134 joints_ = ordered_joints;
135 }
136 joint_state_ = JointState(hardware_name_, joints_);
137
138 // set command interfaces from joints
139 if (!control_type_.empty()) {
140 if (control_type_ == hardware_interface::HW_IF_POSITION) {
141 previous_joint_command_values_ = std::vector<double>(joints_.size(), std::numeric_limits<double>::quiet_NaN());
142 } else {
143 previous_joint_command_values_ = std::vector<double>(joints_.size(), 0.0);
144 }
145 for (const auto& joint : joints_) {
146 add_command_interface(joint, control_type_);
147 }
148 control_type_fixed_ = true;
149 }
150
151 auto ft_sensor_name = get_parameter("ft_sensor_name");
152 if (!ft_sensor_name->is_empty()) {
153 ft_sensor_name_ = ft_sensor_name->get_parameter_value<std::string>();
154 auto ft_sensor_reference_frame = get_parameter("ft_sensor_reference_frame");
155 if (ft_sensor_reference_frame->is_empty()) {
156 RCLCPP_ERROR(
157 get_node()->get_logger(),
158 "The 'ft_sensor_reference_frame' parameter cannot be empty if a force-torque sensor is specified!");
159 return CallbackReturn::ERROR;
160 }
161 ft_sensor_reference_frame_ = ft_sensor_reference_frame->get_parameter_value<std::string>();
162 // TODO check that there is no joint in between
163 if (robot_ != nullptr && !robot_->get_pinocchio_model().existFrame(ft_sensor_reference_frame_)) {
164 RCLCPP_ERROR(
165 get_node()->get_logger(), "The FT sensor reference frame '%s' does not exist on the robot model!",
167 return CallbackReturn::ERROR;
168 }
169 }
170
171 command_decay_factor_ = -log(0.5) / get_parameter_value<double>("command_half_life");
172 command_rate_limit_ = get_parameter_value<double>("command_rate_limit");
173
174 RCLCPP_DEBUG(get_node()->get_logger(), "Configuration of RobotControllerInterface successful");
175 return CallbackReturn::SUCCESS;
176}
177
178rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn RobotControllerInterface::on_activate() {
179 // initialize a force torque sensor state if applicable
180 if (!ft_sensor_name_.empty()) {
181 try {
188 } catch (const std::out_of_range&) {
189 RCLCPP_ERROR(
190 get_node()->get_logger(), "The force torque sensor '%s' does not have all the expected state interfaces!",
191 ft_sensor_name_.c_str());
192 return CallbackReturn::FAILURE;
193 }
194 ft_sensor_ = CartesianWrench(ft_sensor_name_, ft_sensor_reference_frame_);
195 }
196
197 RCLCPP_DEBUG(get_node()->get_logger(), "Activation of RobotControllerInterface successful");
198 return CallbackReturn::SUCCESS;
199}
200
201rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn RobotControllerInterface::on_deactivate() {
202 if (!control_type_.empty() && control_type_ != hardware_interface::HW_IF_POSITION) {
203 std::fill(previous_joint_command_values_.begin(), previous_joint_command_values_.end(), 0.0);
204 }
205 RCLCPP_DEBUG(get_node()->get_logger(), "Deactivation of RobotControllerInterface successful");
206 return CallbackReturn::SUCCESS;
207}
208
209controller_interface::return_type RobotControllerInterface::read_state_interfaces() {
211 if (status != controller_interface::return_type::OK) {
212 return status;
213 }
214
215 if (joint_state_.get_size() > 0 && joint_state_.is_empty()) {
216 RCLCPP_DEBUG(get_node()->get_logger(), "Reading first joint state");
217 joint_state_.set_zero();
218 }
219
220 for (const auto& joint : joints_) {
221 auto data = get_state_interfaces(joint);
222 unsigned int joint_index;
223 try {
224 joint_index = joint_state_.get_joint_index(joint);
225 } catch (exceptions::JointNotFoundException& ex) {
226 RCLCPP_ERROR_STREAM_THROTTLE(get_node()->get_logger(), *get_node()->get_clock(), 1000, ex.what());
227 return controller_interface::return_type::ERROR;
228 }
229 for (const auto& [interface, value] : data) {
230 if (!std::isfinite(value)) {
231 RCLCPP_ERROR_THROTTLE(
232 get_node()->get_logger(), *get_node()->get_clock(), 1000, "Value of state interface '%s/%s' is not finite",
233 joint.c_str(), interface.c_str());
234 return controller_interface::return_type::ERROR;
235 }
236 switch (interface_map.at(interface)) {
237 case JointStateVariable::POSITIONS:
238 joint_state_.set_position(value, joint_index);
239 break;
240 case JointStateVariable::VELOCITIES:
241 joint_state_.set_velocity(value, joint_index);
242 break;
243 case JointStateVariable::ACCELERATIONS:
244 joint_state_.set_acceleration(value, joint_index);
245 break;
246 case JointStateVariable::TORQUES:
247 joint_state_.set_torque(value, joint_index);
248 break;
249 default:
250 break;
251 }
252 }
253 }
254
255 if (!ft_sensor_name_.empty()) {
256 const auto& ft_sensor = get_state_interfaces(ft_sensor_name_);
257 std::vector<double> wrench = {ft_sensor.at("force.x"), ft_sensor.at("force.y"), ft_sensor.at("force.z"),
258 ft_sensor.at("torque.x"), ft_sensor.at("torque.y"), ft_sensor.at("torque.z")};
259 if (std::any_of(wrench.begin(), wrench.end(), [](double v) { return !std::isfinite(v); })) {
260 RCLCPP_ERROR_THROTTLE(
261 get_node()->get_logger(), *get_node()->get_clock(), 1000,
262 "Found non-finite values in the force torque sensor '%s' state interfaces", ft_sensor_name_.c_str());
263 return controller_interface::return_type::ERROR;
264 }
265 ft_sensor_.set_wrench(wrench);
266 }
267
268 return controller_interface::return_type::OK;
269}
270
271controller_interface::return_type RobotControllerInterface::write_command_interfaces(const rclcpp::Duration& period) {
272 if (!control_type_.empty()) {
273 double decay = 1.0 - command_decay_factor_ * period.seconds();
274 decay = std::max(0.0, decay);
275 double rate_limit = command_rate_limit_ * period.seconds();
276
277 auto joint_command_values = joint_command_values_.readFromRT();
278 if (joint_command_values == nullptr) {
279 new_joint_command_ready_ = false;
280 }
281
282 for (std::size_t index = 0; index < joints_.size(); ++index) {
283 double previous_command = previous_joint_command_values_.at(index);
284 if (new_joint_command_ready_) {
285 auto new_command = joint_command_values->at(index);
286 if (std::isfinite(previous_command) && std::abs(new_command - previous_command) > rate_limit) {
287 double command_offset = new_command - previous_command > 0.0 ? rate_limit : -rate_limit;
288 new_command = previous_command + command_offset;
289 }
290 set_command_interface(joints_.at(index), control_type_, new_command);
291 previous_joint_command_values_.at(index) = new_command;
292 } else if (control_type_ != hardware_interface::HW_IF_POSITION) {
293 auto new_command = previous_command * decay;
294 set_command_interface(joints_.at(index), control_type_, new_command);
295 previous_joint_command_values_.at(index) = new_command;
296 }
297 }
298 new_joint_command_ready_ = false;
299 }
300
302}
303
305 return joint_state_;
306}
307
309 // TODO check if recompute is necessary?
311 return cartesian_state_;
312}
313
315 return ft_sensor_;
316}
317
319 if (robot_ != nullptr) {
320 cartesian_state_ = robot_->forward_kinematics(joint_state_, task_space_frame_);
321 cartesian_state_.set_twist(robot_->forward_velocity(joint_state_, task_space_frame_).get_twist());
322
323 if (!ft_sensor_.is_empty() && (ft_sensor_.get_reference_frame() == cartesian_state_.get_name())) {
324 auto ft_sensor_in_robot_frame = cartesian_state_ * ft_sensor_;
325 cartesian_state_.set_wrench(ft_sensor_in_robot_frame.get_wrench());
326 }
327 }
328}
329
330void RobotControllerInterface::set_joint_command(const JointState& joint_command) {
331 if (!joint_command) {
332 RCLCPP_DEBUG(get_node()->get_logger(), "set_joint_command called with an empty JointState");
333 return;
334 }
335
336 std::vector<double> joint_command_values;
337 joint_command_values.reserve(joints_.size());
338
339 for (const auto& joint : joints_) {
340 try {
341 switch (interface_map.at(control_type_)) {
342 case JointStateVariable::POSITIONS:
343 joint_command_values.push_back(joint_command.get_position(joint));
344 break;
345 case JointStateVariable::VELOCITIES:
346 joint_command_values.push_back(joint_command.get_velocity(joint));
347 break;
348 case JointStateVariable::ACCELERATIONS:
349 joint_command_values.push_back(joint_command.get_acceleration(joint));
350 break;
351 case JointStateVariable::TORQUES:
352 joint_command_values.push_back(joint_command.get_torque(joint));
353 break;
354 default:
355 break;
356 }
357 } catch (const state_representation::exceptions::JointNotFoundException& ex) {
358 RCLCPP_WARN_STREAM_THROTTLE(get_node()->get_logger(), *get_node()->get_clock(), 1000, ex.what());
359 }
360 }
361
362 if (joint_command_values.size() == joints_.size()) {
363 joint_command_values_.writeFromNonRT(joint_command_values);
364 new_joint_command_ready_ = true;
365 } else {
366 RCLCPP_WARN_THROTTLE(
367 get_node()->get_logger(), *get_node()->get_clock(), 1000,
368 "Unable to set command values from provided joint command");
369 }
370}
371
372bool RobotControllerInterface::on_validate_parameter_callback(const std::shared_ptr<ParameterInterface>& parameter) {
373 if ((parameter->get_name() == "command_half_life" || parameter->get_name() == "command_rate_limit")
374 && parameter->get_parameter_value<double>() < 0.0) {
375 RCLCPP_ERROR(
376 get_node()->get_logger(), "Parameter value of '%s' should be greater than 0", parameter->get_name().c_str());
377 return false;
378 }
379 return true;
380}
381
383 return control_type_;
384}
385
386void RobotControllerInterface::set_control_type(const std::string& control_type) {
387 if (control_type_fixed_) {
388 throw std::runtime_error("Control type is fixed and cannot be changed anymore");
389 }
390 if (!control_type.empty() && interface_map.find(control_type) == interface_map.cend()) {
391 throw std::runtime_error("Invalid control type: " + control_type);
392 }
393 control_type_ = control_type;
394}
395
396}// namespace modulo_controllers
std::shared_ptr< state_representation::ParameterInterface > get_parameter(const std::string &name) const
Get a parameter by name.
T get_parameter_value(const std::string &name) const
Get a parameter value by name.
void add_parameter(const std::shared_ptr< state_representation::ParameterInterface > &parameter, const std::string &description, bool read_only=false)
Add a parameter.
virtual controller_interface::return_type write_command_interfaces(const rclcpp::Duration &period)
Write the command interfaces.
virtual controller_interface::return_type read_state_interfaces()
Read the state interfaces.
std::unordered_map< std::string, double > get_state_interfaces(const std::string &name) const
Get a map containing the state interfaces by name of the parent tag.
double get_state_interface(const std::string &name, const std::string &interface) const
Get the value of a state interface by name.
void set_command_interface(const std::string &name, const std::string &interface, double value)
Set the value of a command interface by name.
std::string hardware_name_
The hardware name provided by a parameter.
void add_command_interface(const std::string &name, const std::string &interface)
Add a command interface to the controller by name.
Base controller class that automatically associates joints with a JointState object.
std::string ft_sensor_name_
The name of a force torque sensor in the hardware description.
const state_representation::CartesianState & get_cartesian_state()
Access the Cartesian state object.
CallbackReturn on_activate() override
Activate the controller.
bool on_validate_parameter_callback(const std::shared_ptr< state_representation::ParameterInterface > &parameter) override
Parameter validation function to be redefined by derived controller classes.
const state_representation::JointState & get_joint_state()
Access the joint state object.
std::string ft_sensor_reference_frame_
The sensing reference frame.
CallbackReturn on_deactivate() override
Deactivate the controller.
void set_control_type(const std::string &control_type)
Set the control type of the controller after construction.
const state_representation::CartesianWrench & get_ft_sensor()
Access the Cartesian wrench object.
std::shared_ptr< robot_model::Model > robot_
Robot model object generated from URDF.
void compute_cartesian_state()
Compute the Cartesian state from forward kinematics of the current joint state.
void set_joint_command(const state_representation::JointState &joint_command)
Set the joint command object.
CallbackReturn add_interfaces() override
Add interfaces like parameters, signals, services, and predicates to the controller.
std::string task_space_frame_
The frame in task space for forward kinematics calculations, if applicable.
CallbackReturn on_configure() override
Configure the controller.
std::string get_control_type() const
Get the control type of the controller, empty or one of [position, velocity, effort or acceleration].