SMACC2
Loading...
Searching...
No Matches
cb_move_end_effector_trajectory.hpp
Go to the documentation of this file.
1// Copyright 2025 Robosoft Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15/*****************************************************************************************************************
16 *
17 * Authors: Pablo Inigo Blasco, Brett Aldrich
18 *
19 *****************************************************************************************************************/
20
21#pragma once
22
23#include <tf2/transform_datatypes.h>
24#include <tf2_ros/transform_listener.h>
31#include <moveit_msgs/srv/get_position_ik.hpp>
32#include <visualization_msgs/msg/marker_array.hpp>
33
34using namespace std::chrono_literals;
35
36namespace cl_moveit2z
37{
38template <typename AsyncCB, typename Orthogonal>
39struct EvJointDiscontinuity : sc::event<EvJointDiscontinuity<AsyncCB, Orthogonal>>
40{
41 moveit_msgs::msg::RobotTrajectory trajectory;
42};
43
44template <typename AsyncCB, typename Orthogonal>
45struct EvIncorrectInitialPosition : sc::event<EvIncorrectInitialPosition<AsyncCB, Orthogonal>>
46{
47 moveit_msgs::msg::RobotTrajectory trajectory;
48};
49
56
57// this is a base behavior to define any kind of parameterized family of trajectories or motions
59{
60public:
61 // std::string tip_link_;
62 std::optional<std::string> group_;
63
64 std::optional<std::string> tipLink_;
65
67
68 CbMoveEndEffectorTrajectory(std::optional<std::string> tipLink = std::nullopt) : tipLink_(tipLink)
69 {
70 }
71
73 const std::vector<geometry_msgs::msg::PoseStamped> & endEffectorTrajectory,
74 std::optional<std::string> tipLink = std::nullopt)
75 : tipLink_(tipLink), endEffectorTrajectory_(endEffectorTrajectory)
76 {
77 }
78
79 template <typename TOrthogonal, typename TSourceObject>
81 {
82 this->initializeROS();
83
84 // optional components specific to this behavior family; the shared ones
85 // (move group, motion planner, executor, history) come from the base chain
89
91
92 postJointDiscontinuityEvent = [this](auto traj)
93 {
95 ev->trajectory = traj;
96 this->postEvent(ev);
97 };
98
99 postIncorrectInitialStateEvent = [this](auto traj)
100 {
102 ev->trajectory = traj;
103 this->postEvent(ev);
104 };
105
107 {
108 RCLCPP_INFO_STREAM(getLogger(), "[" << this->getName() << "] motion execution failed");
111 };
112 }
113
114 virtual void onEntry() override
115 {
116 // components resolved in onStateOrthogonalAllocation
117 CpTrajectoryVisualizer * trajectoryVisualizer = cpTrajectoryVisualizer_;
118
119 RCLCPP_INFO_STREAM(getLogger(), "[" << getName() << "] Generating end effector trajectory");
120
121 this->generateTrajectory();
122
123 if (this->endEffectorTrajectory_.size() == 0)
124 {
125 RCLCPP_WARN_STREAM(
126 getLogger(), "[" << smacc2::demangleSymbol(typeid(*this).name())
127 << "] No points in the trajectory. Skipping behavior.");
128 return;
129 }
130
131 // Use CpTrajectoryVisualizer if available, otherwise use legacy marker system
132 if (trajectoryVisualizer != nullptr)
133 {
134 RCLCPP_INFO_STREAM(
135 getLogger(),
136 "[" << getName() << "] Setting trajectory visualization using CpTrajectoryVisualizer.");
137 trajectoryVisualizer->setTrajectory(this->endEffectorTrajectory_, "trajectory");
138 }
139 else
140 {
141 RCLCPP_INFO_STREAM(
142 getLogger(), "[" << getName()
143 << "] Creating markers (legacy mode - consider adding "
144 "CpTrajectoryVisualizer component).");
145 this->createMarkers();
146 }
147
148 moveit_msgs::msg::RobotTrajectory computedTrajectory;
149
150 RCLCPP_INFO_STREAM(getLogger(), "[" << getName() << "] Computing joint space trajectory.");
151
152 auto errorcode = computeJointSpaceTrajectory(computedTrajectory);
153
154 bool trajectoryGenerationSuccess = errorcode == ComputeJointTrajectoryErrorCode::SUCCESS;
155
156 CpTrajectoryHistory * trajectoryHistory = cpTrajectoryHistory_;
157
158 if (!trajectoryGenerationSuccess)
159 {
160 RCLCPP_INFO_STREAM(
161 getLogger(), "[" << this->getName() << "] Incorrect trajectory. Posting failure event.");
162 if (trajectoryHistory != nullptr)
163 {
164 moveit_msgs::msg::MoveItErrorCodes error;
165 error.val = moveit_msgs::msg::MoveItErrorCodes::NO_IK_SOLUTION;
166 trajectoryHistory->pushTrajectory(this->getName(), computedTrajectory, error);
167 }
168
170 this->postFailureEvent();
171
173 {
174 this->postJointDiscontinuityEvent(computedTrajectory);
175 }
177 {
178 this->postIncorrectInitialStateEvent(computedTrajectory);
179 }
180 return;
181 }
182 else
183 {
184 if (trajectoryHistory != nullptr)
185 {
186 moveit_msgs::msg::MoveItErrorCodes error;
187 error.val = moveit_msgs::msg::MoveItErrorCodes::SUCCESS;
188 trajectoryHistory->pushTrajectory(this->getName(), computedTrajectory, error);
189 }
190
191 this->executeJointSpaceTrajectory(computedTrajectory);
192 }
193
194 // handle finishing events
195 }
196
197 // Components are resolved in onStateOrthogonalAllocation (state machine
198 // thread); never call requiresComponent from onEntry/onExit - they run on
199 // asynchronous behavior threads and deadlock against state transitions
200 virtual void onExit() override {}
201
202protected:
204 moveit_msgs::msg::RobotTrajectory & computedJointTrajectory)
205 {
206 // Use the CpJointSpaceTrajectoryPlanner component when present (preferred)
208
209 if (trajectoryPlanner != nullptr)
210 {
211 // Use component-based trajectory planner (preferred)
212 RCLCPP_INFO(
213 getLogger(),
214 "[CbMoveEndEffectorTrajectory] Using CpJointSpaceTrajectoryPlanner component for IK "
215 "trajectory generation");
216
218 if (tipLink_ && !tipLink_->empty())
219 {
220 options.tipLink = *tipLink_;
221 }
223 {
225 }
226
227 auto result = trajectoryPlanner->planFromWaypoints(endEffectorTrajectory_, options);
228
229 if (result.success)
230 {
231 computedJointTrajectory = result.trajectory;
232 RCLCPP_INFO(
233 getLogger(),
234 "[CbMoveEndEffectorTrajectory] IK trajectory generation succeeded (via "
235 "CpJointSpaceTrajectoryPlanner)");
237 }
238 else
239 {
240 computedJointTrajectory = result.trajectory; // Still return partial trajectory
241 RCLCPP_WARN(
242 getLogger(),
243 "[CbMoveEndEffectorTrajectory] IK trajectory generation failed (via "
244 "CpJointSpaceTrajectoryPlanner): %s",
245 result.errorMessage.c_str());
246
247 // Map error codes
248 switch (result.errorCode)
249 {
254 default:
256 }
257 }
258 }
259 else
260 {
261 // Fallback to legacy IK trajectory generation
262 RCLCPP_WARN(
263 getLogger(),
264 "[CbMoveEndEffectorTrajectory] CpJointSpaceTrajectoryPlanner component not available, "
265 "using legacy IK trajectory generation (consider adding CpJointSpaceTrajectoryPlanner "
266 "component)");
267
268 // LEGACY IMPLEMENTATION (keep for backward compatibility)
269 RCLCPP_INFO_STREAM(getLogger(), "[" << getName() << "] getting current state.. waiting");
270
271 auto currentState = cpMoveGroup_->moveGroupClientInterface->getCurrentState(100);
272 auto groupname = cpMoveGroup_->moveGroupClientInterface->getName();
273
274 RCLCPP_INFO_STREAM(getLogger(), "[" << getName() << "] getting joint names");
275 auto currentjointnames =
276 currentState->getJointModelGroup(groupname)->getActiveJointModelNames();
277
278 if (!tipLink_ || *tipLink_ == "")
279 {
280 tipLink_ = cpMoveGroup_->moveGroupClientInterface->getEndEffectorLink();
281 }
282
283 std::vector<double> jointPositions;
284 currentState->copyJointGroupPositions(groupname, jointPositions);
285
286 std::vector<std::vector<double>> trajectory;
287 std::vector<rclcpp::Duration> trajectoryTimeStamps;
288
289 trajectory.push_back(jointPositions);
290 trajectoryTimeStamps.push_back(rclcpp::Duration(0s));
291
292 auto & first = endEffectorTrajectory_.front();
293 rclcpp::Time referenceTime(first.header.stamp);
294
295 std::vector<int> discontinuityIndexes;
296
297 int ikAttempts = 4;
298 for (size_t k = 0; k < this->endEffectorTrajectory_.size(); k++)
299 {
300 auto & pose = this->endEffectorTrajectory_[k];
301 auto req = std::make_shared<moveit_msgs::srv::GetPositionIK::Request>();
302
303 req->ik_request.ik_link_name = *tipLink_;
304 req->ik_request.robot_state.joint_state.name = currentjointnames;
305 req->ik_request.robot_state.joint_state.position = jointPositions;
306 req->ik_request.group_name = groupname;
307 req->ik_request.avoid_collisions = true;
308 req->ik_request.pose_stamped = pose;
309
310 RCLCPP_INFO_STREAM(getLogger(), "[" << getName() << "] IK request: " << k << " " << *req);
311
312 auto resfut = iksrv_->async_send_request(req);
313 auto status = resfut.wait_for(3s);
314
315 if (status == std::future_status::ready)
316 {
317 auto & prevtrajpoint = trajectory.back();
318 auto res = resfut.get();
319
320 std::stringstream ss;
321 for (size_t j = 0; j < res->solution.joint_state.position.size(); j++)
322 {
323 auto & jointname = res->solution.joint_state.name[j];
324 auto it = std::find(currentjointnames.begin(), currentjointnames.end(), jointname);
325 if (it != currentjointnames.end())
326 {
327 int index = std::distance(currentjointnames.begin(), it);
328 jointPositions[index] = res->solution.joint_state.position[j];
329 ss << jointname << "(" << index << "): " << jointPositions[index] << std::endl;
330 }
331 }
332
333 // Continuity check
334 size_t jointindex = 0;
335 int discontinuityJointIndex = -1;
336 double discontinuityDeltaJointIndex = -1;
337 double deltajoint;
338
339 bool check = k > 0 || !allowInitialTrajectoryStateJointDiscontinuity_ ||
341 !(*allowInitialTrajectoryStateJointDiscontinuity_));
342 if (check)
343 {
344 for (jointindex = 0; jointindex < jointPositions.size(); jointindex++)
345 {
346 deltajoint = jointPositions[jointindex] - prevtrajpoint[jointindex];
347 if (fabs(deltajoint) > 0.3)
348 {
349 discontinuityDeltaJointIndex = deltajoint;
350 discontinuityJointIndex = jointindex;
351 }
352 }
353 }
354
355 if (ikAttempts > 0 && discontinuityJointIndex != -1)
356 {
357 k--;
358 ikAttempts--;
359 continue;
360 }
361 else
362 {
363 bool discontinuity = false;
364 if (ikAttempts == 0)
365 {
366 discontinuityIndexes.push_back(k);
367 discontinuity = true;
368 }
369
370 ikAttempts = 4;
371
372 if (discontinuity && discontinuityJointIndex != -1)
373 {
374 std::stringstream ss;
375 ss << "Traj[" << k << "/" << endEffectorTrajectory_.size() << "] "
376 << currentjointnames[discontinuityJointIndex]
377 << " IK discontinuity : " << discontinuityDeltaJointIndex << std::endl
378 << "prev joint value: " << prevtrajpoint[discontinuityJointIndex] << std::endl
379 << "current joint value: " << jointPositions[discontinuityJointIndex] << std::endl;
380
381 ss << std::endl;
382 for (size_t ji = 0; ji < jointPositions.size(); ji++)
383 {
384 ss << currentjointnames[ji] << ": " << jointPositions[ji] << std::endl;
385 }
386
387 for (size_t kindex = 0; kindex < trajectory.size(); kindex++)
388 {
389 ss << "[" << kindex << "]: " << trajectory[kindex][discontinuityJointIndex]
390 << std::endl;
391 }
392
393 if (k == 0)
394 {
395 ss
396 << "This is the first posture of the trajectory. Maybe the robot initial posture "
397 "is "
398 "not coincident to the initial posture of the generated joint trajectory."
399 << std::endl;
400 }
401
402 RCLCPP_ERROR_STREAM(getLogger(), ss.str());
403
404 trajectory.push_back(jointPositions);
405 rclcpp::Duration durationFromStart = rclcpp::Time(pose.header.stamp) - referenceTime;
406 trajectoryTimeStamps.push_back(durationFromStart);
407 continue;
408 }
409 else
410 {
411 trajectory.push_back(jointPositions);
412 rclcpp::Duration durationFromStart = rclcpp::Time(pose.header.stamp) - referenceTime;
413 trajectoryTimeStamps.push_back(durationFromStart);
414
415 RCLCPP_DEBUG_STREAM(getLogger(), "IK solution: " << res->solution.joint_state);
416 RCLCPP_DEBUG_STREAM(getLogger(), "trajpoint: " << std::endl << ss.str());
417 }
418 }
419 }
420 else
421 {
422 RCLCPP_ERROR_STREAM(getLogger(), "[" << getName() << "] wrong IK call");
423 }
424 }
425
426 computedJointTrajectory.joint_trajectory.joint_names = currentjointnames;
427 int i = 0;
428 for (auto & p : trajectory)
429 {
430 if (i == 0) // Skip current state
431 {
432 i++;
433 continue;
434 }
435
436 trajectory_msgs::msg::JointTrajectoryPoint jp;
437 jp.positions = p;
438 jp.time_from_start = trajectoryTimeStamps[i];
439 computedJointTrajectory.joint_trajectory.points.push_back(jp);
440 i++;
441 }
442
443 if (discontinuityIndexes.size())
444 {
445 if (discontinuityIndexes[0] == 0)
447 else
449 }
450
452 }
453 }
454
456 const moveit_msgs::msg::RobotTrajectory & computedJointTrajectory)
457 {
458 RCLCPP_INFO_STREAM(getLogger(), "[" << this->getName() << "] Executing joint trajectory");
459
460 // Use the CpTrajectoryExecutor component when present (preferred)
461 CpTrajectoryExecutor * trajectoryExecutor = cpTrajectoryExecutor_;
462
463 bool executionSuccess = false;
464
465 if (trajectoryExecutor != nullptr)
466 {
467 // Use component-based trajectory executor (preferred)
468 RCLCPP_INFO(
469 getLogger(),
470 "[CbMoveEndEffectorTrajectory] Using CpTrajectoryExecutor component for execution");
471
472 ExecutionOptions execOptions;
473 execOptions.trajectoryName = this->getName();
474
475 auto execResult = trajectoryExecutor->execute(computedJointTrajectory, execOptions);
476 executionSuccess = execResult.success;
477
478 if (executionSuccess)
479 {
480 RCLCPP_INFO(
481 getLogger(),
482 "[CbMoveEndEffectorTrajectory] Execution succeeded (via CpTrajectoryExecutor)");
483 }
484 else
485 {
486 RCLCPP_WARN(
487 getLogger(),
488 "[CbMoveEndEffectorTrajectory] Execution failed (via CpTrajectoryExecutor): %s",
489 execResult.errorMessage.c_str());
490 }
491 }
492 else
493 {
494 // Fallback to legacy direct execution
495 RCLCPP_WARN(
496 getLogger(),
497 "[CbMoveEndEffectorTrajectory] CpTrajectoryExecutor component not available, using legacy "
498 "execution (consider adding CpTrajectoryExecutor component)");
499
500 auto executionResult =
501 this->cpMoveGroup_->moveGroupClientInterface->execute(computedJointTrajectory);
502 executionSuccess = (executionResult == moveit_msgs::msg::MoveItErrorCodes::SUCCESS);
503
504 RCLCPP_INFO(
505 getLogger(), "[CbMoveEndEffectorTrajectory] Execution %s (legacy mode)",
506 executionSuccess ? "succeeded" : "failed");
507 }
508
509 // Post events
510 if (executionSuccess)
511 {
512 this->postMotionSuccess();
513 }
514 else
515 {
517 this->postFailureEvent();
518 }
519 }
520
521 virtual void generateTrajectory()
522 {
523 // bypass current trajectory, overridden in derived classes
524 // this->endEffectorTrajectory_ = ...
525 }
526
527 virtual void createMarkers()
528 {
529 tf2::Transform localdirection;
530 localdirection.setIdentity();
531 localdirection.setOrigin(tf2::Vector3(0.05, 0, 0));
532 auto frameid = this->endEffectorTrajectory_.front().header.frame_id;
533
534 for (auto & pose : this->endEffectorTrajectory_)
535 {
536 visualization_msgs::msg::Marker marker;
537 marker.header.frame_id = frameid;
538 marker.header.stamp = getNode()->now();
539 marker.ns = "trajectory";
540 marker.id = this->beahiorMarkers_.markers.size();
541 marker.type = visualization_msgs::msg::Marker::ARROW;
542 marker.action = visualization_msgs::msg::Marker::ADD;
543 marker.scale.x = 0.005;
544 marker.scale.y = 0.01;
545 marker.scale.z = 0.01;
546 marker.color.a = 0.8;
547 marker.color.r = 1.0;
548 marker.color.g = 0;
549 marker.color.b = 0;
550
551 geometry_msgs::msg::Point start, end;
552 start.x = 0;
553 start.y = 0;
554 start.z = 0;
555
556 tf2::Transform basetransform;
557 tf2::fromMsg(pose.pose, basetransform);
558 // tf2::Transform endarrow = localdirection * basetransform;
559
560 end.x = localdirection.getOrigin().x();
561 end.y = localdirection.getOrigin().y();
562 end.z = localdirection.getOrigin().z();
563
564 marker.pose.position = pose.pose.position;
565 marker.pose.orientation = pose.pose.orientation;
566 marker.points.push_back(start);
567 marker.points.push_back(end);
568
569 beahiorMarkers_.markers.push_back(marker);
570 }
571 }
572
573 std::vector<geometry_msgs::msg::PoseStamped> endEffectorTrajectory_;
574
578
579 visualization_msgs::msg::MarkerArray beahiorMarkers_;
580
582 std::string globalFrame, tf2::Stamped<tf2::Transform> & currentEndEffectorTransform)
583 {
584 // components resolved in onStateOrthogonalAllocation
585 CpTfListener * tfListener = cpTfListener_;
586
587 try
588 {
589 if (!tipLink_ || *tipLink_ == "")
590 {
591 tipLink_ = this->cpMoveGroup_->moveGroupClientInterface->getEndEffectorLink();
592 }
593
594 if (tfListener != nullptr)
595 {
596 // Use component-based TF listener (preferred)
597 auto transformOpt = tfListener->lookupTransform(globalFrame, *tipLink_, rclcpp::Time(0));
598 if (transformOpt)
599 {
600 tf2::fromMsg(transformOpt.value(), currentEndEffectorTransform);
601 }
602 else
603 {
604 RCLCPP_ERROR_STREAM(
605 getLogger(), "[" << getName() << "] Failed to lookup transform from " << *tipLink_
606 << " to " << globalFrame);
607 }
608 }
609 else
610 {
611 // Fallback to legacy TF2 usage if component not available
612 RCLCPP_WARN_STREAM(
613 getLogger(), "[" << getName()
614 << "] CpTfListener component not available, using legacy TF2 (consider "
615 "adding CpTfListener component)");
616 tf2_ros::Buffer tfBuffer(getNode()->get_clock());
617 tf2_ros::TransformListener tfListenerLegacy(tfBuffer);
618
619 tf2::fromMsg(
620 tfBuffer.lookupTransform(globalFrame, *tipLink_, rclcpp::Time(0), rclcpp::Duration(10s)),
621 currentEndEffectorTransform);
622 }
623 }
624 catch (const std::exception & e)
625 {
626 RCLCPP_ERROR_STREAM(
627 getLogger(), "[" << getName() << "] Exception in getCurrentEndEffectorPose: " << e.what());
628 }
629 }
630
631private:
633 {
634 RCLCPP_INFO_STREAM(getLogger(), "[" << getName() << "] initializing ros");
635
636 auto nh = this->getNode();
637
638 // Only create marker publisher for legacy mode (when CpTrajectoryVisualizer is not used)
639 markersPub_ = nh->create_publisher<visualization_msgs::msg::MarkerArray>(
640 "trajectory_markers", rclcpp::QoS(1));
641
642 iksrv_ = nh->create_client<moveit_msgs::srv::GetPositionIK>("/compute_ik");
643 }
644
645 rclcpp::Publisher<visualization_msgs::msg::MarkerArray>::SharedPtr markersPub_;
646
647 rclcpp::Client<moveit_msgs::srv::GetPositionIK>::SharedPtr iksrv_;
648
649 std::mutex m_mutex_;
650
651 std::function<void(moveit_msgs::msg::RobotTrajectory &)> postJointDiscontinuityEvent;
652 std::function<void(moveit_msgs::msg::RobotTrajectory &)> postIncorrectInitialStateEvent;
653
654 std::function<void()> postMotionExecutionFailureEvents;
655
656 bool autocleanmarkers = false;
657};
658} // namespace cl_moveit2z
std::function< void(moveit_msgs::msg::RobotTrajectory &)> postJointDiscontinuityEvent
std::function< void(moveit_msgs::msg::RobotTrajectory &)> postIncorrectInitialStateEvent
std::vector< geometry_msgs::msg::PoseStamped > endEffectorTrajectory_
ComputeJointTrajectoryErrorCode computeJointSpaceTrajectory(moveit_msgs::msg::RobotTrajectory &computedJointTrajectory)
void executeJointSpaceTrajectory(const moveit_msgs::msg::RobotTrajectory &computedJointTrajectory)
CbMoveEndEffectorTrajectory(std::optional< std::string > tipLink=std::nullopt)
rclcpp::Publisher< visualization_msgs::msg::MarkerArray >::SharedPtr markersPub_
void getCurrentEndEffectorPose(std::string globalFrame, tf2::Stamped< tf2::Transform > &currentEndEffectorTransform)
CbMoveEndEffectorTrajectory(const std::vector< geometry_msgs::msg::PoseStamped > &endEffectorTrajectory, std::optional< std::string > tipLink=std::nullopt)
rclcpp::Client< moveit_msgs::srv::GetPositionIK >::SharedPtr iksrv_
Component for joint space trajectory generation from Cartesian waypoints.
JointTrajectoryResult planFromWaypoints(const std::vector< geometry_msgs::msg::PoseStamped > &waypoints, const JointTrajectoryOptions &options={})
Compute joint space trajectory from Cartesian waypoints.
std::shared_ptr< moveit::planning_interface::MoveGroupInterface > moveGroupClientInterface
Component for shared TF2 transform management across all behaviors.
std::optional< geometry_msgs::msg::TransformStamped > lookupTransform(const std::string &target_frame, const std::string &source_frame, const rclcpp::Time &time=rclcpp::Time(0))
Thread-safe transform lookup.
Component for centralized trajectory execution.
ExecutionResult execute(const moveit_msgs::msg::RobotTrajectory &trajectory, const ExecutionOptions &options={})
Execute a trajectory synchronously.
void pushTrajectory(std::string name, const moveit_msgs::msg::RobotTrajectory &trajectory, moveit_msgs::msg::MoveItErrorCodes result)
Component for visualizing trajectories as RViz markers.
void setTrajectory(const std::vector< geometry_msgs::msg::PoseStamped > &poses, const std::string &ns="trajectory")
Set trajectory to visualize.
virtual rclcpp::Logger getLogger() const
virtual rclcpp::Node::SharedPtr getNode() const
void requiresComponent(SmaccComponentType *&storage, ComponentRequirement requirementType=ComponentRequirement::SOFT)
std::string demangleSymbol()
Configuration options for trajectory execution.
std::optional< std::string > trajectoryName
Configuration options for joint space trajectory planning.