TrajectoryFollowingController.cpp
Go to the documentation of this file.
2
3#include <algorithm>
4#include <cmath>
5#include <limits>
6#include <vector>
7
8#include <SimoxUtility/math/convert/mat4f_to_pos.h>
9#include <SimoxUtility/math/convert/mat4f_to_rpy.h>
10
14#include <ArmarXCore/interface/serialization/Eigen/Eigen_fdi.h>
15
18
23#include <armarx/navigation/trajectory_control/global/aron/TrajectoryFollowingControllerParams.aron.generated.h>
26
28{
29 // TrajectoryFollowingControllerParams
30
36
39 {
40 arondto::TrajectoryFollowingControllerParams dto;
41
44
45 return dto.toAron();
46 }
47
50 {
51 arondto::TrajectoryFollowingControllerParams dto;
52 dto.fromAron(dict);
53
56
57 return bo;
58 }
59
60 // TrajectoryFollowingController
61
63 params(params),
64 pidPos(params.pidPos.Kp,
65 params.pidPos.Ki,
66 params.pidPos.Kd,
67 std::numeric_limits<double>::max(),
68 std::numeric_limits<double>::max(),
69 false,
70 std::vector<bool>{false, false, false}),
71 pidPosTarget(params.pidPos.Kp,
72 params.pidPos.Ki,
73 params.pidPos.Kd,
74 std::numeric_limits<double>::max(),
75 std::numeric_limits<double>::max(),
76 false,
77 std::vector<bool>{false, false, false}),
78 pidOri(params.pidOri.Kp,
79 params.pidOri.Ki,
80 params.pidOri.Kd,
81 std::numeric_limits<double>::max(),
82 std::numeric_limits<double>::max(),
83 false,
84 std::vector<bool>{true, true, true}),
85 pidOriTarget(params.pidOri.Kp,
86 params.pidOri.Ki,
87 params.pidOri.Kd,
88 std::numeric_limits<double>::max(),
89 std::numeric_limits<double>::max(),
90 false,
91 std::vector<bool>{true, true, true})
92 {
93 ARMARX_IMPORTANT << "Trajectory following controller params: "
94 << VAROUT(params.limits.linear) << ", " << VAROUT(params.limits.angular);
95 }
96
99 {
100 if (params.limits.linear == 0 or params.limits.angular == 0)
101 {
102 return core::Twist{.linear = Eigen::Vector3f::Zero(),
103 .angular = Eigen::Vector3f::Zero()};
104 }
105
106 ARMARX_CHECK_POSITIVE(params.limits.linear);
107 ARMARX_CHECK_POSITIVE(params.limits.angular);
108
109 core::AngularVelocity angularLimit = Eigen::Vector3f::Ones() * params.limits.angular;
110
111 ARMARX_CHECK(std::isfinite(params.limits.linear));
112 ARMARX_CHECK(angularLimit.allFinite());
113
114 // for all entries, scale should be less than 1
115 // velocity limit is for total cartesian velocity, not a limit for each coordinate direction
116 const float scalePos = twist.linear.norm() / params.limits.linear;
117 const auto scaleOri = twist.angular.cwiseAbs().cwiseQuotient(angularLimit.cwiseAbs());
118
119 // ARMARX_CHECK(scalePos.allFinite());
120 ARMARX_CHECK(scaleOri.allFinite());
121
122 const float scaleMax = std::max(scalePos, scaleOri.maxCoeff());
123
124 if (scaleMax < 1.0F) // both linear and angular velocity in bounds?
125 {
126 return twist;
127 }
128
129 ARMARX_CHECK(not std::isnan(scaleMax));
130
131 // scale such that no limit is violated
132 if (params.coupleLinearAndAngularLimits)
133 {
134 twist.linear /= scaleMax;
135 twist.angular /= scaleMax;
136 }
137 else
138 {
139 if (scalePos >= 1.0F)
140 {
141 twist.linear /= scalePos;
142 }
143 if (scaleOri.maxCoeff() >= 1.0F)
144 {
145 twist.angular /= scaleOri.maxCoeff();
146 }
147 }
148
149 // constexpr float eps = 0.001;
150
151 // pedantic checks
152 // ARMARX_CHECK_LESS_EQUAL(std::abs(twist.linear.x()), params.limits.linear + eps);
153 //ARMARX_CHECK_LESS_EQUAL(std::abs(twist.linear.y()), params.limits.linear + eps);
154 //ARMARX_CHECK_LESS_EQUAL(std::abs(twist.linear.z()), params.limits.linear + eps);
155 //ARMARX_CHECK_LESS_EQUAL(std::abs(twist.angular.x()), params.limits.angular + eps);
156 //ARMARX_CHECK_LESS_EQUAL(std::abs(twist.angular.y()), params.limits.angular + eps);
157 //ARMARX_CHECK_LESS_EQUAL(std::abs(twist.angular.z()), params.limits.angular + eps);
158
159 return twist;
160 }
161
164 {
165 twist.linear *= params.velocityFactor;
166 twist.angular *= params.velocityFactor;
167
168 return twist;
169 }
170
171 void
173 {
175 << "Scaling factor for velocity may not be negative, but is " << p.velocityFactor;
177 << "Scaling factor for velocity may not be > 1, but is " << p.velocityFactor;
178
179 // only changes to these to parameters are actually relevant for this class
180 params.velocityFactor = p.velocityFactor;
181 params.limits = p.limits;
182 params.orientationWeight = p.orientationWeight;
183 params.maxSegmentsAhead = p.maxSegmentsAhead;
184 params.coupleLinearAndAngularLimits = p.coupleLinearAndAngularLimits;
185 params.enableAngularFeedforward = p.enableAngularFeedforward;
186 }
187
188 namespace
189 {
190 float
191 signedShortestAngularDiff(const float target, const float current)
192 {
193 float diff = target - current;
194 while (diff > M_PIf32)
195 {
196 diff -= 2.0f * M_PIf32;
197 }
198 while (diff < -M_PIf32)
199 {
200 diff += 2.0f * M_PIf32;
201 }
202 return diff;
203 }
204 } // namespace
205
206 void
208 {
209 lastTrajectoryId_ = std::nullopt;
210 lastProjectionIndex_ = 0;
211 }
212
215 const core::Pose& global_T_robot)
216 {
217 using simox::math::mat4f_to_pos;
218 using simox::math::mat4f_to_rpy;
219
220 const core::Pose currentPose(global_T_robot);
221 const float currentOrientation = mat4f_to_rpy(currentPose.matrix()).z();
222
223 if (trajectory.points().empty())
224 {
225 ARMARX_INFO << "Trajectory is empty.";
227 .twist = core::Twist::Zero(),
228 .dropPoint = {.waypoint = {.pose = core::Pose::Identity()}, .velocity = 0},
229 .isFinalSegment = true,
230 .currentOrientation = currentOrientation,
231 .desiredOrientation = currentOrientation,
232 .orientationError = 0,
233 .positionError = 0};
234 }
235
236 // Detect new trajectory and reset progress if needed
237 const TrajectoryId currentTrajectoryId{
238 .numPoints = trajectory.points().size(),
239 .firstTranslation = trajectory.points().front().waypoint.pose.translation(),
240 .lastTranslation = trajectory.points().back().waypoint.pose.translation()};
241
242 if (not lastTrajectoryId_.has_value() or lastTrajectoryId_.value() != currentTrajectoryId)
243 {
244 lastTrajectoryId_ = currentTrajectoryId;
245 lastProjectionIndex_ = 0;
246 ARMARX_VERBOSE << "New trajectory detected, resetting progress.";
247 }
248
249 const std::size_t startSegment =
250 (lastProjectionIndex_ > 0) ? lastProjectionIndex_ - 1 : 0;
251
252 const auto projectedPose = trajectory.getProjection(
253 currentPose,
255 startSegment,
256 static_cast<std::size_t>(params.maxSegmentsAhead),
257 params.orientationWeight);
258
259 lastProjectionIndex_ = projectedPose.indexBefore;
260
261
262 pidPos.update(mat4f_to_pos(currentPose.matrix()),
263 mat4f_to_pos(projectedPose.projection.waypoint.pose.matrix()));
264 pidOri.update(mat4f_to_rpy(currentPose.matrix()),
265 mat4f_to_rpy(projectedPose.projection.waypoint.pose.matrix()));
266
267 const float desiredOrientation =
268 mat4f_to_rpy(projectedPose.projection.waypoint.pose.matrix()).z();
269
270 float ffAngular = 0.0f;
271 float cappedFfVel = 0.0f;
272
273 const core::Twist twist = [&]() -> core::Twist
274 {
275 // on the final segment, bahavior differs
276 if (projectedPose.segment == core::Projection::Segment::FINAL)
277 {
278
279 ARMARX_VERBOSE << deactivateSpam(1) << "final segment";
280 // TODO fairly inefficient to do this every time
281 pidPos.reset();
282 pidOri.reset();
283
284 pidPosTarget.update(currentPose.translation(),
285 trajectory.points().back().waypoint.pose.translation());
286
287 pidOriTarget.update(
288 mat4f_to_rpy(currentPose.matrix()),
289 mat4f_to_rpy(trajectory.points().back().waypoint.pose.matrix()));
290
291 return core::Twist{.linear = pidPosTarget.getControlValue(),
292 .angular = pidOriTarget.getControlValue()};
293 }
294
295 // pidPosTarget not used yet
296 // TODO fairly inefficient to do this every time
297 pidPosTarget.reset();
298
299 // the "standard" case following the trajectory
300 const Eigen::Vector3f desiredMovementDirection =
301 (projectedPose.wayPointAfter.waypoint.pose.translation() -
302 projectedPose.wayPointBefore.waypoint.pose.translation())
303 .normalized();
304
305 const float ffVel = projectedPose.projection.velocity;
306 ARMARX_CHECK_FINITE(ffVel);
307 ARMARX_CHECK_LESS(ffVel, 3000); // 3 m/s should be sufficient, right?
308
309 // --- Angular feedforward based on segment orientation rate ---
310 cappedFfVel = ffVel;
311 if (params.enableAngularFeedforward)
312 {
313 const float yawBefore =
314 mat4f_to_rpy(projectedPose.wayPointBefore.waypoint.pose.matrix()).z();
315 const float yawAfter =
316 mat4f_to_rpy(projectedPose.wayPointAfter.waypoint.pose.matrix()).z();
317 const float angularDelta = signedShortestAngularDiff(yawAfter, yawBefore);
318
319 const float segmentLength =
320 (projectedPose.wayPointAfter.waypoint.pose.translation() -
321 projectedPose.wayPointBefore.waypoint.pose.translation())
322 .norm();
323
324 // Required angular rate to track this segment at current ffVel
325 const float requiredAngularRate =
326 (segmentLength > 1.0f) ? (angularDelta / segmentLength * ffVel) : 0.0f;
327
328 // Cap linear velocity if the turn is too sharp for the robot
329 if (std::abs(requiredAngularRate) > params.limits.angular)
330 {
331 cappedFfVel = ffVel * (params.limits.angular / std::abs(requiredAngularRate));
332 }
333
334 // Actual angular feedforward at the (possibly capped) linear speed
335 ffAngular =
336 (segmentLength > 1.0f) ? (angularDelta / segmentLength * cappedFfVel) : 0.0f;
337
338 ARMARX_VERBOSE << deactivateSpam(1) << "FF angular " << ffAngular;
339 }
340
341 const auto feedforwardVelocity = desiredMovementDirection * cappedFfVel;
342
343 ARMARX_VERBOSE << deactivateSpam(1) << "Feed forward direction "
344 << feedforwardVelocity.normalized();
345 ARMARX_VERBOSE << deactivateSpam(1) << "Feed forward velocity " << feedforwardVelocity;
346 ARMARX_VERBOSE << deactivateSpam(1) << "Control value " << pidPos.getControlValue();
347
348 core::AngularVelocity angularFF = Eigen::Vector3f::Zero();
349 angularFF.z() = ffAngular;
350 return core::Twist{.linear = pidPos.getControlValue() + feedforwardVelocity,
351 .angular = pidOri.getControlValue() + angularFF};
352 }();
353
354 const auto twistLimited = applyTwistLimits(twist);
355 ARMARX_VERBOSE << deactivateSpam(1) << "Twist limited linear "
356 << twistLimited.linear.transpose();
357 ARMARX_VERBOSE << deactivateSpam(1) << "Twist limited angular "
358 << twistLimited.angular.transpose();
359
360 const auto twistScaled = applyVelocityFactor(twistLimited);
361 ARMARX_VERBOSE << deactivateSpam(1) << "Twist scaled linear "
362 << twistScaled.linear.transpose();
363 ARMARX_VERBOSE << deactivateSpam(1) << "Twist scaled angular "
364 << twistScaled.angular.transpose();
365
366 // convert to the robot's base frame
367 const auto& twistGlobal = twistScaled;
368
369 core::Twist twistLocal;
370 twistLocal.linear = global_T_robot.linear().inverse() * twistGlobal.linear;
371 // TODO if not in 2D, then this must be changed!
372 twistLocal.angular = twistGlobal.angular;
373
374
375 const bool isFinalSegment = projectedPose.segment == core::Projection::Segment::FINAL;
376
378 .twist = twistLocal,
379 .dropPoint = projectedPose.projection,
380 .isFinalSegment = isFinalSegment,
381 .currentOrientation = currentOrientation,
382 .desiredOrientation = desiredOrientation,
383 .orientationError = std::abs(currentOrientation - desiredOrientation),
384 .positionError = (global_T_robot.translation() -
385 trajectory.points().back().waypoint.pose.translation())
386 .head<2>()
387 .norm(),
388 .ffAngular = ffAngular,
389 .cappedFfVel = cappedFfVel};
390 }
391
392} // namespace armarx::navigation::traj_ctrl::global
SpamFilterDataPtr deactivateSpam(SpamFilterDataPtr const &spamFilter, float deactivationDurationSec, const std::string &identifier, bool deactivate)
Definition Logging.cpp:75
#define VAROUT(x)
TrajectoryControllerResult control(const core::GlobalTrajectory &trajectory, const core::Pose &global_T_robot) override
#define ARMARX_CHECK_FINITE(number)
This macro evaluates whether number is finite (not nan or inf) and if it turns out to be false it wil...
#define ARMARX_CHECK(expression)
Shortcut for ARMARX_CHECK_EXPRESSION.
#define ARMARX_CHECK_POSITIVE(number)
This macro evaluates whether number is positive (> 0) and if it turns out to be false it will throw a...
#define ARMARX_CHECK_LESS(lhs, rhs)
This macro evaluates whether lhs is less (<) than rhs and if it turns out to be false it will throw a...
#define ARMARX_INFO
The normal logging level.
Definition Logging.h:181
#define ARMARX_IMPORTANT
The logging level for always important information, but expected behaviour (in contrast to ARMARX_WAR...
Definition Logging.h:190
#define ARMARX_VERBOSE
The logging level for verbose information.
Definition Logging.h:187
std::shared_ptr< Dict > DictPtr
Definition Dict.h:42
Eigen::Isometry3f Pose
Definition basic_types.h:31
Eigen::Vector3f AngularVelocity
Definition basic_types.h:44
void fromAron(const arondto::TrajectoryControllerParams &dto, TrajectoryControllerParams &bo)
void toAron(arondto::TrajectoryControllerParams &dto, const TrajectoryControllerParams &bo)
std::vector< T > max(const std::vector< T > &v1, const std::vector< T > &v2)
bool isfinite(const std::vector< T, Ts... > &v)
Definition algorithm.h:366
double norm(const Point &a)
Definition point.hpp:102
static TrajectoryFollowingControllerParams FromAron(const aron::data::DictPtr &dict)