moveit2
The MoveIt Motion Planning Framework for ROS 2.
Loading...
Searching...
No Matches
servo_node.cpp
Go to the documentation of this file.
1/*******************************************************************************
2 * BSD 3-Clause License
3 *
4 * Copyright (c) 2019, Los Alamos National Security, LLC
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions are met:
9 *
10 * * Redistributions of source code must retain the above copyright notice, this
11 * list of conditions and the following disclaimer.
12 *
13 * * Redistributions in binary form must reproduce the above copyright notice,
14 * this list of conditions and the following disclaimer in the documentation
15 * and/or other materials provided with the distribution.
16 *
17 * * Neither the name of the copyright holder nor the names of its
18 * contributors may be used to endorse or promote products derived from
19 * this software without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE
25 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
26 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
28 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 *******************************************************************************/
33
34/* Title : servo_node.cpp
35 * Project : moveit_servo
36 * Created : 12/31/2018
37 * Author : Andy Zelenak, V Mohammed Ibrahim
38 *
39 */
40
41#if __has_include(<realtime_tools/realtime_helpers.hpp>)
42#include <realtime_tools/realtime_helpers.hpp>
43#else
44#include <realtime_tools/thread_priority.hpp>
45#endif
46
49
50namespace moveit_servo
51{
52
53namespace
54{
55// This function is used to convert times in case planning_scene_monitor uses RCL_SYSTEM_TIME
56rclcpp::Time convertClockType(const rclcpp::Time& time, rcl_clock_type_t new_clock_type)
57{
58 if (time.get_clock_type() != new_clock_type)
59 {
60 return rclcpp::Time(time.nanoseconds(), new_clock_type);
61 }
62 return time;
63}
64} // namespace
65
66rclcpp::node_interfaces::NodeBaseInterface::SharedPtr ServoNode::get_node_base_interface()
67{
68 return node_->get_node_base_interface();
69}
70
72{
73 stop_servo_ = true;
74 if (servo_loop_thread_.joinable())
75 servo_loop_thread_.join();
76}
77
78ServoNode::ServoNode(const rclcpp::NodeOptions& options)
79 : node_{ std::make_shared<rclcpp::Node>("servo_node", options) }
80 , stop_servo_{ false }
81 , servo_paused_{ false }
82 , new_joint_jog_msg_{ false }
83 , new_twist_msg_{ false }
84 , new_pose_msg_{ false }
85{
86 moveit::setNodeLoggerName(node_->get_name());
87
88 // Configure SCHED_FIFO and priority
89 if (realtime_tools::configure_sched_fifo(servo_params_.thread_priority))
90 {
91 RCLCPP_INFO_STREAM(node_->get_logger(), "Enabled SCHED_FIFO and higher thread priority.");
92 }
93 else
94 {
95 RCLCPP_WARN_STREAM(node_->get_logger(), "Could not enable FIFO RT scheduling policy. Continuing with the default.");
96 }
97
98 // Check if a realtime kernel is available
99 if (!realtime_tools::has_realtime_kernel())
100 {
101 RCLCPP_WARN_STREAM(node_->get_logger(), "Realtime kernel is recommended for better performance.");
102 }
103
104 std::shared_ptr<servo::ParamListener> servo_param_listener =
105 std::make_shared<servo::ParamListener>(node_, "moveit_servo");
106
107 // Create Servo instance
108 planning_scene_monitor_ = createPlanningSceneMonitor(node_, servo_param_listener->get_params());
109 servo_ = std::make_unique<Servo>(node_, servo_param_listener, planning_scene_monitor_);
110
111 servo_params_ = servo_->getParams();
112
113 // Create subscriber for jointjog
114 joint_jog_subscriber_ = node_->create_subscription<control_msgs::msg::JointJog>(
115 servo_params_.joint_command_in_topic, rclcpp::SystemDefaultsQoS(),
116 [this](const control_msgs::msg::JointJog::ConstSharedPtr& msg) { return jointJogCallback(msg); });
117
118 // Create subscriber for twist
119 twist_subscriber_ = node_->create_subscription<geometry_msgs::msg::TwistStamped>(
120 servo_params_.cartesian_command_in_topic, rclcpp::SystemDefaultsQoS(),
121 [this](const geometry_msgs::msg::TwistStamped::ConstSharedPtr& msg) { return twistCallback(msg); });
122
123 // Create subscriber for pose
124 pose_subscriber_ = node_->create_subscription<geometry_msgs::msg::PoseStamped>(
125 servo_params_.pose_command_in_topic, rclcpp::SystemDefaultsQoS(),
126 [this](const geometry_msgs::msg::PoseStamped::ConstSharedPtr& msg) { return poseCallback(msg); });
127
128 if (servo_params_.command_out_type == "trajectory_msgs/JointTrajectory")
129 {
130 trajectory_publisher_ = node_->create_publisher<trajectory_msgs::msg::JointTrajectory>(
131 servo_params_.command_out_topic, rclcpp::SystemDefaultsQoS());
132 }
133 else if (servo_params_.command_out_type == "std_msgs/Float64MultiArray")
134 {
135 multi_array_publisher_ = node_->create_publisher<std_msgs::msg::Float64MultiArray>(servo_params_.command_out_topic,
136 rclcpp::SystemDefaultsQoS());
137 }
138 // Create status publisher
139 status_publisher_ =
140 node_->create_publisher<moveit_msgs::msg::ServoStatus>(servo_params_.status_topic, rclcpp::SystemDefaultsQoS());
141
142 // Create service to enable switching command type
143 switch_command_type_ = node_->create_service<moveit_msgs::srv::ServoCommandType>(
144 "~/switch_command_type", [this](const std::shared_ptr<moveit_msgs::srv::ServoCommandType::Request>& request,
145 const std::shared_ptr<moveit_msgs::srv::ServoCommandType::Response>& response) {
146 return switchCommandType(request, response);
147 });
148
149 // Create service to pause/unpause servoing
150 pause_servo_ = node_->create_service<std_srvs::srv::SetBool>(
151 "~/pause_servo", [this](const std::shared_ptr<std_srvs::srv::SetBool::Request>& request,
152 const std::shared_ptr<std_srvs::srv::SetBool::Response>& response) {
153 return pauseServo(request, response);
154 });
155
156 // Start the servoing loop
157 servo_loop_thread_ = std::thread(&ServoNode::servoLoop, this);
158}
159
160void ServoNode::pauseServo(const std::shared_ptr<std_srvs::srv::SetBool::Request>& request,
161 const std::shared_ptr<std_srvs::srv::SetBool::Response>& response)
162{
163 if (servo_paused_ == request->data)
164 {
165 std::string message = "Requested pause state is already active.";
166 RCLCPP_INFO(node_->get_logger(), "%s", message.c_str());
167 response->success = true;
168 response->message = message;
169 return;
170 }
171 std::lock_guard<std::mutex> lock_guard(lock_);
172 servo_paused_ = request->data;
173 response->success = (servo_paused_ == request->data);
174 if (servo_paused_)
175 {
176 servo_->setCollisionChecking(false);
177 response->message = "Servoing disabled";
178 }
179 else
180 {
181 // Reset the smoothing plugin with the robot's current state in case the robot moved between pausing and unpausing.
182 last_commanded_state_ = servo_->getCurrentRobotState(true /* block for current robot state */);
183 servo_->resetSmoothing(last_commanded_state_);
184
185 // clear out the command rolling window and reset last commanded state to be the current state
186 joint_cmd_rolling_window_.clear();
187
188 // reactivate collision checking
189 servo_->setCollisionChecking(true);
190 response->message = "Servoing enabled";
191 }
192}
193
194void ServoNode::switchCommandType(const std::shared_ptr<moveit_msgs::srv::ServoCommandType::Request>& request,
195 const std::shared_ptr<moveit_msgs::srv::ServoCommandType::Response>& response)
196{
197 const bool is_valid = (request->command_type >= static_cast<int8_t>(CommandType::MIN)) &&
198 (request->command_type <= static_cast<int8_t>(CommandType::MAX));
199 if (is_valid)
200 {
201 servo_->setCommandType(static_cast<CommandType>(request->command_type));
202 }
203 else
204 {
205 RCLCPP_WARN_STREAM(node_->get_logger(), "Unknown command type " << request->command_type << " requested");
206 }
207 response->success = (request->command_type == static_cast<int8_t>(servo_->getCommandType()));
208}
209
210void ServoNode::jointJogCallback(const control_msgs::msg::JointJog::ConstSharedPtr& msg)
211{
212 latest_joint_jog_ = *msg;
213 new_joint_jog_msg_ = true;
214}
215
216void ServoNode::twistCallback(const geometry_msgs::msg::TwistStamped::ConstSharedPtr& msg)
217{
218 latest_twist_ = *msg;
219 new_twist_msg_ = true;
220}
221
222void ServoNode::poseCallback(const geometry_msgs::msg::PoseStamped::ConstSharedPtr& msg)
223{
224 latest_pose_ = *msg;
225 new_pose_msg_ = true;
226}
227
228std::optional<KinematicState> ServoNode::processJointJogCommand(const moveit::core::RobotStatePtr& robot_state)
229{
230 std::optional<KinematicState> next_joint_state = std::nullopt;
231 // Reject any other command types that had arrived simultaneously.
232 new_twist_msg_ = new_pose_msg_ = false;
233
234 if (!latest_joint_jog_.displacements.empty())
235 {
236 RCLCPP_WARN(node_->get_logger(), "Joint jog command displacements field is not yet supported, ignoring.");
237 latest_joint_jog_.displacements.clear(); // Only warn once per message.
238 }
239
240 const bool command_stale = (node_->now() - latest_joint_jog_.header.stamp) >=
241 rclcpp::Duration::from_seconds(servo_params_.incoming_command_timeout);
242 if (!command_stale)
243 {
244 JointJogCommand command{ latest_joint_jog_.joint_names, latest_joint_jog_.velocities };
245 next_joint_state = servo_->getNextJointState(robot_state, command);
246 // If the command failed, stop trying to process this message
247 if (servo_->getStatus() == StatusCode::INVALID)
248 {
249 new_joint_jog_msg_ = false;
250 }
251 }
252 else
253 {
254 auto result = servo_->smoothHalt(last_commanded_state_);
255 new_joint_jog_msg_ = !result.first;
256 if (new_joint_jog_msg_)
257 {
258 next_joint_state = result.second;
259 RCLCPP_DEBUG_STREAM(node_->get_logger(), "Joint jog command timed out. Halting to a stop.");
260 }
261 }
262
263 return next_joint_state;
264}
265
266std::optional<KinematicState> ServoNode::processTwistCommand(const moveit::core::RobotStatePtr& robot_state)
267{
268 std::optional<KinematicState> next_joint_state = std::nullopt;
269
270 // Mark latest twist command as processed.
271 // Reject any other command types that had arrived simultaneously.
272 new_joint_jog_msg_ = new_pose_msg_ = false;
273
274 const bool command_stale = (node_->now() - latest_twist_.header.stamp) >=
275 rclcpp::Duration::from_seconds(servo_params_.incoming_command_timeout);
276 if (!command_stale)
277 {
278 const Eigen::Vector<double, 6> velocities{ latest_twist_.twist.linear.x, latest_twist_.twist.linear.y,
279 latest_twist_.twist.linear.z, latest_twist_.twist.angular.x,
280 latest_twist_.twist.angular.y, latest_twist_.twist.angular.z };
281 const TwistCommand command{ latest_twist_.header.frame_id, velocities };
282 next_joint_state = servo_->getNextJointState(robot_state, command);
283 if (servo_->getStatus() == StatusCode::INVALID)
284 {
285 new_twist_msg_ = false;
286 }
287 }
288 else
289 {
290 auto result = servo_->smoothHalt(last_commanded_state_);
291 new_twist_msg_ = !result.first;
292 if (new_twist_msg_)
293 {
294 next_joint_state = result.second;
295 RCLCPP_DEBUG_STREAM(node_->get_logger(), "Twist command timed out. Halting to a stop.");
296 }
297 }
298
299 return next_joint_state;
300}
301
302std::optional<KinematicState> ServoNode::processPoseCommand(const moveit::core::RobotStatePtr& robot_state)
303{
304 std::optional<KinematicState> next_joint_state = std::nullopt;
305
306 // Mark latest pose command as processed.
307 // Reject any other command types that had arrived simultaneously.
308 new_joint_jog_msg_ = new_twist_msg_ = false;
309
310 const bool command_stale = (node_->now() - latest_pose_.header.stamp) >=
311 rclcpp::Duration::from_seconds(servo_params_.incoming_command_timeout);
312 if (!command_stale)
313 {
314 const PoseCommand command = poseFromPoseStamped(latest_pose_);
315 next_joint_state = servo_->getNextJointState(robot_state, command);
316 if (servo_->getStatus() == StatusCode::INVALID)
317 {
318 new_pose_msg_ = false;
319 }
320 }
321 else
322 {
323 auto result = servo_->smoothHalt(last_commanded_state_);
324 new_pose_msg_ = !result.first;
325 if (new_pose_msg_)
326 {
327 next_joint_state = result.second;
328 RCLCPP_DEBUG_STREAM(node_->get_logger(), "Pose command timed out. Halting to a stop.");
329 }
330 }
331
332 return next_joint_state;
333}
334
335void ServoNode::servoLoop()
336{
337 moveit_msgs::msg::ServoStatus status_msg;
338 std::optional<KinematicState> next_joint_state = std::nullopt;
339 rclcpp::WallRate servo_frequency(1 / servo_params_.publish_period);
340
341 const auto servo_node_start = node_->now();
342
343 // convert_clock_type() is used in case planning_scene_monitor uses RCL_SYSTEM_TIME
344 // while Servo uses RCL_ROS_TIME
345 while (servo_node_start >
346 convertClockType(planning_scene_monitor_->getLastUpdateTime(), servo_node_start.get_clock_type()))
347 {
348 RCLCPP_INFO(node_->get_logger(), "Waiting for planning scene monitor to receive robot state update.");
349 rclcpp::sleep_for(std::chrono::seconds(1));
350 }
351 {
352 RCLCPP_INFO(node_->get_logger(), "Waiting to receive robot state update.");
353 rclcpp::sleep_for(std::chrono::seconds(1));
354 }
355 KinematicState current_state = servo_->getCurrentRobotState(true /* block for current robot state */);
356 last_commanded_state_ = current_state;
357 // Ensure the filter is up to date
358 servo_->resetSmoothing(current_state);
359
360 // Get the robot state and joint model group info.
361 moveit::core::RobotStatePtr robot_state = planning_scene_monitor_->getStateMonitor()->getCurrentState();
362 const moveit::core::JointModelGroup* joint_model_group =
363 robot_state->getJointModelGroup(servo_params_.move_group_name);
364
365 while (rclcpp::ok() && !stop_servo_)
366 {
367 // Skip processing if servoing is disabled.
368 if (servo_paused_)
369 {
370 servo_->resetSmoothing(current_state);
371 servo_frequency.sleep();
372 continue;
373 }
374
375 { // scope for mutex-protected operations
376 std::lock_guard<std::mutex> lock_guard(lock_);
377 const bool use_trajectory = servo_params_.command_out_type == "trajectory_msgs/JointTrajectory";
378 const auto cur_time = node_->now();
379
380 if (use_trajectory && !joint_cmd_rolling_window_.empty() && joint_cmd_rolling_window_.back().time_stamp > cur_time)
381 {
382 current_state = joint_cmd_rolling_window_.back();
383 }
384 else
385 {
386 // if all joint_cmd_rolling_window_ is empty or all commands in it are outdated, use current robot state
387 joint_cmd_rolling_window_.clear();
388 current_state = servo_->getCurrentRobotState(false /* block for current robot state */);
389 current_state.velocities *= 0.0;
390 }
391
392 // update robot state values
393 robot_state->setJointGroupPositions(joint_model_group, current_state.positions);
394 robot_state->setJointGroupVelocities(joint_model_group, current_state.velocities);
395
396 next_joint_state = std::nullopt;
397 const CommandType expected_type = servo_->getCommandType();
398
399 if (expected_type == CommandType::JOINT_JOG && new_joint_jog_msg_)
400 {
401 next_joint_state = processJointJogCommand(robot_state);
402 }
403 else if (expected_type == CommandType::TWIST && new_twist_msg_)
404 {
405 next_joint_state = processTwistCommand(robot_state);
406 }
407 else if (expected_type == CommandType::POSE && new_pose_msg_)
408 {
409 next_joint_state = processPoseCommand(robot_state);
410 }
411 else if (new_joint_jog_msg_ || new_twist_msg_ || new_pose_msg_)
412 {
413 new_joint_jog_msg_ = new_twist_msg_ = new_pose_msg_ = false;
414 RCLCPP_WARN_STREAM(node_->get_logger(), "Command type has not been set, cannot accept input");
415 }
416
417 if (next_joint_state && (servo_->getStatus() != StatusCode::INVALID) &&
418 (servo_->getStatus() != StatusCode::HALT_FOR_COLLISION))
419 {
420 if (use_trajectory)
421 {
422 auto& next_joint_state_value = next_joint_state.value();
423 updateSlidingWindow(next_joint_state_value, joint_cmd_rolling_window_, servo_params_.max_expected_latency,
424 cur_time);
425 if (const auto msg = composeTrajectoryMessage(servo_params_, joint_cmd_rolling_window_))
426 {
427 trajectory_publisher_->publish(msg.value());
428 }
429 }
430 else
431 {
432 multi_array_publisher_->publish(composeMultiArrayMessage(servo_->getParams(), next_joint_state.value()));
433 }
434 last_commanded_state_ = next_joint_state.value();
435 }
436 else
437 {
438 // if no new command was created, use current robot state
439 last_commanded_state_ = current_state = servo_->getCurrentRobotState(false);
440 updateSlidingWindow(current_state, joint_cmd_rolling_window_, servo_params_.max_expected_latency, cur_time);
441 servo_->resetSmoothing(current_state);
442 }
443
444 status_msg.code = static_cast<int8_t>(servo_->getStatus());
445 status_msg.message = servo_->getStatusMessage();
446 status_publisher_->publish(status_msg);
447 }
448
449 servo_frequency.sleep();
450 }
451}
452
453} // namespace moveit_servo
454
455#include "rclcpp_components/register_node_macro.hpp"
456RCLCPP_COMPONENTS_REGISTER_NODE(moveit_servo::ServoNode)
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr get_node_base_interface()
ServoNode(const rclcpp::NodeOptions &options)
std::optional< trajectory_msgs::msg::JointTrajectory > composeTrajectoryMessage(const servo::Params &servo_params, const std::deque< KinematicState > &joint_cmd_rolling_window)
Create a trajectory message from a rolling window queue of joint state commands. Method optionally re...
Definition common.cpp:151
std_msgs::msg::Float64MultiArray composeMultiArrayMessage(const servo::Params &servo_params, const KinematicState &joint_state)
Create a Float64MultiArray message from given joint state.
Definition common.cpp:258
void updateSlidingWindow(KinematicState &next_joint_state, std::deque< KinematicState > &joint_cmd_rolling_window, double max_expected_latency, const rclcpp::Time &cur_time)
Adds a new joint state command to a queue containing commands over a time window. Also modifies the v...
Definition common.cpp:203
PoseCommand poseFromPoseStamped(const geometry_msgs::msg::PoseStamped &msg)
Convert a PoseStamped message to a Servo Pose.
Definition common.cpp:464
planning_scene_monitor::PlanningSceneMonitorPtr createPlanningSceneMonitor(const rclcpp::Node::SharedPtr &node, const servo::Params &servo_params)
Creates the planning scene monitor used by servo.
Definition common.cpp:480
void setNodeLoggerName(const std::string &name)
Call once after creating a node to initialize logging namespaces.
Definition logger.cpp:73