TrajectoryFollowingController.cpp
Go to the documentation of this file.
2
3#include <algorithm>
4
5#include <Eigen/Geometry>
6
7#include <SimoxUtility/math/convert/mat4f_to_pos.h>
8#include <SimoxUtility/math/convert/mat4f_to_rpy.h>
9#include <SimoxUtility/math/convert/mat4f_to_xyyaw.h>
10
15#include <ArmarXCore/interface/serialization/Eigen/Eigen_fdi.h>
16
18
23#include <armarx/navigation/trajectory_control/local/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
64 const core::GeneralConfig& generalConfig)
65 {
67
68 p.limits = generalConfig.maxVel;
69
70 return p;
71 }
72
74 const Params& i_params,
75 const core::GeneralConfig& generalConfig) :
76 params(applyLimitFromGeneralConfig(i_params, generalConfig)),
77 pidPos(params.pidPos.Kp,
78 params.pidPos.Ki,
79 params.pidPos.Kd,
80 std::numeric_limits<double>::max(),
81 std::numeric_limits<double>::max(),
82 false,
83 std::vector<bool>{false, false, false}),
84 pidPosTarget(params.pidPos.Kp / 2,
85 params.pidPos.Ki,
86 params.pidPos.Kd,
87 std::numeric_limits<double>::max(),
88 std::numeric_limits<double>::max(),
89 false,
90 std::vector<bool>{false, false, false}),
91 pidOri(params.pidOri.Kp,
92 params.pidOri.Ki,
93 params.pidOri.Kd,
94 std::numeric_limits<double>::max(),
95 std::numeric_limits<double>::max(),
96 false,
97 std::vector<bool>{true, true, true}),
98 pidOriTarget(params.pidOri.Kp,
99 params.pidOri.Ki,
100 params.pidOri.Kd,
101 std::numeric_limits<double>::max(),
102 std::numeric_limits<double>::max(),
103 false,
104 std::vector<bool>{true, true, true})
105 {
106 ARMARX_IMPORTANT << "Trajectory following controller params: "
107 << VAROUT(params.limits.linear) << ", " << VAROUT(params.limits.angular);
108 }
109
111 params(params),
112 pidPos(params.pidPos.Kp,
113 params.pidPos.Ki,
114 params.pidPos.Kd,
115 std::numeric_limits<double>::max(),
116 std::numeric_limits<double>::max(),
117 false,
118 std::vector<bool>{false, false, false}),
119 pidPosTarget(params.pidPos.Kp / 2,
120 params.pidPos.Ki,
121 params.pidPos.Kd,
122 std::numeric_limits<double>::max(),
123 std::numeric_limits<double>::max(),
124 false,
125 std::vector<bool>{false, false, false}),
126 pidOri(params.pidOri.Kp,
127 params.pidOri.Ki,
128 params.pidOri.Kd,
129 std::numeric_limits<double>::max(),
130 std::numeric_limits<double>::max(),
131 false,
132 std::vector<bool>{true, true, true}),
133 pidOriTarget(params.pidOri.Kp,
134 params.pidOri.Ki,
135 params.pidOri.Kd,
136 std::numeric_limits<double>::max(),
137 std::numeric_limits<double>::max(),
138 false,
139 std::vector<bool>{true, true, true})
140 {
141 ARMARX_IMPORTANT << "Trajectory following controller params: "
142 << VAROUT(params.limits.linear) << ", " << VAROUT(params.limits.angular);
143 }
144
147 {
148 const core::Twist limits{.linear = Eigen::Vector3f::Ones() * params.limits.linear,
149 .angular = Eigen::Vector3f::Ones() * params.limits.angular};
150
151 ARMARX_CHECK((limits.linear.array() > 0).all());
152 ARMARX_CHECK((limits.angular.array() > 0).all());
153 ARMARX_CHECK(limits.linear.allFinite());
154 ARMARX_CHECK(limits.angular.allFinite());
155
156 // for all entries, scale should be less than 1
157 const auto scalePos = twist.linear.cwiseAbs().cwiseQuotient(limits.linear.cwiseAbs());
158 const auto scaleOri = twist.angular.cwiseAbs().cwiseQuotient(limits.angular.cwiseAbs());
159
160 ARMARX_CHECK(scalePos.allFinite());
161 ARMARX_CHECK(scaleOri.allFinite());
162
163 const float scaleMax = std::max(scalePos.maxCoeff(), scaleOri.maxCoeff());
164 if (scaleMax < 1.0F) // both linear and angular velocity in bounds?
165 {
166 return twist;
167 }
168
169 // scale such that no limit is violated
170 if (params.coupleLinearAndAngularLimits)
171 {
172 twist.linear /= scaleMax;
173 twist.angular /= scaleMax;
174 }
175 else
176 {
177 if (scalePos.maxCoeff() >= 1.0F)
178 {
179 twist.linear /= scalePos.maxCoeff();
180 }
181 if (scaleOri.maxCoeff() >= 1.0F)
182 {
183 twist.angular /= scaleOri.maxCoeff();
184 }
185 }
186
187 constexpr float eps = 0.001;
188
189 // pedantic checks
190 ARMARX_CHECK_LESS_EQUAL(std::abs(twist.linear.x()), params.limits.linear + eps);
191 ARMARX_CHECK_LESS_EQUAL(std::abs(twist.linear.y()), params.limits.linear + eps);
192 ARMARX_CHECK_LESS_EQUAL(std::abs(twist.linear.z()), params.limits.linear + eps);
193 ARMARX_CHECK_LESS_EQUAL(std::abs(twist.angular.x()), params.limits.angular + eps);
194 ARMARX_CHECK_LESS_EQUAL(std::abs(twist.angular.y()), params.limits.angular + eps);
195 ARMARX_CHECK_LESS_EQUAL(std::abs(twist.angular.z()), params.limits.angular + eps);
196
197 return twist;
198 }
199
202 {
203 twist.linear *= params.velocityFactor;
204 twist.angular *= params.velocityFactor;
205
206 return twist;
207 }
208
209 void
211 {
213 << "Scaling factor for velocity may not be negative, but is " << p.velocityFactor;
215 << "Scaling factor for velocity may not be > 1, but is " << p.velocityFactor;
216
217 // only changes to these to parameters are actually relevant for this class
218 params.velocityFactor = p.velocityFactor;
219 params.limits = p.limits;
220 params.coupleLinearAndAngularLimits = p.coupleLinearAndAngularLimits;
221 params.enableAngularFeedforward = p.enableAngularFeedforward;
222 }
223
224 namespace
225 {
226 float
227 signedShortestAngularDiff(const float target, const float current)
228 {
229 float diff = target - current;
230 while (diff > M_PIf32)
231 {
232 diff -= 2.0f * M_PIf32;
233 }
234 while (diff < -M_PIf32)
235 {
236 diff += 2.0f * M_PIf32;
237 }
238 return diff;
239 }
240 } // namespace
241
242 TrajectoryControllerResult
244 const Eigen::Isometry3f& global_T_robot)
245 {
246 if (trajectory.points().empty())
247 {
248 ARMARX_INFO << "Trajectory is empty.";
250 .target = {}}; // target not applicable
251 }
252
253 // FIXME
255
256 const core::Evaluation target = trajectory.evaluate(timestamp);
257
258 using simox::math::mat4f_to_pos;
259 using simox::math::mat4f_to_rpy;
260
261 ARMARX_VERBOSE << deactivateSpam(1) << VAROUT(global_T_robot.translation().head<2>());
262 ARMARX_VERBOSE << deactivateSpam(1) << VAROUT(target.pose.translation().head<2>());
264 << VAROUT(target.pose.translation().head<2>() -
265 global_T_robot.translation().head<2>());
266
267 pidPos.update(global_T_robot.translation(), target.pose.translation());
268 pidOri.update(mat4f_to_rpy(global_T_robot.matrix()), mat4f_to_rpy(target.pose.matrix()));
269
270 // Feed forward is already in global frame
271 core::Twist twistFeedForward = target.feedforwardTwist;
272 float ffAngular = twistFeedForward.angular.z();
273 float cappedFfVel = twistFeedForward.linear.norm();
274
275 // If local planner didn't provide angular feedforward, compute from segment
276 if (params.enableAngularFeedforward)
277 {
278 if (std::abs(ffAngular) < 0.001f && target.idx + 1 < trajectory.points().size())
279 {
280 const auto& ptBefore = trajectory.points().at(target.idx);
281 const auto& ptAfter = trajectory.points().at(target.idx + 1);
282
283 const float yawBefore = mat4f_to_rpy(ptBefore.pose.matrix()).z();
284 const float yawAfter = mat4f_to_rpy(ptAfter.pose.matrix()).z();
285 const float angularDelta = signedShortestAngularDiff(yawAfter, yawBefore);
286
287 const float distance =
288 (ptAfter.pose.translation() - ptBefore.pose.translation()).norm();
289 const float dt = (ptAfter.timestamp - ptBefore.timestamp).toSecondsDouble();
290
291 if (distance > 1.0f && dt > 0.001f)
292 {
293 const float requiredRate = angularDelta / dt;
294 ffAngular = requiredRate;
295
296 // Scale feedforward if turn is too sharp for the robot
297 if (std::abs(requiredRate) > params.limits.angular)
298 {
299 const float scale = params.limits.angular / std::abs(requiredRate);
300 twistFeedForward.linear *= scale;
301 twistFeedForward.angular.z() = requiredRate * scale;
302 cappedFfVel = twistFeedForward.linear.norm();
303 }
304 }
305 }
306 else if (std::abs(ffAngular) > params.limits.angular)
307 {
308 // Local planner provided angular feedforward but it exceeds limits
309 const float scale = params.limits.angular / std::abs(ffAngular);
310 twistFeedForward.linear *= scale;
311 twistFeedForward.angular *= scale;
312 ffAngular *= scale;
313 cappedFfVel = twistFeedForward.linear.norm();
314 }
315 }
316
317 const core::Twist twistError{.linear = pidPos.getControlValue(),
318 .angular = pidOri.getControlValue()};
319
320 // const core::Twist twistError = core::Twist::Zero();
321
322 const core::Twist twist{.linear = twistFeedForward.linear + twistError.linear,
323 .angular = twistFeedForward.angular + twistError.angular};
324
325 // const core::Twist twist = twistError;
326
327 ARMARX_VERBOSE << deactivateSpam(1) << VAROUT(twistFeedForward.linear.transpose());
328 ARMARX_VERBOSE << deactivateSpam(1) << VAROUT(twistFeedForward.angular.transpose());
329
330 ARMARX_VERBOSE << deactivateSpam(1) << VAROUT(twistError.linear.transpose());
331 ARMARX_VERBOSE << deactivateSpam(1) << VAROUT(twistError.angular.transpose());
332
333 ARMARX_VERBOSE << deactivateSpam(1) << VAROUT(twist.linear.transpose());
334 ARMARX_VERBOSE << deactivateSpam(1) << VAROUT(twist.angular.transpose());
335
336
337 const auto twistLimited = applyTwistLimits(twist);
338 ARMARX_VERBOSE << deactivateSpam(1) << "Twist limited linear "
339 << twistLimited.linear.transpose();
340 ARMARX_VERBOSE << deactivateSpam(1) << "Twist limited angular "
341 << twistLimited.angular.transpose();
342
343 const auto twistScaled = applyVelocityFactor(twistLimited);
344 ARMARX_VERBOSE << deactivateSpam(1) << "Twist scaled linear "
345 << twistScaled.linear.transpose();
346 ARMARX_VERBOSE << deactivateSpam(1) << "Twist scaled angular "
347 << twistScaled.angular.transpose();
348
349 // convert to the robot's base frame
350 const auto& twistGlobal = twistScaled;
351
352 const core::Twist twistLocal{.linear =
353 global_T_robot.linear().inverse() * twistGlobal.linear,
354 .angular = twistGlobal.angular};
355
356 return TrajectoryControllerResult{.twist = twistLocal,
357 .target = target,
358 .ffAngular = ffAngular,
359 .cappedFfVel = cappedFfVel};
360 }
361
362} // namespace armarx::navigation::traj_ctrl::local
std::string timestamp()
SpamFilterDataPtr deactivateSpam(SpamFilterDataPtr const &spamFilter, float deactivationDurationSec, const std::string &identifier, bool deactivate)
Definition Logging.cpp:75
#define VAROUT(x)
constexpr T dt
static DateTime Now()
Current time on the virtual clock.
Definition Clock.cpp:93
static Duration MilliSeconds(std::int64_t milliSeconds)
Constructs a duration in milliseconds.
Definition Duration.cpp:48
Represents a point in time.
Definition DateTime.h:25
TrajectoryControllerResult control(const core::LocalTrajectory &trajectory, const Eigen::Isometry3f &global_T_robot) override
#define ARMARX_CHECK(expression)
Shortcut for ARMARX_CHECK_EXPRESSION.
#define ARMARX_CHECK_LESS_EQUAL(lhs, rhs)
This macro evaluates whether lhs is less or equal (<=) rhs and if it turns out to be false it will th...
#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
This file is part of ArmarX.
Definition fwd.h:47
void toAron(arondto::TrajectoryFollowingControllerParams &dto, const TrajectoryFollowingControllerParams &bo)
TrajectoryFollowingController::Params applyLimitFromGeneralConfig(const TrajectoryFollowingController::Params &params, const core::GeneralConfig &generalConfig)
void fromAron(const arondto::TrajectoryFollowingControllerParams &dto, TrajectoryFollowingControllerParams &bo)
std::vector< T > max(const std::vector< T > &v1, const std::vector< T > &v2)
double distance(const Point &a, const Point &b)
Definition point.hpp:95
static TrajectoryFollowingControllerParams FromAron(const aron::data::DictPtr &dict)