TimedElasticBands.cpp
Go to the documentation of this file.
1#include "TimedElasticBands.h"
2
3#include <filesystem>
4#include <fstream>
5#include <memory>
6#include <optional>
7#include <string>
8#include <vector>
9
10#include <boost/smart_ptr/make_shared_object.hpp>
11
12#include <SimoxUtility/algorithm/apply.hpp>
13#include <SimoxUtility/color/Color.h>
14#include <VirtualRobot/CollisionDetection/CollisionModel.h> // IWYU pragma: keep
15#include <VirtualRobot/Robot.h> // IWYU pragma: keep
16#include <VirtualRobot/SceneObjectSet.h> // IWYU pragma: keep
17
23
27
34#include <armarx/navigation/local_planning/aron/TimedElasticBands.aron.generated.h>
38
39#include <geometry_msgs/Twist.h>
40#include <nlohmann/json_fwd.hpp>
41#include <ros/time.h>
42#include <teb_local_planner/homotopy_class_planner.h>
43#include <teb_local_planner/pose_se2.h>
44#include <teb_local_planner/robot_footprint_model.h>
45#include <teb_local_planner/timed_elastic_band.h>
46#include <teb_local_planner/visualization.h>
47
49{
50
51 // TimedElasticBands
52
55 const core::Scene& scene) :
56 LocalPlanner(scene, generalConfig), params(i_params), obstManager(teb_obstacles)
57 {
59 const auto robotName = scene.robot->getName();
60
61
62 //TODO (SALt): find proper place to init with default config
63 // read config from .json file into dto
64 readConfig(params.cfg, robotName);
65 // write from dto to teb config file
66 toTebCfg(params.cfg, cfg_);
67
68 // overwrite specific config values
69 {
70 // cfg_.trajectory.allow_init_with_backwards_motion = true;
71 // FIXME cfg_.trajectory.global_plan_overwrite_orientation = true;
72 // cfg_.robot.min_turning_radius = 0;
73 cfg_.robot.cmd_angle_instead_rotvel = false;
74 // FIXME check cfg_.robot.use_proportional_saturation = false;
75 cfg_.optim.weight_kinematics_nh = 0;
76 cfg_.optim.weight_kinematics_forward_drive = 0;
77 cfg_.optim.weight_kinematics_turning_radius = 0;
78
79 // cfg_.goal_tolerance.xy_goal_tolerance = 0.001;
80 // cfg_.goal_tolerance.yaw_goal_tolerance = 0.001;
81 cfg_.goal_tolerance.free_goal_vel = false;
82
83 // cfg_.obstacles.min_obstacle_dist = 0.5;
84 }
85
86 // apply max velocity from generalConfig
87 {
88 cfg_.robot.max_vel_x = generalConfig.maxVel.linear / 1000; // [mm/s] to [m/s]
89 cfg_.robot.max_vel_x_backwards = generalConfig.maxVel.linear / 1000; // [mm/s] to [m/s]
90 cfg_.robot.max_vel_y = generalConfig.maxVel.linear / 1000; // [mm/s] to [m/s]
91 cfg_.robot.max_vel_trans = generalConfig.maxVel.linear / 1000; // [mm/s] to [m/s]
92 cfg_.robot.max_vel_theta = generalConfig.maxVel.angular;
93 }
94
95 auto robot_model = boost::make_shared<teb_local_planner::CircularRobotFootprint>(
96 params.cfg.robot_footprint_radius);
97
98 ARMARX_INFO << "Robot footprint is " << params.cfg.robot_footprint_radius << "m";
99
100 hcp_ = std::make_unique<teb_local_planner::HomotopyClassPlanner>();
101 hcp_->initialize(
102 cfg_, &teb_obstacles, robot_model, teb_local_planner::TebVisualizationPtr(), nullptr);
103 //set member teb_costmap
104 setTebCostmap();
105 if (teb_costmap)
106 {
107 ARMARX_INFO << "Costmap available.";
108 // TODO: where to put all the parameters
109 const human::ExponentialPenaltyModel penalty{
110 .minDistance = 0.5, .epsilon = 0, .exponent = 1.2};
111
112 teb_costmap->setPenaltyModel(conv::toRos(penalty));
113 hcp_->setCostmap(&teb_costmap.value());
114 }
115 ros::Time::init(); // we have to init time before we can use the planner
116 }
117
118 void
119 TimedElasticBands::readConfig(arondto::TimedElasticBandsParams& target,
120 const std::string& robotName)
121 {
122
123 const armarx::PackagePath configPath = [&]() -> armarx::PackagePath
124 {
125 const armarx::PackagePath robotSpecificConfigPath(
126 "armarx_navigation",
127 "local_planner_config/TimedElasticBands/" + robotName + ".json");
128
129 if (std::filesystem::exists(robotSpecificConfigPath.toSystemPath()))
130 {
131 ARMARX_INFO << "Using robot-specific config " << QUOTED(robotSpecificConfigPath)
132 << ".";
133 return robotSpecificConfigPath;
134 }
135
136 const armarx::PackagePath defaultConfigPath(
137 "armarx_navigation", "local_planner_config/TimedElasticBands/default.json");
138 ARMARX_INFO << "Using default config " << QUOTED(robotSpecificConfigPath);
139
140 return defaultConfigPath;
141 }();
142
143 const std::filesystem::path file = configPath.toSystemPath();
144
145 ARMARX_CHECK(std::filesystem::is_regular_file(file)) << file;
146
147 ARMARX_INFO << "Loading config from file " << QUOTED(file) << ".";
148 std::ifstream ifs{file};
149
150 nlohmann::json jsonConfig;
151 ifs >> jsonConfig;
152
153 ARMARX_INFO << "Reading config";
154
156 target.read(reader, jsonConfig);
157 }
158
159 std::optional<LocalPlannerResult>
161 {
162 const core::Pose currentPose{scene.robot->getGlobalPose()};
163 ARMARX_DEBUG << VAROUT(currentPose.translation().head<2>());
164
165 // prune global trajectory
166 const auto [prunedGoal, planToDest] = goal.getSubTrajectory(
167 currentPose.translation(), params.cfg.planning_distance * 1000); // [m] to [mm]
168
169 ARMARX_DEBUG << prunedGoal.points().size() << " waypoints in pruned goal";
170
171 for (const auto& pt : prunedGoal.points())
172 {
173 ARMARX_DEBUG << pt.waypoint.pose.translation().head<2>();
174 }
175
176 const teb_local_planner::TimedElasticBand globalPath = conv::toRos(prunedGoal);
177 teb_globalPath = globalPath.poses();
178 hcp_->setGlobalPath(&teb_globalPath);
179
180 const teb_local_planner::PoseSE2 start = conv::toRos(currentPose);
181 const teb_local_planner::PoseSE2 end =
182 conv::toRos(prunedGoal.points().back().waypoint.pose);
183
184 geometry_msgs::Twist velocity_start = conv::toRos(scene.platformVelocity);
185 // velocity_start.linear.x = 0.1; // FIXME remove
186
187
188 fillObstacles();
189
190 // TODO differentiate between exception and failure (e.g. no trajectory could be generated due to obstacles)
191 const auto runTebOptimization =
192 [&hpc = this->hcp_, &start, &end, &velocity_start, &planToDest]() -> bool
193 {
194 bool success = false;
195 try
196 {
197 success = hpc->plan(start, end, &velocity_start, !planToDest);
198 }
199 catch (...)
200 {
201 ARMARX_ERROR << "Caught exception while planning: " << GetHandledExceptionString();
202 }
203
204 return success;
205 };
206
207 if (not runTebOptimization())
208 {
209 // try to recover
211 << "Failed to plan trajectory. Trying to recover by replanning from scratch.";
212 hcp_->clearPlanner();
213
214 if (not runTebOptimization())
215 {
216 ARMARX_WARNING << "Failed to replan trajectory.";
217 return std::nullopt;
218 }
219 }
220
221
222 ARMARX_INFO << VAROUT(hcp_->hasDiverged());
223
224 // if (not success)
225 // {
226 // ARMARX_INFO << "Found trajectory is not feasible!";
227 // hcp_->clearPlanner();
228 // ARMARX_WARNING << deactivateSpam(5) << "Found trajectory is not feasible!";
229 // return std::nullopt;
230 // }
231
232 if (false && hcp_->hasDiverged())
233 //if (hcp_->hasDiverged())
234 {
235 hcp_->clearPlanner();
236 ARMARX_WARNING << /*deactivateSpam(5) << */ "Planner has diverged!";
237 return std::nullopt;
238 }
239
240 if (hcp_->getTrajectoryContainer().empty())
241 {
242 hcp_->clearPlanner();
243 ARMARX_INFO << "Did not find any trajectory!";
244 return std::nullopt;
245 }
246
247 ARMARX_INFO << "Planned successfully (found " << hcp_->getTrajectoryContainer().size()
248 << " Trajectories)";
249
250 const auto timestamp = armarx::Clock::Now(); // FIXME
251
252 const ::teb_local_planner::TebOptimalPlannerPtr tebResult = hcp_->findBestTeb();
253 ARMARX_CHECK_NOT_NULL(tebResult);
254
255 core::LocalTrajectory best = conv::fromRos(tebResult->teb(), timestamp);
256
257 // a lot of false positives,
258 // e.g. if the scene doesn't change in a meaningful way the trajectory won't either
259 // if (lastTrajectory.has_value())
260 // {
261 // if (not best.isApproximatelyEqual(lastTrajectory.value(), 1))
262 // {
263
264 // ARMARX_WARNING << "Trajectory did not change";
265 // hcp_->clearPlanner();
266
267 // lastTrajectory = std::nullopt;
268
269 // // try to recover from diverged state
270 // {
271 // ARMARX_INFO << "Trying to recover by replanning from scratch.";
272 // hcp_->clearPlanner();
273
274 // if (not runTebOptimization())
275 // {
276 // // Recovery failed. Can't do anything more.
277 // ARMARX_WARNING << "Failed to replan trajectory.";
278 // return std::nullopt;
279 // }
280
281 // ARMARX_INFO << "Successfully recovered by replanned trajectory.";
282
283 // const ::teb_local_planner::TebOptimalPlannerPtr tebResult = hcp_->findBestTeb();
284 // ARMARX_CHECK_NOT_NULL(tebResult);
285
286 // best = conv::fromRos(tebResult->teb(), timestamp);
287 // lastTrajectory = best;
288 // }
289
290 // // check again whether the trajectory as changed
291 // }
292 // else
293 // {
294 // lastTrajectory = best;
295 // }
296 // }
297 // else
298 // {
299 // // initialize lastTrajectory
300 // lastTrajectory = best;
301 // }
302
303 ARMARX_INFO << VAROUT(hcp_->bestTebIdx());
304
305 ARMARX_INFO << VAROUT((best.points().front().pose.translation().head<2>() -
306 currentPose.translation().head<2>())
307 .norm());
308
309 // visualize path alternatives
310 if (arviz)
311 {
312 auto layer = arviz.value().layer("local_planner_path_alternatives");
313
314 int i = 0;
315 int bestTebIdx = hcp_->bestTebIdx();
316 for (const auto& teb : hcp_->getTrajectoryContainer())
317 {
318 if (i == bestTebIdx)
319 {
320 continue;
321 }
323 const std::vector<Eigen::Vector3f> points =
324 simox::alg::apply(trajectory.points(),
325 [](const core::LocalTrajectoryPoint& pt) -> Eigen::Vector3f
326 { return pt.pose.translation(); });
327 layer.add(viz::Path("path_alternative_" + std::to_string(i))
328 .points(points)
329 .color(simox::Color::gray()));
330 i++;
331 }
332
333 arviz.value().commit(layer);
334 }
335
336 return {{.trajectory = best}};
337 }
338
339 void
340 TimedElasticBands::fillObstacles()
341 {
342 obstManager.clear();
343
344 viz::Layer* visPtr = nullptr;
345 viz::Layer visLayer;
346 if (arviz)
347 {
348 visLayer = arviz.value().layer("local_planner_obstacles");
349 visPtr = &visLayer;
350 }
351
352
353 if (scene.staticScene)
354 {
355 ARMARX_CHECK(scene.staticScene.has_value());
356 for (const auto& obst : scene.staticScene.value().objects->getCollisionModels())
357 {
358 obstManager.addBoxObstacle(obst->getGlobalBoundingBox(), visPtr);
359 }
360 }
362 {
363 ARMARX_CHECK(scene.dynamicScene.has_value());
364 for (const auto& obst : scene.dynamicScene.value().humans)
365 {
366 obstManager.addHumanObstacle(obst, visPtr);
367 }
368
369 for (const auto& obst : scene.dynamicScene->laserScannerFeatures)
370 {
371 obstManager.addLaserScannerFeaturesObstacle(obst, visPtr);
372 }
373 }
374
375 if (arviz)
376 {
377 arviz.value().commit(visLayer);
378 }
379
380 ARMARX_VERBOSE << "TEB: added " << obstManager.size() << " obstacles";
381 }
382
383 //export algorithms::Costmap to costmap type of teb local planner and provide costmap to planner
384 void
385 TimedElasticBands::setTebCostmap()
386 {
387 if (not scene.staticScene.has_value())
388 {
389 return;
390 }
391
392 if (not scene.staticScene->distanceToObstaclesCostmap.has_value())
393 {
394 return;
395 }
396
397 return;
398
399 const algorithms::Costmap& navigationCostmap =
400 scene.staticScene->distanceToObstaclesCostmap.value();
401 teb_costmap.emplace(conv::toRos(navigationCostmap));
402 }
403
404} // namespace armarx::navigation::local_planning
std::string timestamp()
#define VAROUT(x)
#define QUOTED(x)
static DateTime Now()
Current time on the virtual clock.
Definition Clock.cpp:93
static std::filesystem::path toSystemPath(const data::PackagePath &pp)
std::pair< GlobalTrajectory, bool > getSubTrajectory(const Position &point, const float distance) const
Project point onto the trajectory and return a new trajectory along the old one from that point for t...
const std::vector< LocalTrajectoryPoint > & points() const
std::optional< viz::ScopedClient > arviz
LocalPlanner(const core::Scene &context, const core::GeneralConfig &generalConfig)
void addBoxObstacle(const VirtualRobot::BoundingBox &bbox, viz::Layer *visLayer=nullptr)
void addHumanObstacle(const human::Human &human, viz::Layer *visLayer=nullptr)
TimedElasticBands(const Params &params, const core::GeneralConfig &generalConfig, const core::Scene &scene)
std::optional< LocalPlannerResult > plan(const core::GlobalTrajectory &goal) override
#define ARMARX_CHECK(expression)
Shortcut for ARMARX_CHECK_EXPRESSION.
#define ARMARX_CHECK_NOT_NULL(ptr)
This macro evaluates whether ptr is not null and if it turns out to be false it will throw an Express...
#define ARMARX_INFO
The normal logging level.
Definition Logging.h:181
#define ARMARX_ERROR
The logging level for unexpected behaviour, that must be fixed.
Definition Logging.h:196
#define ARMARX_DEBUG
The logging level for output that is only interesting while debugging.
Definition Logging.h:184
#define ARMARX_WARNING
The logging level for unexpected behaviour, but not a serious problem.
Definition Logging.h:193
#define ARMARX_VERBOSE
The logging level for verbose information.
Definition Logging.h:187
Eigen::Vector2d toRos(const Eigen::Vector3f &vec)
Eigen::Vector3f fromRos(const Eigen::Vector2d &vec)
Eigen::Isometry3f Pose
Definition basic_types.h:31
This file is part of ArmarX.
Definition fwd.h:36
void toTebCfg(const arondto::TimedElasticBandsParams &dto, ::teb_local_planner::TebConfig &bo)
std::string GetHandledExceptionString()
std::optional< core::StaticScene > staticScene
Definition types.h:70
std::optional< core::DynamicScene > dynamicScene
Definition types.h:71
std::size_t size() const noexcept
Definition Layer.h:52