221 using simox::math::mat4f_to_pos;
222 using simox::math::mat4f_to_rpy;
225 const float currentOrientation = mat4f_to_rpy(currentPose.matrix()).z();
232 .dropPoint = {.waypoint = {.pose = core::Pose::Identity()}, .velocity = 0},
233 .isFinalSegment =
true,
234 .currentOrientation = currentOrientation,
235 .desiredOrientation = currentOrientation,
236 .orientationError = 0,
241 const TrajectoryId currentTrajectoryId{
243 .firstTranslation =
trajectory.points().front().waypoint.pose.translation(),
244 .lastTranslation =
trajectory.points().back().waypoint.pose.translation()};
246 if (not lastTrajectoryId_.has_value() or lastTrajectoryId_.value() != currentTrajectoryId)
248 lastTrajectoryId_ = currentTrajectoryId;
249 lastProjectionIndex_ = 0;
250 guards_ = GuardState{};
254 const std::size_t startSegment =
255 (lastProjectionIndex_ > 0) ? lastProjectionIndex_ - 1 : 0;
257 const auto projectedPose =
trajectory.getProjection(
261 static_cast<std::size_t
>(params.maxSegmentsAhead),
262 params.orientationWeight,
263 params.lookaheadDistance);
265 lastProjectionIndex_ = projectedPose.indexBefore;
268 pidPos.update(mat4f_to_pos(currentPose.matrix()),
269 mat4f_to_pos(projectedPose.projection.waypoint.pose.matrix()));
270 pidOri.update(mat4f_to_rpy(currentPose.matrix()),
271 mat4f_to_rpy(projectedPose.projection.waypoint.pose.matrix()));
273 const float desiredOrientation =
274 mat4f_to_rpy(projectedPose.projection.waypoint.pose.matrix()).z();
276 float ffAngular = 0.0f;
277 float cappedFfVel = 0.0f;
278 bool angularFeedforwardSaturated =
false;
292 pidPosTarget.update(currentPose.translation(),
293 trajectory.points().back().waypoint.pose.translation());
296 mat4f_to_rpy(currentPose.matrix()),
297 mat4f_to_rpy(
trajectory.points().back().waypoint.pose.matrix()));
299 return core::Twist{.linear = pidPosTarget.getControlValue(),
300 .angular = pidOriTarget.getControlValue()};
305 pidPosTarget.reset();
308 const Eigen::Vector3f segmentDelta =
309 projectedPose.wayPointAfter.waypoint.pose.translation() -
310 projectedPose.wayPointBefore.waypoint.pose.translation();
316 const bool degenerateSegment = not(segmentDelta.norm() > 0.F);
318 if (degenerateSegment)
322 if (not guards_.zeroLengthSegment)
324 guards_.zeroLengthSegment =
true;
325 ARMARX_WARNING << guardTag <<
"zero-length-segment: trajectory segment "
326 <<
QUOTED(projectedPose.indexBefore)
327 <<
" has zero length. Dropping the feed-forward for it and "
328 "following on the orientation feedback alone.";
333 <<
"zero-length-segment: still at segment "
334 <<
QUOTED(projectedPose.indexBefore) <<
".";
337 return core::Twist{.linear = pidPos.getControlValue(),
338 .angular = pidOri.getControlValue()};
341 const Eigen::Vector3f desiredMovementDirection = segmentDelta.normalized();
343 const float ffVel = projectedPose.projection.velocity;
348 constexpr float implausibleVelocity = 3000.F;
350 if (not
std::isfinite(ffVel) or ffVel >= implausibleVelocity)
354 if (not guards_.nonFiniteTwist)
356 guards_.nonFiniteTwist =
true;
357 ARMARX_WARNING << guardTag <<
"nonfinite-twist: implausible feed-forward "
358 <<
"velocity " <<
QUOTED(ffVel) <<
" at segment "
359 <<
QUOTED(projectedPose.indexBefore)
360 <<
". Dropping the feed-forward for this cycle.";
363 return core::Twist{.linear = pidPos.getControlValue(),
364 .angular = pidOri.getControlValue()};
369 if (params.enableAngularFeedforward)
371 const float yawBefore =
372 mat4f_to_rpy(projectedPose.wayPointBefore.waypoint.pose.matrix()).z();
373 const float yawAfter =
374 mat4f_to_rpy(projectedPose.wayPointAfter.waypoint.pose.matrix()).z();
375 const float angularDelta = signedShortestAngularDiff(yawAfter, yawBefore);
377 const float segmentLength =
378 (projectedPose.wayPointAfter.waypoint.pose.translation() -
379 projectedPose.wayPointBefore.waypoint.pose.translation())
387 constexpr float minSegmentLength = 1e-3F;
389 const float turnRatePerLength =
390 (segmentLength > minSegmentLength) ? (angularDelta / segmentLength) : 0.0f;
396 const float ffAngularLimit =
397 std::clamp(params.angularFeedforwardFraction, 0.F, 1.F) * params.limits.angular;
399 const float requiredAngularRate = turnRatePerLength * ffVel;
402 if (std::abs(requiredAngularRate) > ffAngularLimit and ffAngularLimit > 0.F)
407 const float minFfVel =
408 std::clamp(params.minFeedforwardVelocityFraction, 0.F, 1.F) * ffVel;
410 cappedFfVel = std::max(
411 minFfVel, ffVel * (ffAngularLimit / std::abs(requiredAngularRate)));
417 ffAngular = std::clamp(
418 turnRatePerLength * cappedFfVel, -ffAngularLimit, ffAngularLimit);
425 angularFeedforwardSaturated =
426 ffAngularLimit > 0.F and std::abs(requiredAngularRate) > ffAngularLimit;
431 const auto feedforwardVelocity = desiredMovementDirection * cappedFfVel;
434 << feedforwardVelocity.normalized();
439 angularFF.z() = ffAngular;
440 return core::Twist{.linear = pidPos.getControlValue() + feedforwardVelocity,
441 .angular = pidOri.getControlValue() + angularFF};
448 if (angularFeedforwardSaturated and cappedFfVel < 0.25F * projectedPose.projection.velocity)
450 guards_.angularSaturatedCycles++;
452 constexpr std::size_t sustainedCycles = 50;
454 if (guards_.angularSaturatedCycles >= sustainedCycles and
455 not guards_.angularFeedforwardSaturated)
457 guards_.angularFeedforwardSaturated =
true;
459 << guardTag <<
"ff-angular-saturated: the angular feed-forward has been "
460 <<
"pinned at the limit for " << guards_.angularSaturatedCycles
461 <<
" cycles while the linear command collapsed. segment="
462 <<
QUOTED(projectedPose.indexBefore) <<
" ffVel="
463 <<
QUOTED(projectedPose.projection.velocity)
464 <<
" cappedFfVel=" <<
QUOTED(cappedFfVel) <<
" ffAngular="
465 <<
QUOTED(ffAngular) <<
" limitAngular=" <<
QUOTED(params.limits.angular)
466 <<
". The robot is turning on the spot rather than following the path.";
471 guards_.angularSaturatedCycles = 0;
478 if (not twistChecked.
linear.allFinite() or not twistChecked.
angular.allFinite())
483 if (not guards_.nonFiniteTwist)
485 guards_.nonFiniteTwist =
true;
487 <<
"nonfinite-twist: the computed twist was not finite at segment "
488 <<
QUOTED(projectedPose.indexBefore)
489 <<
". Commanding zero. A non-finite pose or velocity reached the "
490 "controller from the trajectory or the localization.";
500 << twistLimited.linear.transpose();
502 << twistLimited.angular.transpose();
506 << twistScaled.linear.transpose();
508 << twistScaled.angular.transpose();
511 const auto& twistGlobal = twistScaled;
514 twistLocal.
linear = global_T_robot.linear().inverse() * twistGlobal.linear;
516 twistLocal.
angular = twistGlobal.angular;
525 if (not twistLocal.
linear.allFinite() or not twistLocal.
angular.allFinite())
530 if (not guards_.nonFiniteTwist)
532 guards_.nonFiniteTwist =
true;
534 <<
"nonfinite-twist: the computed twist was not finite at segment "
535 <<
QUOTED(projectedPose.indexBefore)
536 <<
". Commanding zero. This means a non-finite pose or velocity "
537 "reached the controller from the trajectory or the localization.";
547 .dropPoint = projectedPose.projection,
548 .isFinalSegment = isFinalSegment,
549 .currentOrientation = currentOrientation,
550 .desiredOrientation = desiredOrientation,
554 std::abs(signedShortestAngularDiff(desiredOrientation, currentOrientation)),
555 .positionError = (global_T_robot.translation() -
556 trajectory.points().back().waypoint.pose.translation())
559 .ffAngular = ffAngular,
560 .cappedFfVel = cappedFfVel,
561 .projectionIndex = projectedPose.indexBefore,
562 .angularFeedforwardSaturated = angularFeedforwardSaturated,