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 const std::scoped_lock lock{stateMtx, srv.sceneProvider->sceneMutex()};
374 std::vector<core::Pose> globalWaypoints;
375 switch (navigationFrame)
378 globalWaypoints = waypoints;
381 globalWaypoints.reserve(waypoints.size());
385 const core::Pose global_T_robot(srv.sceneProvider->scene().robot->getGlobalPose());
386 ARMARX_VERBOSE <<
"Initial robot pose: " << global_T_robot.matrix();
388 std::transform(std::begin(waypoints),
390 std::back_inserter(globalWaypoints),
391 [&](
const core::Pose& p) {
return global_T_robot * p; });
396 moveToAbsolute(globalWaypoints);
405 const std::scoped_lock lock{stateMtx, srv.sceneProvider->sceneMutex()};
407 ARMARX_INFO <<
"Received moveToAlternatives() request.";
409 std::vector<core::TargetAlternative> globalTargets;
410 switch (navigationFrame)
416 globalTargets.reserve(
targets.size());
420 const core::Pose global_T_robot(srv.sceneProvider->scene().robot->getGlobalPose());
421 ARMARX_VERBOSE <<
"Initial robot pose: " << global_T_robot.matrix();
423 std::transform(std::begin(
targets),
425 std::back_inserter(globalTargets),
434 moveToAbsoluteAlternatives(globalTargets);
443 const std::scoped_lock lock{stateMtx, srv.sceneProvider->sceneMutex()};
447 std::vector<core::Pose> globalWaypoints;
448 switch (navigationFrame)
451 globalWaypoints = waypoints;
454 globalWaypoints.reserve(waypoints.size());
456 std::begin(waypoints),
458 std::back_inserter(globalWaypoints),
460 {
return core::Pose(srv.sceneProvider->scene().robot->getGlobalPose()) * p; });
465 updateAbsolute(globalWaypoints);
472 const core::Graph::ConstVertex& startVertex,
473 const core::Graph::ConstVertex& goalVertex)
476 <<
graph.numEdges() <<
" edges.";
478 std::vector<core::Graph::VertexDescriptor> predecessors(
graph.numVertices());
479 std::vector<int> d(num_vertices(
graph));
485 core::Graph::VertexDescriptor start = startVertex.descriptor();
487 auto predecessorMap = boost::make_iterator_property_map(
488 predecessors.begin(), boost::get(boost::vertex_index,
graph));
490 auto params = boost::predecessor_map(predecessorMap)
491 .distance_map(boost::make_iterator_property_map(
492 d.begin(), boost::get(boost::vertex_index,
graph)))
493 .weight_map(weightMap);
500 for (
const auto& edge :
graph.edges())
502 ARMARX_VERBOSE << edge.sourceObjectID() <<
" -> " << edge.targetObjectID() <<
": "
503 << edge.attrib().m_value;
506 std::vector<core::Graph::EdgeDescriptor> edgesToBeRemoved;
507 for (
const auto edge :
graph.edges())
509 if (edge.attrib().m_value == std::numeric_limits<float>::max())
511 edgesToBeRemoved.push_back(edge.descriptor());
515 for (
const auto edge : edgesToBeRemoved)
517 boost::remove_edge(edge,
graph);
521 for (
const auto& edge :
graph.edges())
523 ARMARX_VERBOSE << edge.sourceObjectID() <<
" -> " << edge.targetObjectID() <<
": "
524 << edge.attrib().m_value;
525 ARMARX_VERBOSE <<
"Edge: " << edge <<
"cost: " << edge.attrib().cost()
526 <<
", type: " <<
static_cast<int>(edge.attrib().strategy)
527 <<
" , has traj " << edge.attrib().trajectory.has_value();
531 <<
graph.vertex(start).objectID() <<
"` to vertex `"
532 <<
graph.vertex(goalVertex.descriptor()).objectID() <<
"`";
534 boost::dijkstra_shortest_paths(
graph, start, params);
542 core::Graph::VertexDescriptor currentVertex = goalVertex.descriptor();
543 while (currentVertex != startVertex.descriptor())
545 shortestPath.push_back(
graph.vertex(currentVertex).objectID());
549 auto parent = predecessorMap[currentVertex];
555 auto outEdges =
graph.vertex(parent).outEdges();
556 ARMARX_VERBOSE <<
"Parent has " << std::distance(outEdges.begin(), outEdges.end())
560 <<
"Cannot reach another vertex from vertex `"
561 <<
graph.vertex(parent).objectID();
563 auto edgeIt = std::find_if(outEdges.begin(),
565 [¤tVertex](
const auto& edge) ->
bool
566 { return edge.target().descriptor() == currentVertex; });
571 currentVertex = parent;
574 shortestPath.push_back(startVertex.objectID());
580 shortestPath = shortestPath | ranges::views::reverse | ranges::to_vector;
588 Navigator::convertToTrajectory(
const GraphPath& shortestPath,
const core::Graph&
graph)
const
593 <<
"At least start and goal vertices must be available";
597 std::vector<core::GlobalTrajectoryPoint> trajectoryPoints;
604 ARMARX_VERBOSE <<
"Shortest path with " << shortestPath.size() <<
" vertices";
607 for (
size_t i = 0; i < shortestPath.size() - 1; i++)
613 const core::Graph::ConstEdge edge =
614 graph.edge(
graph.vertex(shortestPath.at(i)),
graph.vertex(shortestPath.at(i + 1)));
620 switch (edge.attrib().strategy)
630 ARMARX_INFO <<
"Length: " << edge.attrib().trajectory->length();
635 << edge.attrib().trajectory->points().size() <<
" waypoints";
640 const std::vector<core::GlobalTrajectoryPoint> edgeTrajectoryPoints =
641 edge.attrib().trajectory->points();
644 const int offset = trajectoryPoints.empty() ? 0 : 1;
646 if (edgeTrajectoryPoints.size() > 2)
650 trajectoryPoints.insert(trajectoryPoints.end(),
651 edgeTrajectoryPoints.begin() + offset,
652 edgeTrajectoryPoints.end());
677 const float point2pointVelocity = 400;
679 const core::GlobalTrajectoryPoint currentTrajPt = {
680 .waypoint = {.pose = resolveGraphVertex(graph.vertex(shortestPath.at(i)))},
681 .velocity = point2pointVelocity};
685 resolveGraphVertex(
graph.vertex(shortestPath.at(i + 1)))},
709 trajectoryPoints.push_back(currentTrajPt);
710 trajectoryPoints.push_back(nextTrajPt);
721 ARMARX_INFO <<
"Trajectory consists of " << trajectoryPoints.size() <<
" points";
723 for (
const auto& pt : trajectoryPoints)
728 return {trajectoryPoints};
732 Navigator::convertToGraph(
const std::vector<client::WaypointTarget>& targets)
const
735 GraphBuilder graphBuilder;
736 graphBuilder.initialize(
core::Pose(srv.sceneProvider->scene().robot->getGlobalPose()));
739 for (
const auto& target : targets)
776 if (
target.pose.has_value())
778 graphBuilder.connect(
target.pose.value(),
target.strategy);
785 if (not
target.locationId->empty())
788 target.locationId.value(), srv.sceneProvider->scene().graph->subgraphs);
793 << vertex.attrib().getPose();
798 ARMARX_INFO <<
"Found " << routes.size() <<
" routes to location `"
799 <<
target.locationId.value();
802 <<
"` is not a reachable vertex on the graph!";
805 graphBuilder.connect(routes,
811 ARMARX_ERROR <<
"Either `location_id` or `pose` has to be provided!";
814 const auto goalVertex = graphBuilder.getGraph().vertex(graphBuilder.goalVertex());
815 ARMARX_INFO <<
"Goal vertex is " <<
QUOTED(goalVertex.attrib().getLocationName());
821 Navigator::resolveGraphVertex(
const core::Graph::ConstVertex& vertex)
const
824 srv.sceneProvider->scene().staticScene->objectInfo,
825 vertex.attrib().getPose());
827 <<
"The location of vertex " << vertex.attrib().getLocationName()
828 <<
" couldn't be resolved (" << goal.errorMsg <<
")";
829 return goal.pose.value();
838 const std::scoped_lock lock{stateMtx, srv.sceneProvider->sceneMutex()};
842 <<
"only absolute movement implemented atm.";
854 auto graphBuilder = convertToGraph(
targets);
859 auto startVertex = graphBuilder.startVertex;
860 auto goalVertex = graphBuilder.getGraph().vertex(graphBuilder.goalVertex());
862 ARMARX_INFO <<
"Goal pose according to graph is " << graphBuilder.goalPose().matrix();
868 goalReachedMonitor = std::nullopt;
870 graphBuilder.goalPose(), srv.sceneProvider->scene(), config.goalReachedConfig);
872 if (goalReachedMonitor->goalReached(
false))
875 << goalReachedMonitor->goal().translation().head<2>()
876 <<
". Robot won't move.";
880 core::Pose(srv.sceneProvider->scene().robot->getGlobalPose())});
886 setGraphEdgeCosts(
graph);
890 srv.introspector->onGlobalGraph(
graph);
898 std::vector<core::Pose> vertexPoses;
899 vertexPoses.emplace_back(srv.sceneProvider->scene().robot->getGlobalPose());
901 ARMARX_INFO <<
"Navigating along the following nodes:";
902 for (
const semrel::ShapeID& vertex : shortestPath)
905 vertexPoses.push_back(resolveGraphVertex(
graph.vertex(vertex)));
908 srv.introspector->onGlobalShortestPath(vertexPoses);
923 .helperTrajectory = std::nullopt};
928 srv.executor->execute(globalPlan->currentGlobalSegment());
935 srv.introspector->onGlobalPlannerResult(globalPlan->plan);
937 ARMARX_INFO <<
"Global planning completed. Will now start all required threads";
944 Navigator::startStack()
953 const std::scoped_lock<std::mutex> lock{runningTaskMtx};
963 config.general.tasks.replanningUpdatePeriod,
966 runningTask->start();
968 else if (not runningTask->isRunning())
970 runningTask->start();
975 if (globalPlan->useLocalPlanner(hasLocalPlanner()))
977 if (srv.executor !=
nullptr)
979 srv.executor->start(ExecutorInterface::ControllerType::LocalTrajectory);
984 if (srv.executor !=
nullptr)
986 srv.executor->start(ExecutorInterface::ControllerType::GlobalTrajectory);
994 core::Pose(srv.sceneProvider->scene().robot->getGlobalPose())});
998 Navigator::moveToAbsolute(
const std::vector<core::Pose>& waypoints,
bool sceneUpdate)
1018 ARMARX_INFO <<
"Request to move from " << srv.sceneProvider->scene().robot->getGlobalPose()
1019 <<
" to " << waypoints.back().matrix();
1022 goalReachedMonitor = std::nullopt;
1023 goalReachedMonitor = GoalReachedMonitor(
1024 waypoints.back(), srv.sceneProvider->scene(), config.goalReachedConfig);
1026 if (goalReachedMonitor->goalReached(
false))
1028 ARMARX_INFO <<
"Already at goal position. Robot won't move.";
1034 core::Pose(srv.sceneProvider->scene().robot->getGlobalPose())});
1046 srv.introspector->onGoal(waypoints.back());
1047 globalPlan = config.stack.globalPlanner->plan(waypoints.back());
1049 if (srv.drawer !=
nullptr)
1051 srv.drawer->callGenericDrawFunction(
1053 { config.stack.globalPlanner->visualizeDebugInfo(
client); });
1057 ARMARX_WARNING <<
"No drawer available. Cannot visualize global planner debug info.";
1063 if (not globalPlan.has_value())
1069 srv.introspector->failure();
1079 setupGlobalPlanSubvidision();
1081 startGlobalPathSegment(
false,
false);
1085 Navigator::updateAbsolute(
const std::vector<core::Pose>& waypoints)
1099 srv.introspector->onGoal(waypoints.back());
1100 globalPlan = config.stack.globalPlanner->plan(waypoints.back());
1104 if (not globalPlan.has_value())
1110 srv.introspector->failure();
1115 setupGlobalPlanSubvidision();
1117 startGlobalPathSegment(
false,
true);
1121 Navigator::moveToAbsoluteAlternatives(
const std::vector<core::TargetAlternative>& targets)
1135 ARMARX_INFO <<
"Request to move from " << srv.sceneProvider->scene().robot->getGlobalPose()
1136 <<
" to " << targets.size() <<
" alternatives";
1138 lastAllAlternativesImpossible = std::nullopt;
1141 for (
const auto& t : targets)
1143 GoalReachedMonitor monitor(
1144 t.target, srv.sceneProvider->scene(), config.goalReachedConfig);
1146 if (monitor.goalReached(
false))
1148 ARMARX_INFO <<
"Already at a possible goal position. Robot won't move.";
1152 core::Pose(srv.sceneProvider->scene().robot->getGlobalPose())});
1159 targetAlternatives = targets;
1162 if (setupTargetAlternatives())
1165 setupGlobalPlanSubvidision();
1167 startGlobalPathSegment(
false,
false);
1172 Navigator::setupTargetAlternatives()
1176 globalPlan = std::nullopt;
1182 const auto spfaPlanner =
1183 std::dynamic_pointer_cast<global_planning::SPFA>(config.stack.globalPlanner);
1184 if (spfaPlanner !=
nullptr)
1186 const core::Pose start(srv.sceneProvider->scene().robot->getGlobalPose());
1188 const auto planningResult = spfaPlanner->executePlanner(start);
1190 for (
const auto& target : targetAlternatives)
1193 const auto path = spfaPlanner->calculatePath(planningResult,
target.target);
1194 if (path.has_value() and verifyGlobalPathPossible(path->trajectory))
1197 srv.introspector->onGoal(
target.target);
1198 goalReachedMonitor = std::nullopt;
1199 goalReachedMonitor = GoalReachedMonitor(
1200 target.target, srv.sceneProvider->scene(), config.goalReachedConfig);
1203 <<
" with priority " <<
target.priority;
1209 <<
" not reachable";
1215 const auto goal = targetAlternatives.front().target;
1217 <<
"Current global planner is not an SPFA planner, discarding alternatives!";
1219 globalPlan = config.stack.globalPlanner->plan(goal);
1220 srv.introspector->onGoal(goal);
1221 goalReachedMonitor = std::nullopt;
1222 goalReachedMonitor =
1223 GoalReachedMonitor(goal, srv.sceneProvider->scene(), config.goalReachedConfig);
1226 targetAlternatives.clear();
1229 if (srv.drawer !=
nullptr)
1231 srv.drawer->callGenericDrawFunction(
1233 { config.stack.globalPlanner->visualizeDebugInfo(
client); });
1237 ARMARX_WARNING <<
"No drawer available. Cannot visualize global planner debug info.";
1243 if (not globalPlan.has_value())
1249 srv.introspector->failure();
1257 Navigator::setupGlobalPlanSubvidision()
1261 const auto& globalPlanPoses = globalPlan->plan.trajectory.poses();
1264 if (not globalPlan->plan.trajectory.points().empty())
1266 ARMARX_INFO <<
"Trajectory final pose " << globalPlanPoses.back().matrix();
1268 goalReachedMonitor->updateGoal(globalPlanPoses.back());
1273 reportPathGeometry(globalPlan->plan.trajectory);
1278 if ((not config.general.subdivision.enable) or (not hasLocalPlanner()) or
1279 globalPlan->plan.trajectory.points().empty())
1289 ARMARX_INFO <<
"Starting subdividing global path.";
1291 ARMARX_CHECK(srv.sceneProvider->scene().staticScene.has_value());
1293 srv.sceneProvider->scene().staticScene->distanceToObstaclesCostmap.has_value());
1294 const auto& costmap = srv.sceneProvider->scene().staticScene->distanceToObstaclesCostmap;
1296 std::vector<bool> localPlanEligibility;
1297 localPlanEligibility.reserve(globalPlan->plan.trajectory.points().size());
1298 for (
const auto& pose : globalPlanPoses)
1300 Eigen::Vector2f pt =
conv::to2D(pose.translation());
1301 localPlanEligibility.push_back(costmap->value(pt).value_or(0) >
1302 config.general.subdivision.localPlannerCostmapThreshold);
1306 bool falseSequence =
false;
1307 const int n = localPlanEligibility.size();
1308 const float expansion = config.general.subdivision.globalPlanExpansionDistance;
1311 for (
int i = 0; i <
n;)
1315 if (localPlanEligibility[i])
1318 falseSequence =
false;
1321 float totalDistance = 0;
1324 for (; (totalDistance < expansion) and (i < n); i++)
1326 localPlanEligibility[i] =
false;
1327 totalDistance += (globalPlanPoses[i - 1].translation() -
1328 globalPlanPoses[i].translation())
1338 if (not localPlanEligibility[i])
1341 falseSequence =
true;
1344 float totalDistance = 0;
1345 for (
int j = i - 1; (totalDistance < expansion) and (j >= 0); j--)
1347 localPlanEligibility[j] =
false;
1348 totalDistance += (globalPlanPoses[j].translation() -
1349 globalPlanPoses[j + 1].translation())
1365 GlobalPathSubdivision::Subdivision segment;
1367 segment.useLocalPlanner = localPlanEligibility[0];
1370 const auto checkSegmentLength = [&]()
1373 if (not segment.useLocalPlanner)
1378 const auto& subTrajectory =
1379 globalPlan->plan.trajectory.getSubTrajectory(segment.s, segment.t);
1380 const float segmentLength = subTrajectory.length();
1381 if (segmentLength >= config.general.subdivision.minSegmentDistance)
1386 if (globalPlan->subdivision.empty() and
static_cast<int>(segment.t) >= n)
1389 ARMARX_VERBOSE <<
"Segment " << globalPlan->subdivision.size() <<
": [" << segment.s
1390 <<
", " << segment.t
1391 <<
"); is the only segment. length=" << segmentLength;
1395 ARMARX_VERBOSE <<
"Segment " << globalPlan->subdivision.size() <<
": [" << segment.s
1396 <<
", " << segment.t
1397 <<
"); was too short for local planner: " << segmentLength;
1399 if (globalPlan->subdivision.empty())
1402 segment.useLocalPlanner =
false;
1404 <<
"First segment, convert to global planner segment and grow it further.";
1408 if (
static_cast<int>(segment.t) < n)
1411 segment = globalPlan->subdivision.back();
1412 globalPlan->subdivision.pop_back();
1413 ARMARX_VERBOSE <<
"Removing preceding segment and grow it further.";
1418 globalPlan->subdivision.back().t = segment.t;
1419 ARMARX_VERBOSE <<
"Last segment; extent previous segment to the end.";
1425 for (
int i = 0; i <
n; i++)
1428 while (i < n and localPlanEligibility[i] == segment.useLocalPlanner)
1434 if (checkSegmentLength())
1436 globalPlan->subdivision.emplace_back(segment);
1437 ARMARX_VERBOSE <<
"Segment " << globalPlan->subdivision.size() <<
": [" << segment.s
1438 <<
", " << segment.t
1439 <<
"); localPlanner=" << segment.useLocalPlanner;
1443 segment.useLocalPlanner = localPlanEligibility[i];
1450 std::size_t lastSegmentEnd = 0;
1451 for (
const auto& segment : globalPlan->subdivision)
1454 const std::ptrdiff_t segmentLength = segment.t - segment.s;
1456 lastSegmentEnd = segment.t;
1460 globalPlan->currentSegment = 0;
1461 srv.introspector->onGlobalPlannerSubdivision(globalPlan.value());
1463 ARMARX_INFO <<
"Divided global path into " << globalPlan->subdivision.size() <<
" segments";
1479 constexpr std::size_t minimumWaypointsForToppra = 4;
1482 trajectory.points().size() < minimumWaypointsForToppra)
1485 rampingFallback->apply(trajectory, startVelocity);
1491 parametrization->apply(trajectory, startVelocity);
1493 catch (
const std::exception& e)
1497 ARMARX_WARNING <<
"Trajectory parametrization failed (" << e.what()
1498 <<
"). Falling back to ramping for this request.";
1501 trajectory = planned;
1502 rampingFallback->apply(trajectory, startVelocity);
1510 ARMARX_INFO <<
"Parametrized " << trajectory.points().size() <<
" waypoints with "
1513 <<
" s (the planner's obstacle-aware velocities alone would take "
1517 if (config.general.parametrizationDump)
1520 {.requested = config.stack.generalConfig.parametrization,
1523 .parametrized = trajectory,
1525 .driveParams = driveParams,
1526 .maxVelocity = config.stack.generalConfig.maxVel.linear,
1527 .maxAngularVelocity = config.stack.generalConfig.maxVel.angular},
1528 config.general.parametrizationDumpPath);
1533 Navigator::resumeAfterEmergencyStop()
1535 if (not globalPlan.has_value() or isPaused() or isStopped())
1540 if (globalPlan->useLocalPlanner(hasLocalPlanner()))
1548 if (trajectory.points().empty())
1554 core::Pose(srv.sceneProvider->scene().robot->getGlobalPose()).translation();
1558 auto [remaining, endsAtGoal] = trajectory.getSubTrajectory(
1559 robotPosition, std::numeric_limits<float>::infinity());
1561 if (remaining.points().size() < 2)
1563 ARMARX_WARNING <<
"Cannot re-anchor the trajectory after the emergency stop: only "
1564 << remaining.points().size()
1565 <<
" waypoints remain. Resuming on the trajectory as planned.";
1569 ARMARX_IMPORTANT <<
"Re-anchoring the trajectory at the robot's current pose after the "
1571 << remaining.points().size() <<
" of " << trajectory.points().size()
1572 <<
" waypoints remain.";
1574 applyParametrization(remaining, config.stack.generalConfig.boundaryVelocity);
1576 updateExecutor(remaining);
1580 Navigator::startGlobalPathSegment(
bool incrementSegment,
bool rampFromCurrentVelocity)
1582 if (not globalPlan->subdivision.empty())
1586 if (incrementSegment)
1588 globalPlan->currentSegment++;
1591 if (globalPlan->currentSegment >= globalPlan->subdivision.size())
1597 ARMARX_INFO <<
"Starting segment " << globalPlan->currentSegment;
1598 const auto& currentSegment = globalPlan->subdivision[globalPlan->currentSegment];
1600 globalPlan->currentSegmentTrajectory =
1601 globalPlan->plan.trajectory.getSubTrajectory(currentSegment.s, currentSegment.t);
1603 applyParametrization(globalPlan->currentGlobalSegment(),
1604 rampFromCurrentVelocity
1605 ? srv.sceneProvider->scene().platformVelocity.linear.norm()
1606 : config.stack.generalConfig.boundaryVelocity);
1608 goalReachedMonitor->updateGoal(
1609 globalPlan->currentGlobalSegment().points().back().waypoint.pose);
1613 if (incrementSegment)
1618 applyParametrization(globalPlan->currentGlobalSegment(),
1619 rampFromCurrentVelocity
1620 ? srv.sceneProvider->scene().platformVelocity.linear.norm()
1621 : config.stack.generalConfig.boundaryVelocity);
1623 goalReachedMonitor->updateGoal(
1624 globalPlan->currentGlobalSegment().points().back().waypoint.pose);
1627 srv.introspector->onGlobalPlannerResult(globalPlan->plan);
1630 if (globalPlan->useLocalPlanner(hasLocalPlanner()))
1632 const auto localPlannerResult = updateLocalPlanner();
1633 updateExecutor(localPlannerResult);
1634 updateIntrospector(localPlannerResult);
1638 updateExecutor(globalPlan->currentGlobalSegment());
1641 ARMARX_INFO <<
"Start executing global plan segment (local planner="
1642 << globalPlan->useLocalPlanner(hasLocalPlanner())
1643 <<
"). Will now start all required threads.";
1659 const std::optional<std::string>& providerName)
1663 const std::scoped_lock lock{stateMtx, srv.sceneProvider->sceneMutex()};
1668 const auto resolveLocation =
1670 const std::optional<std::string>& providerName) -> std::vector<core::Location>
1672 const auto locations = srv.sceneProvider->scene().staticScene->locations;
1675 for (
const auto&
location : locations)
1681 const auto matchingLocs =
1686 <<
QUOTED(providerName.value_or(
"~unset~")) <<
".";
1687 return matchingLocs;
1692 <<
"The given location does not match the format <location>(:<instance-id>)? '"
1695 std::string instanceID;
1696 if (
split.size() == 2)
1699 instanceID =
split.back();
1702 const auto matchingLocations = resolveLocation(
split.front(), providerName);
1705 if (matchingLocations.empty())
1708 <<
QUOTED(providerName.value_or(
"~unset~"));
1711 if (matchingLocations.size() > 1)
1714 <<
" from provider " <<
QUOTED(providerName.value_or(
"~unset~"));
1715 for (
const auto&
location : matchingLocations)
1723 srv.sceneProvider->scene().staticScene->objectInfo,
1724 matchingLocations.front().framedPose,
1727 if (goal.pose.has_value())
1729 moveToAbsolute({goal.pose.value()},
false);
1753 const std::unique_lock lock{stateMtx, std::try_to_lock};
1754 if (not lock.owns_lock())
1757 <<
"Navigation state is busy with a request. Skipping this iteration.";
1764 const std::unique_lock sceneLock{srv.sceneProvider->sceneMutex(), std::try_to_lock};
1765 if (not sceneLock.owns_lock())
1768 <<
"Scene is busy with another navigator. Skipping this iteration.";
1775 <<
"Called Navigator::run() although shouldRun is false. Directly returning.";
1788 [&]() { updateScene(
true ); });
1793 srv.debugObserverHelper->setDebugObserverDatafield(
"scene update [ms]",
1794 duration.toMilliSecondsDouble());
1801 if (globalPlan.has_value() and not targetAlternatives.empty())
1803 checkGlobalPathAlternatives();
1808 if ((srv.introspector !=
nullptr) and (srv.sceneProvider !=
nullptr) and
1809 srv.sceneProvider->scene().robot)
1812 srv.introspector->onRobotPose(
1813 core::Pose(srv.sceneProvider->scene().robot->getGlobalPose()));
1882 if (srv.executor !=
nullptr and srv.executor->consumeEmergencyStopRelease())
1884 resumeAfterEmergencyStop();
1894 if (not globalPlan.has_value())
1901 if (globalPlan->useLocalPlanner(hasLocalPlanner()))
1903 const auto localPlannerResult = updateLocalPlanner();
1904 updateExecutor(localPlannerResult);
1905 updateIntrospector(localPlannerResult);
1907 if (srv.executor !=
nullptr && localPlannerResult.has_value() &&
1908 not isPaused() && not isStopped())
1910 srv.executor->ensureIsActive(
1911 ExecutorInterface::ControllerType::LocalTrajectory);
1914 else if (srv.executor !=
nullptr && not isPaused() && not isStopped())
1916 srv.executor->ensureIsActive(
1917 ExecutorInterface::ControllerType::GlobalTrajectory);
1923 srv.debugObserverHelper->setDebugObserverDatafield(
"local planner update [ms]",
1924 duration.toMilliSecondsDouble());
1933 if (hasSafetyGuard() and globalPlan.has_value())
1936 const auto result = updateSafetyGuard();
1937 srv.debugObserverHelper->setDebugObserverDatafield(
"safety_guard.limit_linear",
1938 result.twistLimits.linear);
1939 srv.debugObserverHelper->setDebugObserverDatafield(
"safety_guard.limit_angular",
1940 result.twistLimits.angular);
1942 if (srv.executor !=
nullptr)
1944 srv.executor->updateVelocityLimits(result.twistLimits);
1949 checkRobotBlocked(result);
1954 if (srv.executor !=
nullptr)
1956 srv.executor->updateVelocityLimits(config.stack.generalConfig.maxVel);
1964 srv.debugObserverHelper->setDebugObserverDatafield(
"velocity_factor",
1965 velocityFactor.load());
1968 if (srv.executor !=
nullptr)
1970 srv.executor->updateVelocityFactor(velocityFactor);
1985 srv.debugObserverHelper->setDebugObserverDatafield(
"monitor update [ms]",
1986 duration.toMilliSecondsDouble());
1991 Navigator::updateSafetyGuard()
1995 const core::Pose global_T_robot(srv.sceneProvider->scene().robot->getGlobalPose());
1996 const auto proj = globalPlan->currentGlobalSegment().getProjection(
1998 const Eigen::Vector3f global_V_movement = proj.wayPointAfter.waypoint.pose.translation() -
1999 proj.projection.waypoint.pose.translation();
2001 return config.stack.safetyGuard->computeSafetyLimits(global_V_movement.head<2>());
2007 const auto& cfg = config.general.blockedReplanning;
2009 if (not cfg.enabled)
2015 if (isPaused() or isStopped() or not globalPlan.has_value() or
2016 not goalReachedMonitor.has_value())
2018 blockedSince = std::nullopt;
2024 const bool blocked = (result.twistLimits.linear < cfg.linearLimit) or
2025 (result.twistLimits.angular < cfg.angularLimit);
2030 blockedSince = std::nullopt;
2035 if (not blockedSince.has_value())
2042 const armarx::Duration blockedFor = now - blockedSince.value();
2043 srv.debugObserverHelper->setDebugObserverDatafield(
"safety_guard.blocked_for [ms]",
2053 <<
"ms (safety guard limits near zero). Re-running the global planner.";
2056 blockedSince = std::nullopt;
2059 updateAbsolute({goalReachedMonitor->goal()});
2063 Navigator::hasSafetyGuard()
const
2065 return config.stack.safetyGuard !=
nullptr;
2069 Navigator::hasLocalPlanner() const noexcept
2071 return config.stack.localPlanner !=
nullptr;
2075 Navigator::updateScene(
const bool fullUpdate)
2082 Navigator::checkGlobalPathAlternatives()
2086 const auto replanAlternatives = [&]()
2088 lastAllAlternativesImpossible = std::nullopt;
2093 const auto previousGlobalPlan = globalPlan;
2095 if (setupTargetAlternatives())
2099 setupGlobalPlanSubvidision();
2103 ARMARX_INFO <<
"No possible alternatives, restoring previous global path.";
2104 globalPlan = std::move(previousGlobalPlan);
2109 startGlobalPathSegment(
false,
true);
2112 if (not verifyGlobalPathPossible(globalPlan->currentGlobalSegment()))
2116 if (lastAllAlternativesImpossible.has_value() and
2119 config.general.targetAlternativesFilterTimeSeconds)))
2122 <<
"Current globalPlan invalid, waiting for timeout before replanning.";
2126 ARMARX_INFO <<
"Global path no longer possible, trying other alternatives!";
2128 replanAlternatives();
2131 else if (lastAllAlternativesImpossible.has_value())
2136 <<
"Previously invalid global plan valid again -> replanning all alternatives";
2138 replanAlternatives();
2145 const auto& costmap = srv.sceneProvider->scene().staticScene->distanceToObstaclesCostmap;
2148 for (
const auto& pt : plan.points())
2150 const auto& pose = pt.waypoint.pose;
2151 const Eigen::Vector2f position =
conv::to2D(pose.translation());
2152 const auto vertex = costmap->toVertex(position);
2153 if (not costmap->isValid(vertex.index) or costmap->isInCollision(vertex.position))
2162 std::optional<local_planning::LocalPlannerResult>
2163 Navigator::updateLocalPlanner()
2166 const std::scoped_lock<std::mutex> lock{updateLocalPlannerMtx};
2174 const auto& globalTrajectory = globalPlan->currentGlobalSegment();
2175 ARMARX_VERBOSE << globalTrajectory.points().size() <<
" points in global plan";
2176 localPlan = config.stack.localPlanner->plan(globalTrajectory);
2178 if (localPlan.has_value())
2193 return std::nullopt;
2199 return std::nullopt;
2203 Navigator::updateExecutor(
const std::optional<local_planning::LocalPlannerResult>& localPlan)
2205 if (srv.executor ==
nullptr)
2211 if (isPaused() or isStopped())
2218 if (not localPlan.has_value())
2223 srv.executor->stop();
2244 srv.executor->execute(localPlan->trajectory,
true);
2250 if (srv.executor ==
nullptr)
2256 << globalTrajectory.points().size() <<
" points.";
2266 if (globalTrajectory.points().empty())
2270 srv.executor->stop();
2274 ARMARX_IMPORTANT <<
"Executing global plan with " << globalTrajectory.points().size()
2277 srv.executor->execute(globalTrajectory,
false);
2281 Navigator::updateIntrospector(
2282 const std::optional<local_planning::LocalPlannerResult>& localPlan)
2286 srv.introspector->onLocalPlannerResult(localPlan);
2290 Navigator::updateMonitor()
2294 if (not goalReachedMonitor.has_value())
2302 const auto status = goalReachedMonitor->status();
2303 armarx::DebugObserverHelper* debugObserver = srv.debugObserverHelper;
2305 if (debugObserver !=
nullptr)
2310 goalReachedMonitor->getConfig().posTh);
2312 goalReachedMonitor->getConfig().oriTh);
2315 if (not isPaused() and goalReachedMonitor->goalReached())
2318 ARMARX_INFO <<
"Current global path segment finished!";
2321 if (startGlobalPathSegment(
true,
true))
2324 ARMARX_INFO <<
"Goal " << goalReachedMonitor->goal().translation().head<2>()
2331 if (srv.executor !=
nullptr)
2333 srv.executor->dumpDiagnostics();
2340 {
core::Pose(srv.sceneProvider->scene().robot->getGlobalPose())}});
2342 srv.introspector->success();
2348 Navigator::stopAllThreads()
2350 const std::scoped_lock<std::mutex> lock{runningTaskMtx};
2354 runningTask->stop();
2404 <<
"Scaling factor for velocity may not be negative, but is " << velocityFactor;
2406 <<
"Scaling factor for velocity may not be > 1, but is " << velocityFactor;
2408 this->velocityFactor = velocityFactor;
2416 executorEnabled.store(
false);
2418 if (srv.executor !=
nullptr)
2421 srv.executor->stop();
2430 executorEnabled.store(
true);
2432 if (srv.executor !=
nullptr)
2434 if (hasLocalPlanner())
2457 const std::scoped_lock lock{stateMtx};
2458 goalReachedMonitor.reset();
2464 return not executorEnabled.load();
2470 return (not shouldRun) or (not runningTask) or (not runningTask->isRunning());
2474 config{std::move(other.config)},
2475 srv{std::move(other.srv)},
2476 driveParams{std::move(other.driveParams)},
2477 parametrizationMode{other.parametrizationMode},
2478 parametrization{std::move(other.parametrization)},
2479 rampingFallback{std::move(other.rampingFallback)},
2480 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