19#include <boost/graph/detail/adjacency_list.hpp>
20#include <boost/graph/dijkstra_shortest_paths.hpp>
21#include <boost/graph/named_function_params.hpp>
22#include <boost/graph/properties.hpp>
23#include <boost/property_map/property_map.hpp>
25#include <Eigen/Geometry>
27#include <range/v3/algorithm/sort.hpp>
28#include <range/v3/range/conversion.hpp>
29#include <range/v3/view/filter.hpp>
30#include <range/v3/view/reverse.hpp>
32#include <SimoxUtility/algorithm/string/string_tools.h>
33#include <VirtualRobot/Robot.h>
68#include <SemanticObjectRelations/Shapes/Shape.h>
85 return hasLocalPlanner;
91 return segmentLocalPlanner;
100 return plan.trajectory;
111 return plan.trajectory;
132 std::optional<algorithms::Toppra::DriveParams>
151 catch (
const std::exception& e)
153 ARMARX_WARNING <<
"Cannot read the platform dynamics config for platform "
162 const std::optional<algorithms::Toppra::DriveParams>& driveParams,
165 *applied = generalConfig.parametrization;
167 const auto rampingInstead = [&](
const std::string& reason)
169 ARMARX_WARNING <<
"TOPP-RA trajectory parametrization was requested but " << reason
170 <<
". Falling back to ramping.";
176 return fac::TrajectoryParametrizationFactory::create(ramping);
181 return fac::TrajectoryParametrizationFactory::create(generalConfig);
186 return rampingInstead(
"it is not available in this build: " +
190 if (not general.platformDynamicsEnabled)
192 return rampingInstead(
"its drive parameters are disabled by "
193 "`p.navigator.general.platformDynamicsEnabled`");
196 if (not driveParams.has_value())
198 return rampingInstead(
199 "its drive parameters could not be read for platform '" + general.platform +
200 "'. Check `p.navigator.general.platform` against the files in "
201 "data/armarx_navigation/config/platform/");
206 return fac::TrajectoryParametrizationFactory::create(generalConfig, *driveParams);
208 catch (
const std::exception& e)
210 return rampingInstead(std::string{
"it could not be initialised: "} + e.what());
229 reportPathGeometry(
const core::GlobalTrajectory& trajectory)
231 namespace smoothing = algorithms::spfa::smoothing;
233 if (trajectory.points().size() < 2)
238 const smoothing::GeometryLimits limits;
241 smoothing::findGeometryViolations(trajectory, limits);
246 <<
" of " << trajectory.points().size()
247 <<
" waypoints of the accepted plan reverse direction or sit on a "
248 <<
"segment shorter than " << limits.minSegmentLength
249 <<
" mm; the first is waypoint " <<
degenerate.front()
250 <<
". A heading taken across such a segment is numerical noise.";
253 const std::vector<std::size_t> unturnable =
254 smoothing::findTurnRateViolations(trajectory, limits);
256 if (not unturnable.empty())
258 ARMARX_WARNING <<
"[nav-guard] plan-unturnable: " << unturnable.size() <<
" of "
259 << trajectory.points().size() - 1
260 <<
" segments of the accepted plan turn faster than "
261 << limits.maxTurnRate <<
" rad/mm, the worst at "
262 << smoothing::maxTurnRate(trajectory, limits)
263 <<
" rad/mm. The reparametrization will slow the whole trajectory "
272 driveParams{loadDriveParams(config.stack.generalConfig, config.general)},
273 parametrization{createParametrization(config.stack.generalConfig,
276 ¶metrizationMode)},
277 rampingFallback{
fac::TrajectoryParametrizationFactory::create(
308 return diff.translation().norm();
312 for (
auto edge :
graph.edges())
314 const core::Pose start = resolveGraphVertex(edge.source());
315 const core::Pose goal = resolveGraphVertex(edge.target());
317 switch (edge.attrib().strategy)
321 ARMARX_VERBOSE <<
"Global planning from " << start.translation() <<
" to "
322 << goal.translation();
327 const auto globalPlan = config.stack.globalPlanner->plan(start, goal);
328 if (globalPlan.has_value())
332 edge.attrib().trajectory = globalPlan->trajectory;
334 << globalPlan->trajectory.length();
335 edge.attrib().cost() = globalPlan->trajectory.length();
340 edge.attrib().cost() = std::numeric_limits<float>::max();
347 edge.attrib().cost() = std::numeric_limits<float>::max();
355 edge.attrib().cost() = cost(start, goal);
370 std::vector<core::Pose> globalWaypoints;
371 switch (navigationFrame)
374 globalWaypoints = waypoints;
377 globalWaypoints.reserve(waypoints.size());
381 const core::Pose global_T_robot(srv.sceneProvider->scene().robot->getGlobalPose());
382 ARMARX_VERBOSE <<
"Initial robot pose: " << global_T_robot.matrix();
384 std::transform(std::begin(waypoints),
386 std::back_inserter(globalWaypoints),
387 [&](
const core::Pose& p) {
return global_T_robot * p; });
392 moveToAbsolute(globalWaypoints);
399 ARMARX_INFO <<
"Received moveToAlternatives() request.";
401 std::vector<core::TargetAlternative> globalTargets;
402 switch (navigationFrame)
408 globalTargets.reserve(
targets.size());
412 const core::Pose global_T_robot(srv.sceneProvider->scene().robot->getGlobalPose());
413 ARMARX_VERBOSE <<
"Initial robot pose: " << global_T_robot.matrix();
415 std::transform(std::begin(
targets),
417 std::back_inserter(globalTargets),
426 moveToAbsoluteAlternatives(globalTargets);
435 std::vector<core::Pose> globalWaypoints;
436 switch (navigationFrame)
439 globalWaypoints = waypoints;
442 globalWaypoints.reserve(waypoints.size());
444 std::begin(waypoints),
446 std::back_inserter(globalWaypoints),
448 {
return core::Pose(srv.sceneProvider->scene().robot->getGlobalPose()) * p; });
453 updateAbsolute(globalWaypoints);
460 const core::Graph::ConstVertex& startVertex,
461 const core::Graph::ConstVertex& goalVertex)
464 <<
graph.numEdges() <<
" edges.";
466 std::vector<core::Graph::VertexDescriptor> predecessors(
graph.numVertices());
467 std::vector<int> d(num_vertices(
graph));
473 core::Graph::VertexDescriptor start = startVertex.descriptor();
475 auto predecessorMap = boost::make_iterator_property_map(
476 predecessors.begin(), boost::get(boost::vertex_index,
graph));
478 auto params = boost::predecessor_map(predecessorMap)
479 .distance_map(boost::make_iterator_property_map(
480 d.begin(), boost::get(boost::vertex_index,
graph)))
481 .weight_map(weightMap);
488 for (
const auto& edge :
graph.edges())
490 ARMARX_VERBOSE << edge.sourceObjectID() <<
" -> " << edge.targetObjectID() <<
": "
491 << edge.attrib().m_value;
494 std::vector<core::Graph::EdgeDescriptor> edgesToBeRemoved;
495 for (
const auto edge :
graph.edges())
497 if (edge.attrib().m_value == std::numeric_limits<float>::max())
499 edgesToBeRemoved.push_back(edge.descriptor());
503 for (
const auto edge : edgesToBeRemoved)
505 boost::remove_edge(edge,
graph);
509 for (
const auto& edge :
graph.edges())
511 ARMARX_VERBOSE << edge.sourceObjectID() <<
" -> " << edge.targetObjectID() <<
": "
512 << edge.attrib().m_value;
513 ARMARX_VERBOSE <<
"Edge: " << edge <<
"cost: " << edge.attrib().cost()
514 <<
", type: " <<
static_cast<int>(edge.attrib().strategy)
515 <<
" , has traj " << edge.attrib().trajectory.has_value();
519 <<
graph.vertex(start).objectID() <<
"` to vertex `"
520 <<
graph.vertex(goalVertex.descriptor()).objectID() <<
"`";
522 boost::dijkstra_shortest_paths(
graph, start, params);
530 core::Graph::VertexDescriptor currentVertex = goalVertex.descriptor();
531 while (currentVertex != startVertex.descriptor())
533 shortestPath.push_back(
graph.vertex(currentVertex).objectID());
537 auto parent = predecessorMap[currentVertex];
543 auto outEdges =
graph.vertex(parent).outEdges();
544 ARMARX_VERBOSE <<
"Parent has " << std::distance(outEdges.begin(), outEdges.end())
548 <<
"Cannot reach another vertex from vertex `"
549 <<
graph.vertex(parent).objectID();
551 auto edgeIt = std::find_if(outEdges.begin(),
553 [¤tVertex](
const auto& edge) ->
bool
554 { return edge.target().descriptor() == currentVertex; });
559 currentVertex = parent;
562 shortestPath.push_back(startVertex.objectID());
568 shortestPath = shortestPath | ranges::views::reverse | ranges::to_vector;
576 Navigator::convertToTrajectory(
const GraphPath& shortestPath,
const core::Graph&
graph)
const
581 <<
"At least start and goal vertices must be available";
585 std::vector<core::GlobalTrajectoryPoint> trajectoryPoints;
592 ARMARX_VERBOSE <<
"Shortest path with " << shortestPath.size() <<
" vertices";
595 for (
size_t i = 0; i < shortestPath.size() - 1; i++)
601 const core::Graph::ConstEdge edge =
602 graph.edge(
graph.vertex(shortestPath.at(i)),
graph.vertex(shortestPath.at(i + 1)));
608 switch (edge.attrib().strategy)
618 ARMARX_INFO <<
"Length: " << edge.attrib().trajectory->length();
623 << edge.attrib().trajectory->points().size() <<
" waypoints";
628 const std::vector<core::GlobalTrajectoryPoint> edgeTrajectoryPoints =
629 edge.attrib().trajectory->points();
632 const int offset = trajectoryPoints.empty() ? 0 : 1;
634 if (edgeTrajectoryPoints.size() > 2)
638 trajectoryPoints.insert(trajectoryPoints.end(),
639 edgeTrajectoryPoints.begin() + offset,
640 edgeTrajectoryPoints.end());
665 const float point2pointVelocity = 400;
667 const core::GlobalTrajectoryPoint currentTrajPt = {
668 .waypoint = {.pose = resolveGraphVertex(graph.vertex(shortestPath.at(i)))},
669 .velocity = point2pointVelocity};
673 resolveGraphVertex(
graph.vertex(shortestPath.at(i + 1)))},
697 trajectoryPoints.push_back(currentTrajPt);
698 trajectoryPoints.push_back(nextTrajPt);
709 ARMARX_INFO <<
"Trajectory consists of " << trajectoryPoints.size() <<
" points";
711 for (
const auto& pt : trajectoryPoints)
716 return {trajectoryPoints};
720 Navigator::convertToGraph(
const std::vector<client::WaypointTarget>& targets)
const
723 GraphBuilder graphBuilder;
724 graphBuilder.initialize(
core::Pose(srv.sceneProvider->scene().robot->getGlobalPose()));
727 for (
const auto& target : targets)
764 if (
target.pose.has_value())
766 graphBuilder.connect(
target.pose.value(),
target.strategy);
773 if (not
target.locationId->empty())
776 target.locationId.value(), srv.sceneProvider->scene().graph->subgraphs);
781 << vertex.attrib().getPose();
786 ARMARX_INFO <<
"Found " << routes.size() <<
" routes to location `"
787 <<
target.locationId.value();
790 <<
"` is not a reachable vertex on the graph!";
793 graphBuilder.connect(routes,
799 ARMARX_ERROR <<
"Either `location_id` or `pose` has to be provided!";
802 const auto goalVertex = graphBuilder.getGraph().vertex(graphBuilder.goalVertex());
803 ARMARX_INFO <<
"Goal vertex is " <<
QUOTED(goalVertex.attrib().getLocationName());
809 Navigator::resolveGraphVertex(
const core::Graph::ConstVertex& vertex)
const
812 srv.sceneProvider->scene().staticScene->objectInfo,
813 vertex.attrib().getPose());
815 <<
"The location of vertex " << vertex.attrib().getLocationName()
816 <<
" couldn't be resolved (" << goal.errorMsg <<
")";
817 return goal.pose.value();
826 <<
"only absolute movement implemented atm.";
838 auto graphBuilder = convertToGraph(
targets);
843 auto startVertex = graphBuilder.startVertex;
844 auto goalVertex = graphBuilder.getGraph().vertex(graphBuilder.goalVertex());
846 ARMARX_INFO <<
"Goal pose according to graph is " << graphBuilder.goalPose().matrix();
852 goalReachedMonitor = std::nullopt;
854 graphBuilder.goalPose(), srv.sceneProvider->scene(), config.goalReachedConfig);
856 if (goalReachedMonitor->goalReached(
false))
859 << goalReachedMonitor->goal().translation().head<2>()
860 <<
". Robot won't move.";
864 core::Pose(srv.sceneProvider->scene().robot->getGlobalPose())});
870 setGraphEdgeCosts(
graph);
874 srv.introspector->onGlobalGraph(
graph);
882 std::vector<core::Pose> vertexPoses;
883 vertexPoses.emplace_back(srv.sceneProvider->scene().robot->getGlobalPose());
885 ARMARX_INFO <<
"Navigating along the following nodes:";
886 for (
const semrel::ShapeID& vertex : shortestPath)
889 vertexPoses.push_back(resolveGraphVertex(
graph.vertex(vertex)));
892 srv.introspector->onGlobalShortestPath(vertexPoses);
907 .helperTrajectory = std::nullopt};
912 srv.executor->execute(globalPlan->currentGlobalSegment());
919 srv.introspector->onGlobalPlannerResult(globalPlan->plan);
921 ARMARX_INFO <<
"Global planning completed. Will now start all required threads";
928 Navigator::startStack()
937 const std::scoped_lock<std::mutex> lock{runningTaskMtx};
947 config.general.tasks.replanningUpdatePeriod,
950 runningTask->start();
952 else if (not runningTask->isRunning())
954 runningTask->start();
959 if (globalPlan->useLocalPlanner(hasLocalPlanner()))
961 if (srv.executor !=
nullptr)
963 srv.executor->start(ExecutorInterface::ControllerType::LocalTrajectory);
968 if (srv.executor !=
nullptr)
970 srv.executor->start(ExecutorInterface::ControllerType::GlobalTrajectory);
978 core::Pose(srv.sceneProvider->scene().robot->getGlobalPose())});
982 Navigator::moveToAbsolute(
const std::vector<core::Pose>& waypoints,
bool sceneUpdate)
1002 ARMARX_INFO <<
"Request to move from " << srv.sceneProvider->scene().robot->getGlobalPose()
1003 <<
" to " << waypoints.back().matrix();
1006 goalReachedMonitor = std::nullopt;
1007 goalReachedMonitor = GoalReachedMonitor(
1008 waypoints.back(), srv.sceneProvider->scene(), config.goalReachedConfig);
1010 if (goalReachedMonitor->goalReached(
false))
1012 ARMARX_INFO <<
"Already at goal position. Robot won't move.";
1018 core::Pose(srv.sceneProvider->scene().robot->getGlobalPose())});
1030 srv.introspector->onGoal(waypoints.back());
1031 globalPlan = config.stack.globalPlanner->plan(waypoints.back());
1033 if (srv.drawer !=
nullptr)
1035 srv.drawer->callGenericDrawFunction(
1037 { config.stack.globalPlanner->visualizeDebugInfo(
client); });
1041 ARMARX_WARNING <<
"No drawer available. Cannot visualize global planner debug info.";
1047 if (not globalPlan.has_value())
1053 srv.introspector->failure();
1058 setupGlobalPlanSubvidision();
1060 startGlobalPathSegment(
false,
false);
1064 Navigator::updateAbsolute(
const std::vector<core::Pose>& waypoints)
1078 srv.introspector->onGoal(waypoints.back());
1079 globalPlan = config.stack.globalPlanner->plan(waypoints.back());
1083 if (not globalPlan.has_value())
1089 srv.introspector->failure();
1094 setupGlobalPlanSubvidision();
1096 startGlobalPathSegment(
false,
true);
1100 Navigator::moveToAbsoluteAlternatives(
const std::vector<core::TargetAlternative>& targets)
1114 ARMARX_INFO <<
"Request to move from " << srv.sceneProvider->scene().robot->getGlobalPose()
1115 <<
" to " << targets.size() <<
" alternatives";
1117 lastAllAlternativesImpossible = std::nullopt;
1120 for (
const auto& t : targets)
1122 GoalReachedMonitor monitor(
1123 t.target, srv.sceneProvider->scene(), config.goalReachedConfig);
1125 if (monitor.goalReached(
false))
1127 ARMARX_INFO <<
"Already at a possible goal position. Robot won't move.";
1131 core::Pose(srv.sceneProvider->scene().robot->getGlobalPose())});
1138 targetAlternatives = targets;
1141 if (setupTargetAlternatives())
1144 setupGlobalPlanSubvidision();
1146 startGlobalPathSegment(
false,
false);
1151 Navigator::setupTargetAlternatives()
1155 globalPlan = std::nullopt;
1161 const auto spfaPlanner =
1162 std::dynamic_pointer_cast<global_planning::SPFA>(config.stack.globalPlanner);
1163 if (spfaPlanner !=
nullptr)
1165 const core::Pose start(srv.sceneProvider->scene().robot->getGlobalPose());
1167 const auto planningResult = spfaPlanner->executePlanner(start);
1169 for (
const auto& target : targetAlternatives)
1172 const auto path = spfaPlanner->calculatePath(planningResult,
target.target);
1173 if (path.has_value() and verifyGlobalPathPossible(path->trajectory))
1176 srv.introspector->onGoal(
target.target);
1177 goalReachedMonitor = std::nullopt;
1178 goalReachedMonitor = GoalReachedMonitor(
1179 target.target, srv.sceneProvider->scene(), config.goalReachedConfig);
1182 <<
" with priority " <<
target.priority;
1188 <<
" not reachable";
1194 const auto goal = targetAlternatives.front().target;
1196 <<
"Current global planner is not an SPFA planner, discarding alternatives!";
1198 globalPlan = config.stack.globalPlanner->plan(goal);
1199 srv.introspector->onGoal(goal);
1200 goalReachedMonitor = std::nullopt;
1201 goalReachedMonitor =
1202 GoalReachedMonitor(goal, srv.sceneProvider->scene(), config.goalReachedConfig);
1205 targetAlternatives.clear();
1208 if (srv.drawer !=
nullptr)
1210 srv.drawer->callGenericDrawFunction(
1212 { config.stack.globalPlanner->visualizeDebugInfo(
client); });
1216 ARMARX_WARNING <<
"No drawer available. Cannot visualize global planner debug info.";
1222 if (not globalPlan.has_value())
1228 srv.introspector->failure();
1236 Navigator::setupGlobalPlanSubvidision()
1240 const auto& globalPlanPoses = globalPlan->plan.trajectory.poses();
1243 if (not globalPlan->plan.trajectory.points().empty())
1245 ARMARX_INFO <<
"Trajectory final pose " << globalPlanPoses.back().matrix();
1247 goalReachedMonitor->updateGoal(globalPlanPoses.back());
1252 reportPathGeometry(globalPlan->plan.trajectory);
1257 if ((not config.general.subdivision.enable) or (not hasLocalPlanner()) or
1258 globalPlan->plan.trajectory.points().empty())
1268 ARMARX_INFO <<
"Starting subdividing global path.";
1270 ARMARX_CHECK(srv.sceneProvider->scene().staticScene.has_value());
1272 srv.sceneProvider->scene().staticScene->distanceToObstaclesCostmap.has_value());
1273 const auto& costmap = srv.sceneProvider->scene().staticScene->distanceToObstaclesCostmap;
1275 std::vector<bool> localPlanEligibility;
1276 localPlanEligibility.reserve(globalPlan->plan.trajectory.points().size());
1277 for (
const auto& pose : globalPlanPoses)
1279 Eigen::Vector2f pt =
conv::to2D(pose.translation());
1280 localPlanEligibility.push_back(costmap->value(pt).value_or(0) >
1281 config.general.subdivision.localPlannerCostmapThreshold);
1285 bool falseSequence =
false;
1286 const int n = localPlanEligibility.size();
1287 const float expansion = config.general.subdivision.globalPlanExpansionDistance;
1290 for (
int i = 0; i <
n;)
1294 if (localPlanEligibility[i])
1297 falseSequence =
false;
1300 float totalDistance = 0;
1303 for (; (totalDistance < expansion) and (i < n); i++)
1305 localPlanEligibility[i] =
false;
1306 totalDistance += (globalPlanPoses[i - 1].translation() -
1307 globalPlanPoses[i].translation())
1317 if (not localPlanEligibility[i])
1320 falseSequence =
true;
1323 float totalDistance = 0;
1324 for (
int j = i - 1; (totalDistance < expansion) and (j >= 0); j--)
1326 localPlanEligibility[j] =
false;
1327 totalDistance += (globalPlanPoses[j].translation() -
1328 globalPlanPoses[j + 1].translation())
1344 GlobalPathSubdivision::Subdivision segment;
1346 segment.useLocalPlanner = localPlanEligibility[0];
1349 const auto checkSegmentLength = [&]()
1352 if (not segment.useLocalPlanner)
1357 const auto& subTrajectory =
1358 globalPlan->plan.trajectory.getSubTrajectory(segment.s, segment.t);
1359 const float segmentLength = subTrajectory.length();
1360 if (segmentLength >= config.general.subdivision.minSegmentDistance)
1365 if (globalPlan->subdivision.empty() and
static_cast<int>(segment.t) >= n)
1368 ARMARX_VERBOSE <<
"Segment " << globalPlan->subdivision.size() <<
": [" << segment.s
1369 <<
", " << segment.t
1370 <<
"); is the only segment. length=" << segmentLength;
1374 ARMARX_VERBOSE <<
"Segment " << globalPlan->subdivision.size() <<
": [" << segment.s
1375 <<
", " << segment.t
1376 <<
"); was too short for local planner: " << segmentLength;
1378 if (globalPlan->subdivision.empty())
1381 segment.useLocalPlanner =
false;
1383 <<
"First segment, convert to global planner segment and grow it further.";
1387 if (
static_cast<int>(segment.t) < n)
1390 segment = globalPlan->subdivision.back();
1391 globalPlan->subdivision.pop_back();
1392 ARMARX_VERBOSE <<
"Removing preceding segment and grow it further.";
1397 globalPlan->subdivision.back().t = segment.t;
1398 ARMARX_VERBOSE <<
"Last segment; extent previous segment to the end.";
1404 for (
int i = 0; i <
n; i++)
1407 while (i < n and localPlanEligibility[i] == segment.useLocalPlanner)
1413 if (checkSegmentLength())
1415 globalPlan->subdivision.emplace_back(segment);
1416 ARMARX_VERBOSE <<
"Segment " << globalPlan->subdivision.size() <<
": [" << segment.s
1417 <<
", " << segment.t
1418 <<
"); localPlanner=" << segment.useLocalPlanner;
1422 segment.useLocalPlanner = localPlanEligibility[i];
1429 std::size_t lastSegmentEnd = 0;
1430 for (
const auto& segment : globalPlan->subdivision)
1433 const std::ptrdiff_t segmentLength = segment.t - segment.s;
1435 lastSegmentEnd = segment.t;
1439 globalPlan->currentSegment = 0;
1440 srv.introspector->onGlobalPlannerSubdivision(globalPlan.value());
1442 ARMARX_INFO <<
"Divided global path into " << globalPlan->subdivision.size() <<
" segments";
1458 constexpr std::size_t minimumWaypointsForToppra = 4;
1461 trajectory.points().size() < minimumWaypointsForToppra)
1464 rampingFallback->apply(trajectory, startVelocity);
1470 parametrization->apply(trajectory, startVelocity);
1472 catch (
const std::exception& e)
1476 ARMARX_WARNING <<
"Trajectory parametrization failed (" << e.what()
1477 <<
"). Falling back to ramping for this request.";
1480 trajectory = planned;
1481 rampingFallback->apply(trajectory, startVelocity);
1489 ARMARX_INFO <<
"Parametrized " << trajectory.points().size() <<
" waypoints with "
1492 <<
" s (the planner's obstacle-aware velocities alone would take "
1496 if (config.general.parametrizationDump)
1499 {.requested = config.stack.generalConfig.parametrization,
1502 .parametrized = trajectory,
1504 .driveParams = driveParams,
1505 .maxVelocity = config.stack.generalConfig.maxVel.linear,
1506 .maxAngularVelocity = config.stack.generalConfig.maxVel.angular},
1507 config.general.parametrizationDumpPath);
1512 Navigator::resumeAfterEmergencyStop()
1514 if (not globalPlan.has_value() or isPaused() or isStopped())
1519 if (globalPlan->useLocalPlanner(hasLocalPlanner()))
1527 if (trajectory.points().empty())
1533 core::Pose(srv.sceneProvider->scene().robot->getGlobalPose()).translation();
1537 auto [remaining, endsAtGoal] = trajectory.getSubTrajectory(
1538 robotPosition, std::numeric_limits<float>::infinity());
1540 if (remaining.points().size() < 2)
1542 ARMARX_WARNING <<
"Cannot re-anchor the trajectory after the emergency stop: only "
1543 << remaining.points().size()
1544 <<
" waypoints remain. Resuming on the trajectory as planned.";
1548 ARMARX_IMPORTANT <<
"Re-anchoring the trajectory at the robot's current pose after the "
1550 << remaining.points().size() <<
" of " << trajectory.points().size()
1551 <<
" waypoints remain.";
1553 applyParametrization(remaining, config.stack.generalConfig.boundaryVelocity);
1555 updateExecutor(remaining);
1559 Navigator::startGlobalPathSegment(
bool incrementSegment,
bool rampFromCurrentVelocity)
1561 if (not globalPlan->subdivision.empty())
1565 if (incrementSegment)
1567 globalPlan->currentSegment++;
1570 if (globalPlan->currentSegment >= globalPlan->subdivision.size())
1576 ARMARX_INFO <<
"Starting segment " << globalPlan->currentSegment;
1577 const auto& currentSegment = globalPlan->subdivision[globalPlan->currentSegment];
1579 globalPlan->currentSegmentTrajectory =
1580 globalPlan->plan.trajectory.getSubTrajectory(currentSegment.s, currentSegment.t);
1582 applyParametrization(globalPlan->currentGlobalSegment(),
1583 rampFromCurrentVelocity
1584 ? srv.sceneProvider->scene().platformVelocity.linear.norm()
1585 : config.stack.generalConfig.boundaryVelocity);
1587 goalReachedMonitor->updateGoal(
1588 globalPlan->currentGlobalSegment().points().back().waypoint.pose);
1592 if (incrementSegment)
1597 applyParametrization(globalPlan->currentGlobalSegment(),
1598 rampFromCurrentVelocity
1599 ? srv.sceneProvider->scene().platformVelocity.linear.norm()
1600 : config.stack.generalConfig.boundaryVelocity);
1602 goalReachedMonitor->updateGoal(
1603 globalPlan->currentGlobalSegment().points().back().waypoint.pose);
1606 srv.introspector->onGlobalPlannerResult(globalPlan->plan);
1609 if (globalPlan->useLocalPlanner(hasLocalPlanner()))
1611 const auto localPlannerResult = updateLocalPlanner();
1612 updateExecutor(localPlannerResult);
1613 updateIntrospector(localPlannerResult);
1617 updateExecutor(globalPlan->currentGlobalSegment());
1620 ARMARX_INFO <<
"Start executing global plan segment (local planner="
1621 << globalPlan->useLocalPlanner(hasLocalPlanner())
1622 <<
"). Will now start all required threads.";
1638 const std::optional<std::string>& providerName)
1643 const auto resolveLocation =
1645 const std::optional<std::string>& providerName) -> std::vector<core::Location>
1647 const auto locations = srv.sceneProvider->scene().staticScene->locations;
1650 for (
const auto&
location : locations)
1656 const auto matchingLocs =
1661 <<
QUOTED(providerName.value_or(
"~unset~")) <<
".";
1662 return matchingLocs;
1667 <<
"The given location does not match the format <location>(:<instance-id>)? '"
1670 std::string instanceID;
1671 if (
split.size() == 2)
1674 instanceID =
split.back();
1677 const auto matchingLocations = resolveLocation(
split.front(), providerName);
1680 if (matchingLocations.empty())
1683 <<
QUOTED(providerName.value_or(
"~unset~"));
1686 if (matchingLocations.size() > 1)
1689 <<
" from provider " <<
QUOTED(providerName.value_or(
"~unset~"));
1690 for (
const auto&
location : matchingLocations)
1698 srv.sceneProvider->scene().staticScene->objectInfo,
1699 matchingLocations.front().framedPose,
1702 if (goal.pose.has_value())
1704 moveToAbsolute({goal.pose.value()},
false);
1724 const std::scoped_lock<std::mutex> lock{runMtx};
1729 <<
"Called Navigator::run() although shouldRun is false. Directly returning.";
1742 [&]() { updateScene(
true ); });
1747 srv.debugObserverHelper->setDebugObserverDatafield(
"scene update [ms]",
1748 duration.toMilliSecondsDouble());
1755 if (globalPlan.has_value() and not targetAlternatives.empty())
1757 checkGlobalPathAlternatives();
1762 if ((srv.introspector !=
nullptr) and (srv.sceneProvider !=
nullptr) and
1763 srv.sceneProvider->scene().robot)
1766 srv.introspector->onRobotPose(
1767 core::Pose(srv.sceneProvider->scene().robot->getGlobalPose()));
1836 if (srv.executor !=
nullptr and srv.executor->consumeEmergencyStopRelease())
1838 resumeAfterEmergencyStop();
1848 if (globalPlan->useLocalPlanner(hasLocalPlanner()))
1850 const auto localPlannerResult = updateLocalPlanner();
1851 updateExecutor(localPlannerResult);
1852 updateIntrospector(localPlannerResult);
1854 if (srv.executor !=
nullptr && localPlannerResult.has_value() &&
1855 not isPaused() && not isStopped())
1857 srv.executor->ensureIsActive(
1858 ExecutorInterface::ControllerType::LocalTrajectory);
1861 else if (srv.executor !=
nullptr && not isPaused() && not isStopped())
1863 srv.executor->ensureIsActive(
1864 ExecutorInterface::ControllerType::GlobalTrajectory);
1870 srv.debugObserverHelper->setDebugObserverDatafield(
"local planner update [ms]",
1871 duration.toMilliSecondsDouble());
1878 if (hasSafetyGuard())
1881 const auto result = updateSafetyGuard();
1882 srv.debugObserverHelper->setDebugObserverDatafield(
"safety_guard.limit_linear",
1883 result.twistLimits.linear);
1884 srv.debugObserverHelper->setDebugObserverDatafield(
"safety_guard.limit_angular",
1885 result.twistLimits.angular);
1887 if (srv.executor !=
nullptr)
1889 srv.executor->updateVelocityLimits(result.twistLimits);
1894 checkRobotBlocked(result);
1899 if (srv.executor !=
nullptr)
1901 srv.executor->updateVelocityLimits(config.stack.generalConfig.maxVel);
1909 srv.debugObserverHelper->setDebugObserverDatafield(
"velocity_factor",
1910 velocityFactor.load());
1913 if (srv.executor !=
nullptr)
1915 srv.executor->updateVelocityFactor(velocityFactor);
1930 srv.debugObserverHelper->setDebugObserverDatafield(
"monitor update [ms]",
1931 duration.toMilliSecondsDouble());
1936 Navigator::updateSafetyGuard()
1940 const core::Pose global_T_robot(srv.sceneProvider->scene().robot->getGlobalPose());
1941 const auto proj = globalPlan->currentGlobalSegment().getProjection(
1943 const Eigen::Vector3f global_V_movement = proj.wayPointAfter.waypoint.pose.translation() -
1944 proj.projection.waypoint.pose.translation();
1946 return config.stack.safetyGuard->computeSafetyLimits(global_V_movement.head<2>());
1952 const auto& cfg = config.general.blockedReplanning;
1954 if (not cfg.enabled)
1960 if (isPaused() or isStopped() or not globalPlan.has_value() or
1961 not goalReachedMonitor.has_value())
1963 blockedSince = std::nullopt;
1969 const bool blocked = (result.twistLimits.linear < cfg.linearLimit) or
1970 (result.twistLimits.angular < cfg.angularLimit);
1975 blockedSince = std::nullopt;
1980 if (not blockedSince.has_value())
1987 const armarx::Duration blockedFor = now - blockedSince.value();
1988 srv.debugObserverHelper->setDebugObserverDatafield(
"safety_guard.blocked_for [ms]",
1998 <<
"ms (safety guard limits near zero). Re-running the global planner.";
2001 blockedSince = std::nullopt;
2004 updateAbsolute({goalReachedMonitor->goal()});
2008 Navigator::hasSafetyGuard()
const
2010 return config.stack.safetyGuard !=
nullptr;
2014 Navigator::hasLocalPlanner() const noexcept
2016 return config.stack.localPlanner !=
nullptr;
2020 Navigator::updateScene(
const bool fullUpdate)
2027 Navigator::checkGlobalPathAlternatives()
2031 const auto replanAlternatives = [&]()
2033 lastAllAlternativesImpossible = std::nullopt;
2038 const auto previousGlobalPlan = globalPlan;
2040 if (setupTargetAlternatives())
2044 setupGlobalPlanSubvidision();
2048 ARMARX_INFO <<
"No possible alternatives, restoring previous global path.";
2049 globalPlan = std::move(previousGlobalPlan);
2054 startGlobalPathSegment(
false,
true);
2057 if (not verifyGlobalPathPossible(globalPlan->currentGlobalSegment()))
2061 if (lastAllAlternativesImpossible.has_value() and
2064 config.general.targetAlternativesFilterTimeSeconds)))
2067 <<
"Current globalPlan invalid, waiting for timeout before replanning.";
2071 ARMARX_INFO <<
"Global path no longer possible, trying other alternatives!";
2073 replanAlternatives();
2076 else if (lastAllAlternativesImpossible.has_value())
2081 <<
"Previously invalid global plan valid again -> replanning all alternatives";
2083 replanAlternatives();
2090 const auto& costmap = srv.sceneProvider->scene().staticScene->distanceToObstaclesCostmap;
2093 for (
const auto& pt : plan.points())
2095 const auto& pose = pt.waypoint.pose;
2096 const Eigen::Vector2f position =
conv::to2D(pose.translation());
2097 const auto vertex = costmap->toVertex(position);
2098 if (not costmap->isValid(vertex.index) or costmap->isInCollision(vertex.position))
2107 std::optional<local_planning::LocalPlannerResult>
2108 Navigator::updateLocalPlanner()
2111 const std::scoped_lock<std::mutex> lock{updateLocalPlannerMtx};
2119 const auto& globalTrajectory = globalPlan->currentGlobalSegment();
2120 ARMARX_VERBOSE << globalTrajectory.points().size() <<
" points in global plan";
2121 localPlan = config.stack.localPlanner->plan(globalTrajectory);
2123 if (localPlan.has_value())
2138 return std::nullopt;
2144 return std::nullopt;
2148 Navigator::updateExecutor(
const std::optional<local_planning::LocalPlannerResult>& localPlan)
2150 if (srv.executor ==
nullptr)
2156 if (isPaused() or isStopped())
2163 if (not localPlan.has_value())
2168 srv.executor->stop();
2189 srv.executor->execute(localPlan->trajectory,
true);
2195 if (srv.executor ==
nullptr)
2201 << globalTrajectory.points().size() <<
" points.";
2211 if (globalTrajectory.points().empty())
2215 srv.executor->stop();
2219 ARMARX_IMPORTANT <<
"Executing global plan with " << globalTrajectory.points().size()
2222 srv.executor->execute(globalTrajectory,
false);
2226 Navigator::updateIntrospector(
2227 const std::optional<local_planning::LocalPlannerResult>& localPlan)
2231 srv.introspector->onLocalPlannerResult(localPlan);
2235 Navigator::updateMonitor()
2239 if (not goalReachedMonitor.has_value())
2247 const auto status = goalReachedMonitor->status();
2248 armarx::DebugObserverHelper* debugObserver = srv.debugObserverHelper;
2250 if (debugObserver !=
nullptr)
2255 goalReachedMonitor->getConfig().posTh);
2257 goalReachedMonitor->getConfig().oriTh);
2260 if (not isPaused() and goalReachedMonitor->goalReached())
2263 ARMARX_INFO <<
"Current global path segment finished!";
2266 if (startGlobalPathSegment(
true,
true))
2269 ARMARX_INFO <<
"Goal " << goalReachedMonitor->goal().translation().head<2>()
2276 if (srv.executor !=
nullptr)
2278 srv.executor->dumpDiagnostics();
2285 {
core::Pose(srv.sceneProvider->scene().robot->getGlobalPose())}});
2287 srv.introspector->success();
2293 Navigator::stopAllThreads()
2295 const std::scoped_lock<std::mutex> lock{runningTaskMtx};
2299 runningTask->stop();
2349 <<
"Scaling factor for velocity may not be negative, but is " << velocityFactor;
2351 <<
"Scaling factor for velocity may not be > 1, but is " << velocityFactor;
2353 this->velocityFactor = velocityFactor;
2361 executorEnabled.store(
false);
2363 if (srv.executor !=
nullptr)
2366 srv.executor->stop();
2375 executorEnabled.store(
true);
2377 if (srv.executor !=
nullptr)
2379 if (hasLocalPlanner())
2400 goalReachedMonitor.reset();
2401 goalReachedMonitor = std::nullopt;
2407 return not executorEnabled.load();
2413 return (not shouldRun) or (not runningTask) or (not runningTask->isRunning());
2417 config{std::move(other.config)},
2418 srv{std::move(other.srv)},
2419 parametrization{std::move(other.parametrization)},
2420 executorEnabled{other.executorEnabled.load()}
#define ARMARX_CHECK_NOT_EMPTY(c)
SpamFilterDataPtr deactivateSpam(SpamFilterDataPtr const &spamFilter, float deactivationDurationSec, const std::string &identifier, bool deactivate)
std::size_t degenerate
How many segments were excluded as too short to carry a direction.
static DateTime Now()
Current time on the virtual clock.
void setDebugObserverDatafield(const std::string &channelName, const std::string &datafieldName, const TimedVariantPtr &value) const
static Duration SecondsDouble(double seconds)
Constructs a duration in seconds.
The periodic task executes one thread method repeatedly using the time period specified in the constr...
double toMilliSecondsDouble() const
Returns the amount of milliseconds.
static Duration measure(std::function< void(void)> subjectToMeasure, ClockType clockType=ClockType::Virtual)
Measures the duration needed to execute the given lambda and returns it.
Time-optimal reparametrization under per-motor torque and command-ramp limits.
static bool available()
Whether the python side was found at configure time. False means apply() throws.
static std::string unavailableReason()
Why it is unavailable, as recorded at configure time. Empty when available.
void moveToAlternatives(const std::vector< core::TargetAlternative > &targets, core::NavigationFrame navigationFrame) override
void moveTowards(const core::Direction &direction, core::NavigationFrame navigationFrame) override
void update(const std::vector< core::Pose > &waypoints, core::NavigationFrame navigationFrame) override
Navigator(const Config &config, const InjectedServices &services)
void setVelocityFactor(float velocityFactor) override
void moveToLocation(const std::string &location, const std::optional< std::string > &providerName) override
bool isPaused() const noexcept override
void moveTo(const std::vector< core::Pose > &waypoints, core::NavigationFrame navigationFrame) override
bool isStopped() const noexcept override
Brief description of class targets.
#define ARMARX_CHECK_GREATER(lhs, rhs)
This macro evaluates whether lhs is greater (>) than rhs and if it turns out to be false it will thro...
#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_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_CHECK_GREATER_EQUAL(lhs, rhs)
This macro evaluates whether lhs is greater or equal (>=) rhs and if it turns out to be false it will...
#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_CHECK_EQUAL(lhs, rhs)
This macro evaluates whether lhs is equal (==) rhs and if it turns out to be false it will throw an E...
#define ARMARX_INFO
The normal logging level.
#define ARMARX_IMPORTANT
The logging level for always important information, but expected behaviour (in contrast to ARMARX_WAR...
#define ARMARX_ERROR
The logging level for unexpected behaviour, that must be fixed.
#define ARMARX_DEBUG
The logging level for output that is only interesting while debugging.
#define ARMARX_WARNING
The logging level for unexpected behaviour, but not a serious problem.
#define ARMARX_VERBOSE
The logging level for verbose information.
armarx::core::time::Duration Duration
Toppra::DriveParams LoadDriveParams(const std::filesystem::path &configFile)
Read Toppra::DriveParams from a PlatformDynamics<Robot>.json.
std::shared_ptr< TrajectoryParametrization > TrajectoryParametrizationPtr
std::filesystem::path DriveParamsPath(const std::string &robot)
Resolve config/platform/PlatformDynamics<robot>.json inside the armarx_navigation package.
This file is part of ArmarX.
std::vector< Eigen::Vector2f > to2D(const std::vector< Eigen::Vector3f > &v)
std::vector< core::Location > findMatchingLocations(const std::vector< core::Location > &locations, const std::string &locationName, const std::optional< std::string > &provider)
Graph::ConstVertex getVertexByName(const std::string &vertexName, const Graph &graph)
Eigen::Vector3f Direction
TrajectoryParametrization
How the velocities along a planned path are assigned.
@ Ramping
Ramp down at the start, the goal and every corner. The stack's historical behaviour.
@ Toppra
Time-optimal under per-motor torque and command-ramp limits.
std::string ToString(const TrajectoryParametrization parametrization)
The lowercase name used in configs, logs and the analysis application's results.
std::vector< GraphPath > findPathsTo(Graph::ConstVertex vertex, const Graph &graph)
void resolveLocation(Graph::Vertex &vertex, const aron::data::DictPtr &locationData)
const core::Graph & getSubgraph(const std::string &vertexName, const Graphs &graphs)
This file is part of ArmarX.
This file is part of ArmarX.
This file is part of ArmarX.
This file is part of ArmarX.
std::vector< semrel::ShapeID > GraphPath
GraphPath findShortestPath(core::Graph graph, const core::Graph::ConstVertex &startVertex, const core::Graph::ConstVertex &goalVertex)
void writeParametrizationDump(const ParametrizationRecord &record, const std::string &path)
Write record as JSON, for offline rendering with plot-navigation-request.
std::vector< std::string > split(const std::string &source, const std::string &splitBy, bool trimElements=false, bool removeEmptyElements=false)
std::string GetHandledExceptionString()
Vertex target(const detail::edge_base< Directed, Vertex > &e, const PCG &)
constexpr auto n() noexcept
TrajectoryParametrization parametrization
How the planned velocity profile is (re-)assigned before execution.
Event describing that the global trajectory was updated.
Event describing that the targeted goal was successfully reached.
Event describing that the local trajectory was updated.
GlobalPathSubdivision(const global_planning::GlobalPlannerResult &globalPlan)
global_planning::GlobalPlannerResult plan
const core::GlobalTrajectory & currentGlobalSegment() const
bool useLocalPlanner(bool hasLocalPlanner)
core::GlobalTrajectory currentSegmentTrajectory
std::size_t currentSegment
std::vector< Subdivision > subdivision
bool platformDynamicsEnabled