Navigator.cpp
Go to the documentation of this file.
1#include "Navigator.h"
2
4
6
7#include <algorithm>
8#include <cmath>
9#include <cstddef>
10#include <iterator>
11#include <limits>
12#include <memory>
13#include <mutex>
14#include <optional>
15#include <string>
16#include <utility>
17#include <vector>
18
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>
24
25#include <Eigen/Geometry>
26
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>
31
32#include <SimoxUtility/algorithm/string/string_tools.h>
33#include <VirtualRobot/Robot.h> // IWYU pragma: keep
34
45
66
67#include <SemanticObjectRelations/Shapes/Shape.h>
68
70{
71
77
78 bool
80 {
81 if (subdivision.empty())
82 {
83 // no subdivision
84 return hasLocalPlanner;
85 }
86
87 ARMARX_CHECK(hasLocalPlanner); // we always need local planner for segment subdivision
88 const bool segmentLocalPlanner = subdivision[currentSegment].useLocalPlanner;
89
90 return segmentLocalPlanner;
91 }
92
95 {
96 if (subdivision.empty())
97 {
98 // no subdivision
99 return plan.trajectory;
100 }
102 }
103
106 {
107 if (subdivision.empty())
108 {
109 // no subdivision
110 return plan.trajectory;
111 }
113 }
114
115 namespace
116 {
117 /// Build the configured parametrization, falling back to ramping rather than failing.
118 ///
119 /// The fallback lives here and deliberately *not* in
120 /// `TrajectoryParametrizationFactory`: the analysis application calls the same factory
121 /// and then reads the time parametrization back out of the `Toppra` instance. A silent
122 /// substitution inside the factory would make it write a result labelled `toppra` that
123 /// actually contains a ramping profile -- wrong data in the tool used to validate this.
124 /// A navigation component must not crash; an offline analysis tool must not lie.
125 ///
126 /// Three things can go wrong, and all three land here:
127 /// 1. TOPP-RA was not compiled in (no python environment at configure time).
128 /// 2. No platform dynamics config, or it does not parse / the robot model fails.
129 /// 3. Anything the `Toppra` constructor throws while loading the model.
130 /// Load the drive parameters, or nothing if they are unavailable or unusable.
131 std::optional<algorithms::Toppra::DriveParams>
132 loadDriveParams(const core::GeneralConfig& generalConfig,
133 const Navigator::Config::General& general)
134 {
136 {
137 return std::nullopt;
138 }
139
140 if (not general.platformDynamicsEnabled)
141 {
142 return std::nullopt;
143 }
144
145 try
146 {
149 }
150 catch (const std::exception& e)
151 {
152 ARMARX_WARNING << "Cannot read the platform dynamics config for platform "
153 << QUOTED(general.platform) << ": " << e.what();
154 return std::nullopt;
155 }
156 }
157
159 createParametrization(const core::GeneralConfig& generalConfig,
160 const Navigator::Config::General& general,
161 const std::optional<algorithms::Toppra::DriveParams>& driveParams,
163 {
164 *applied = generalConfig.parametrization;
165
166 const auto rampingInstead = [&](const std::string& reason)
167 {
168 ARMARX_WARNING << "TOPP-RA trajectory parametrization was requested but " << reason
169 << ". Falling back to ramping.";
170
171 core::GeneralConfig ramping = generalConfig;
172 ramping.parametrization = core::TrajectoryParametrization::Ramping;
174
175 return fac::TrajectoryParametrizationFactory::create(ramping);
176 };
177
178 if (generalConfig.parametrization != core::TrajectoryParametrization::Toppra)
179 {
180 return fac::TrajectoryParametrizationFactory::create(generalConfig);
181 }
182
184 {
185 return rampingInstead("it is not available in this build: " +
187 }
188
189 if (not general.platformDynamicsEnabled)
190 {
191 return rampingInstead("its drive parameters are disabled by "
192 "`p.navigator.general.platformDynamicsEnabled`");
193 }
194
195 if (not driveParams.has_value())
196 {
197 return rampingInstead(
198 "its drive parameters could not be read for platform '" + general.platform +
199 "'. Check `p.navigator.general.platform` against the files in "
200 "data/armarx_navigation/config/platform/");
201 }
202
203 try
204 {
205 return fac::TrajectoryParametrizationFactory::create(generalConfig, *driveParams);
206 }
207 catch (const std::exception& e)
208 {
209 return rampingInstead(std::string{"it could not be initialised: "} + e.what());
210 }
211 }
212 } // namespace
213
214 Navigator::Navigator(const Config& config, const InjectedServices& services) :
215 config{config},
216 srv{services},
217 driveParams{loadDriveParams(config.stack.generalConfig, config.general)},
218 parametrization{createParametrization(config.stack.generalConfig,
219 config.general,
220 driveParams,
221 &parametrizationMode)},
222 rampingFallback{fac::TrajectoryParametrizationFactory::create(
223 [&]
224 {
225 core::GeneralConfig ramping = config.stack.generalConfig;
227 return ramping;
228 }())}
229 {
230 ARMARX_INFO << "Trajectory parametrization: " << core::ToString(parametrizationMode);
231
232 ARMARX_CHECK_NOT_NULL(srv.sceneProvider) << "The scene provider must be set!";
233 // ARMARX_CHECK_NOT_NULL(services.executor) << "The executor service must be set!";
234 ARMARX_CHECK_NOT_NULL(services.publisher) << "The publisher service must be set!";
235 }
236
238 {
239 ARMARX_INFO << "Navigator destructor";
240 stop();
241
242 stopAllThreads();
243 }
244
245 void
246 Navigator::setGraphEdgeCosts(core::Graph& graph) const
247 {
248 const auto cost = [](const core::Pose& p1, const core::Pose& p2)
249 {
250 const core::Pose diff = p1.inverse() * p2;
251
252 // FIXME consider rotation as well
253 return diff.translation().norm();
254 };
255
256 // graph -> trajectory (set costs)
257 for (auto edge : graph.edges())
258 {
259 const core::Pose start = resolveGraphVertex(edge.source());
260 const core::Pose goal = resolveGraphVertex(edge.target());
261
262 switch (edge.attrib().strategy)
263 {
265 {
266 ARMARX_VERBOSE << "Global planning from " << start.translation() << " to "
267 << goal.translation();
268 ARMARX_CHECK_NOT_NULL(config.stack.globalPlanner);
269
270 try
271 {
272 const auto globalPlan = config.stack.globalPlanner->plan(start, goal);
273 if (globalPlan.has_value())
274 {
275 ARMARX_CHECK(globalPlan->trajectory.isValid());
276
277 edge.attrib().trajectory = globalPlan->trajectory;
278 ARMARX_VERBOSE << "Free path length: "
279 << globalPlan->trajectory.length();
280 edge.attrib().cost() = globalPlan->trajectory.length();
281 }
282 else
283 {
284 ARMARX_VERBOSE << "Global planning failed for this edge.";
285 edge.attrib().cost() = std::numeric_limits<float>::max();
286 }
287 }
288 catch (...)
289 {
290 ARMARX_VERBOSE << "Global planning failed due to exception "
292 edge.attrib().cost() = std::numeric_limits<float>::max();
293 }
294
295
296 break;
297 }
299 {
300 edge.attrib().cost() = cost(start, goal);
301 break;
302 }
303 }
304
305 ARMARX_VERBOSE << "Edge cost: " << edge.attrib().cost();
306 }
307 }
308
309 void
310 Navigator::moveTo(const std::vector<core::Pose>& waypoints,
311 core::NavigationFrame navigationFrame)
312 {
313 ARMARX_INFO << "Received moveTo() request.";
314
315 std::vector<core::Pose> globalWaypoints;
316 switch (navigationFrame)
317 {
319 globalWaypoints = waypoints;
320 break;
322 globalWaypoints.reserve(waypoints.size());
323
324 ARMARX_CHECK(srv.sceneProvider->synchronize(Clock::Now(), true));
325
326 const core::Pose global_T_robot(srv.sceneProvider->scene().robot->getGlobalPose());
327 ARMARX_VERBOSE << "Initial robot pose: " << global_T_robot.matrix();
328
329 std::transform(std::begin(waypoints),
330 std::end(waypoints),
331 std::back_inserter(globalWaypoints),
332 [&](const core::Pose& p) { return global_T_robot * p; });
333 break;
334 }
335
337 moveToAbsolute(globalWaypoints);
338 }
339
340 void
341 Navigator::moveToAlternatives(const std::vector<core::TargetAlternative>& targets,
342 core::NavigationFrame navigationFrame)
343 {
344 ARMARX_INFO << "Received moveToAlternatives() request.";
345
346 std::vector<core::TargetAlternative> globalTargets;
347 switch (navigationFrame)
348 {
350 globalTargets = targets;
351 break;
353 globalTargets.reserve(targets.size());
354
355 ARMARX_CHECK(srv.sceneProvider->synchronize(Clock::Now(), true));
356
357 const core::Pose global_T_robot(srv.sceneProvider->scene().robot->getGlobalPose());
358 ARMARX_VERBOSE << "Initial robot pose: " << global_T_robot.matrix();
359
360 std::transform(std::begin(targets),
361 std::end(targets),
362 std::back_inserter(globalTargets),
363 [&](const core::TargetAlternative& p) {
365 .target = global_T_robot * p.target, .priority = p.priority};
366 });
367 break;
368 }
369
371 moveToAbsoluteAlternatives(globalTargets);
372 }
373
374 void
375 Navigator::update(const std::vector<core::Pose>& waypoints,
376 core::NavigationFrame navigationFrame)
377 {
378 ARMARX_INFO << "Received update() request.";
379
380 std::vector<core::Pose> globalWaypoints;
381 switch (navigationFrame)
382 {
384 globalWaypoints = waypoints;
385 break;
387 globalWaypoints.reserve(waypoints.size());
388 std::transform(
389 std::begin(waypoints),
390 std::end(waypoints),
391 std::back_inserter(globalWaypoints),
392 [&](const core::Pose& p)
393 { return core::Pose(srv.sceneProvider->scene().robot->getGlobalPose()) * p; });
394 break;
395 }
396
398 updateAbsolute(globalWaypoints);
399 }
400
401 using GraphPath = std::vector<semrel::ShapeID>;
402
405 const core::Graph::ConstVertex& startVertex,
406 const core::Graph::ConstVertex& goalVertex)
407 {
408 ARMARX_VERBOSE << "Graph consists of " << graph.numVertices() << " vertices and "
409 << graph.numEdges() << " edges.";
410
411 std::vector<core::Graph::VertexDescriptor> predecessors(graph.numVertices());
412 std::vector<int> d(num_vertices(graph));
413
414 // FIXME ARMARX_CHECK(graphBuilder.startVertex.has_value());
415
416 auto weightMap = boost::get(&core::EdgeAttribs::m_value, graph);
417
418 core::Graph::VertexDescriptor start = startVertex.descriptor();
419
420 auto predecessorMap = boost::make_iterator_property_map(
421 predecessors.begin(), boost::get(boost::vertex_index, graph));
422
423 auto params = boost::predecessor_map(predecessorMap)
424 .distance_map(boost::make_iterator_property_map(
425 d.begin(), boost::get(boost::vertex_index, graph)))
426 .weight_map(weightMap);
427
431
432 ARMARX_VERBOSE << "Edge weights:";
433 for (const auto& edge : graph.edges())
434 {
435 ARMARX_VERBOSE << edge.sourceObjectID() << " -> " << edge.targetObjectID() << ": "
436 << edge.attrib().m_value;
437 }
438
439 std::vector<core::Graph::EdgeDescriptor> edgesToBeRemoved;
440 for (const auto edge : graph.edges())
441 {
442 if (edge.attrib().m_value == std::numeric_limits<float>::max())
443 {
444 edgesToBeRemoved.push_back(edge.descriptor());
445 }
446 }
447
448 for (const auto edge : edgesToBeRemoved)
449 {
450 boost::remove_edge(edge, graph);
451 }
452 ARMARX_VERBOSE << "Edge weights after removal of inf edges:";
453
454 for (const auto& edge : graph.edges())
455 {
456 ARMARX_VERBOSE << edge.sourceObjectID() << " -> " << edge.targetObjectID() << ": "
457 << edge.attrib().m_value;
458 ARMARX_VERBOSE << "Edge: " << edge << "cost: " << edge.attrib().cost()
459 << ", type: " << static_cast<int>(edge.attrib().strategy)
460 << " , has traj " << edge.attrib().trajectory.has_value();
461 }
462
463 ARMARX_VERBOSE << "Searching shortest path from vertex with id `"
464 << graph.vertex(start).objectID() << "` to vertex `"
465 << graph.vertex(goalVertex.descriptor()).objectID() << "`";
466
467 boost::dijkstra_shortest_paths(graph, start, params);
468
469 // find shortest path
471
472 // WARNING: shortest path will be from goal to start first
473 GraphPath shortestPath;
474
475 core::Graph::VertexDescriptor currentVertex = goalVertex.descriptor();
476 while (currentVertex != startVertex.descriptor())
477 {
478 shortestPath.push_back(graph.vertex(currentVertex).objectID());
479
481
482 auto parent = predecessorMap[currentVertex];
483 ARMARX_VERBOSE << "Parent id: " << parent;
484
486
487 // find edge between parent and currentVertex
488 auto outEdges = graph.vertex(parent).outEdges();
489 ARMARX_VERBOSE << "Parent has " << std::distance(outEdges.begin(), outEdges.end())
490 << " out edges";
491
492 ARMARX_CHECK_GREATER(std::distance(outEdges.begin(), outEdges.end()), 0)
493 << "Cannot reach another vertex from vertex `"
494 << graph.vertex(parent).objectID(); // not empty
495
496 auto edgeIt = std::find_if(outEdges.begin(),
497 outEdges.end(),
498 [&currentVertex](const auto& edge) -> bool
499 { return edge.target().descriptor() == currentVertex; });
500
501 ARMARX_CHECK(edgeIt != outEdges.end());
502 // shortestPath.edges.push_back(edgeIt->descriptor());
503
504 currentVertex = parent;
505 }
506
507 shortestPath.push_back(startVertex.objectID());
508
509 // ARMARX_CHECK_EQUAL(shortestPath.vertices.size() - 1, shortestPath.edges.size());
510
512 // reverse the range => now it is from start to goal again
513 shortestPath = shortestPath | ranges::views::reverse | ranges::to_vector;
514 // shortestPath.edges = shortestPath.edges | ranges::views::reverse | ranges::to_vector;
515
517 return shortestPath;
518 }
519
521 Navigator::convertToTrajectory(const GraphPath& shortestPath, const core::Graph& graph) const
522 {
524
525 ARMARX_CHECK_GREATER_EQUAL(shortestPath.size(), 2)
526 << "At least start and goal vertices must be available";
527
528 // ARMARX_CHECK_EQUAL(shortestPath.size() - 1, shortestPath.edges.size());
529
530 std::vector<core::GlobalTrajectoryPoint> trajectoryPoints;
531
532 // TODO add the start
533 // trajectoryPoints.push_back(core::TrajectoryPoint{
534 // .waypoint = {.pose = graph.vertex(shortestPath.front()).attrib().getPose()},
535 // .velocity = 0.F});
536
537 ARMARX_VERBOSE << "Shortest path with " << shortestPath.size() << " vertices";
539
540 for (size_t i = 0; i < shortestPath.size() - 1; i++)
541 {
542 // ARMARX_CHECK(graph.hasEdge(shortestPath.edges.at(i).m_source,
543 // shortestPath.edges.at(i).m_target));
544
545 // TODO add method edge(shapeId, shapeId)
546 const core::Graph::ConstEdge edge =
547 graph.edge(graph.vertex(shortestPath.at(i)), graph.vertex(shortestPath.at(i + 1)));
548
550 ARMARX_VERBOSE << "Index " << i;
551 ARMARX_VERBOSE << static_cast<int>(edge.attrib().strategy);
552 ARMARX_VERBOSE << edge;
553 switch (edge.attrib().strategy)
554 {
556 {
557 ARMARX_INFO << "Free navigation on edge";
558
560 ARMARX_CHECK(edge.attrib().trajectory.has_value());
561
563 ARMARX_INFO << "Length: " << edge.attrib().trajectory->length();
564
565
567 ARMARX_INFO << "Free navigation with "
568 << edge.attrib().trajectory->points().size() << " waypoints";
569
570 // we have a trajectory
571 // FIXME trajectory points can be invalid => more points than expected. Why?
573 const std::vector<core::GlobalTrajectoryPoint> edgeTrajectoryPoints =
574 edge.attrib().trajectory->points();
575
576 // if trajectory is being initialized, include the start, otherwise skip it
577 const int offset = trajectoryPoints.empty() ? 0 : 1;
578
579 if (edgeTrajectoryPoints.size() > 2) // not only start and goal position
580 {
582 // append `edge trajectory` to `trajectory` (without start and goal points)
583 trajectoryPoints.insert(trajectoryPoints.end(),
584 edgeTrajectoryPoints.begin() + offset,
585 edgeTrajectoryPoints.end());
586 }
587
588
590 // clang-format off
591 // trajectoryPoints.push_back(
592 // core::TrajectoryPoint
593 // {
594 // .waypoint =
595 // {
596 // .pose = graph.vertex(shortestPath.at(i+1)).attrib().getPose()
597 // },
598 // .velocity = std::numeric_limits<float>::max()
599 // });
600 // clang-format on
601
602 break;
603 }
604
606 {
607 ARMARX_INFO << "Point2Point navigation on edge";
608
609 // FIXME variable
610 const float point2pointVelocity = 400;
611
612 const core::GlobalTrajectoryPoint currentTrajPt = {
613 .waypoint = {.pose = resolveGraphVertex(graph.vertex(shortestPath.at(i)))},
614 .velocity = point2pointVelocity};
615
616 const core::GlobalTrajectoryPoint nextTrajPt{
617 .waypoint = {.pose =
618 resolveGraphVertex(graph.vertex(shortestPath.at(i + 1)))},
619 .velocity = 0};
620
621 // resample event straight lines
622 // ARMARX_CHECK_NOT_EMPTY(trajectoryPoints);
623
624 // const core::Trajectory segmentTraj({trajectoryPoints.back(), nextTrajPt});
625 // ARMARX_INFO << "Segment trajectory length: " << segmentTraj.length();
626
627 // const auto resampledTrajectory = segmentTraj.resample(500); // FIXME param
628
629 // ARMARX_INFO << "Resampled trajectory contains "
630 // << resampledTrajectory.points().size() << " points";
631
632 // this is the same pose as the goal of the previous segment but with a different velocity
633 // FIXME MUST set velocity here.
634 // trajectoryPoints.push_back(
635 // core::TrajectoryPoint{.waypoint = trajectoryPoints.back().waypoint,
636 // .velocity = std::numeric_limits<float>::max()});
637
639 // trajectoryPoints.insert(trajectoryPoints.end(),
640 // resampledTrajectory.points().begin(),
641 // resampledTrajectory.points().end());
642 trajectoryPoints.push_back(currentTrajPt);
643 trajectoryPoints.push_back(nextTrajPt);
644
645 break;
646 }
647 default:
648 {
649 ARMARX_ERROR << "Boom.";
650 }
651 }
652 }
653
654 ARMARX_INFO << "Trajectory consists of " << trajectoryPoints.size() << " points";
655
656 for (const auto& pt : trajectoryPoints)
657 {
658 ARMARX_INFO << pt.waypoint.pose.translation();
659 }
660
661 return {trajectoryPoints};
662 }
663
664 GraphBuilder
665 Navigator::convertToGraph(const std::vector<client::WaypointTarget>& targets) const
666 {
667 //
668 GraphBuilder graphBuilder;
669 graphBuilder.initialize(core::Pose(srv.sceneProvider->scene().robot->getGlobalPose()));
670
671 // std::optional<Graph*> activeSubgraph;
672 for (const auto& target : targets)
673 {
674 ARMARX_INFO << "Adding target " << QUOTED(target) << " to graph";
675 // if the last location was on a subgraph, that we are about to leave
676 // const bool leavingSubgraph = [&]() -> bool
677 // {
678 // if (not activeSubgraph.has_value())
679 // {
680 // return false;
681 // }
682
683 // // if a user specifies a pose, then this pose is not meant to be part of a graph
684 // // -> can be reached directly
685 // if (target.pose.has_value())
686 // {
687 // return true;
688 // }
689
690 // if (not target.locationId->empty())
691 // {
692 // const auto& subgraph = core::getSubgraph(target.locationId.value(),srv.sceneProvider->scene().graph->subgraphs);
693 // return subgraph.name() != activeSubgraph;
694 // }
695
696 // throw LocalException("this line should not be reachable");
697 // }();
698
699 // if(leavingSubgraph)
700 // {
701 // const auto activeNodes = graph.activeVertices();
702 // ARMARX_CHECK(activeNodes.size() == 1);
703 // graph.connect(activeSubgraph->getRoutesFrom(activeNodes.front()));
704 // }
705
706
707 // if a user specifies a pose, then this pose is not meant to be part of a graph
708 // -> can be reached directly
709 if (target.pose.has_value())
710 {
711 graphBuilder.connect(target.pose.value(), target.strategy);
712 continue;
713 }
714
715 // if a user specified a vertex of a subgraph (aka location), then this vertex
716 // might not be reachable directly. It might be needed to navigate on the graph
717 // instead. Thus, we collect all routes to reach the node.
718 if (not target.locationId->empty())
719 {
720 const auto& subgraph = core::getSubgraph(
721 target.locationId.value(), srv.sceneProvider->scene().graph->subgraphs);
722
723 const auto vertex = core::getVertexByName(target.locationId.value(), subgraph);
724
725 ARMARX_INFO << "Vertex " << QUOTED(target.locationId.value()) << " resolved to "
726 << vertex.attrib().getPose();
727
728 const std::vector<core::GraphPath> routes = core::findPathsTo(vertex, subgraph);
729 // const auto routes = subgraph->getRoutesTo(target.locationId);
730
731 ARMARX_INFO << "Found " << routes.size() << " routes to location `"
732 << target.locationId.value();
733
734 ARMARX_CHECK(not routes.empty()) << "The location `" << target.locationId.value()
735 << "` is not a reachable vertex on the graph!";
736
737 // we now add all routes to the graph that take us to the desired location
738 graphBuilder.connect(routes,
739 target.strategy); // TODO: all routes to the same target
740
741 continue;
742 }
743
744 ARMARX_ERROR << "Either `location_id` or `pose` has to be provided!";
745 }
746
747 const auto goalVertex = graphBuilder.getGraph().vertex(graphBuilder.goalVertex());
748 ARMARX_INFO << "Goal vertex is " << QUOTED(goalVertex.attrib().getLocationName());
749
750 return graphBuilder;
751 }
752
754 Navigator::resolveGraphVertex(const core::Graph::ConstVertex& vertex) const
755 {
756 const auto goal = core::resolveLocation(srv.sceneProvider->scene().staticScene->objectMap,
757 srv.sceneProvider->scene().staticScene->objectInfo,
758 vertex.attrib().getPose());
759 ARMARX_CHECK(goal.pose.has_value())
760 << "The location of vertex " << vertex.attrib().getLocationName()
761 << " couldn't be resolved (" << goal.errorMsg << ")";
762 return goal.pose.value();
763 }
764
765 void
766 Navigator::moveTo(const std::vector<client::WaypointTarget>& targets,
767 core::NavigationFrame navigationFrame)
768 {
769 // arlt: Is this function deprecated?
771 << "only absolute movement implemented atm.";
772
774
775 validate(targets);
776
777 ARMARX_CHECK_NOT_EMPTY(targets) << "At least the goal has to be provided!";
778 ARMARX_INFO << "Navigating to " << targets.back();
779
780 // update static scene including locations
781 updateScene(true);
782
783 auto graphBuilder = convertToGraph(targets);
784
786
787 core::Graph graph = graphBuilder.getGraph();
788 auto startVertex = graphBuilder.startVertex;
789 auto goalVertex = graphBuilder.getGraph().vertex(graphBuilder.goalVertex());
790
791 ARMARX_INFO << "Goal pose according to graph is " << graphBuilder.goalPose().matrix();
792
793 ARMARX_CHECK(startVertex.has_value());
794
796
797 goalReachedMonitor = std::nullopt;
798 goalReachedMonitor = GoalReachedMonitor(
799 graphBuilder.goalPose(), srv.sceneProvider->scene(), config.goalReachedConfig);
800
801 if (goalReachedMonitor->goalReached(false))
802 {
803 ARMARX_IMPORTANT << "Already at goal position "
804 << goalReachedMonitor->goal().translation().head<2>()
805 << ". Robot won't move.";
806
807 srv.publisher->goalReached(core::GoalReachedEvent{
809 core::Pose(srv.sceneProvider->scene().robot->getGlobalPose())});
810
811 return;
812 }
813
815 setGraphEdgeCosts(graph);
816
818
819 srv.introspector->onGlobalGraph(graph);
820
822 const auto shortestPath = findShortestPath(graph, startVertex.value(), goalVertex);
823
824 // print
826
827 std::vector<core::Pose> vertexPoses;
828 vertexPoses.emplace_back(srv.sceneProvider->scene().robot->getGlobalPose());
829
830 ARMARX_INFO << "Navigating along the following nodes:";
831 for (const semrel::ShapeID& vertex : shortestPath)
832 {
833 ARMARX_INFO << " - " << graph.vertex(vertex).attrib().getLocationName();
834 vertexPoses.push_back(resolveGraphVertex(graph.vertex(vertex)));
835 }
836
837 srv.introspector->onGlobalShortestPath(vertexPoses);
838
839
840 // convert graph / vertex chain to trajectory
842 core::GlobalTrajectory globalPlanTrajectory = convertToTrajectory(shortestPath, graph);
843
844 // globalPlanTrajectory.setMaxVelocity(1000); // FIXME
845
846 // move ...
847
848 // this is our `global plan`
850
851 globalPlan = global_planning::GlobalPlannerResult{.trajectory = globalPlanTrajectory,
852 .helperTrajectory = std::nullopt};
853
854 // the following is similar to moveToAbsolute
855 // TODO(fabian.reister): remove code duplication
856
857 srv.executor->execute(globalPlan->currentGlobalSegment());
858
860 srv.publisher->globalTrajectoryUpdated(core::GlobalTrajectoryUpdatedEvent{
861 {.timestamp = armarx::Clock::Now()}, globalPlan->currentGlobalSegment()});
862
864 srv.introspector->onGlobalPlannerResult(globalPlan->plan);
865
866 ARMARX_INFO << "Global planning completed. Will now start all required threads";
868
869 startStack();
870 }
871
872 void
873 Navigator::startStack()
874 {
875
877
878 ARMARX_INFO << "Starting stack.";
879
880 {
881 // ensure running task is never started and stopped simultaneously
882 const std::scoped_lock<std::mutex> lock{runningTaskMtx};
883
884 shouldRun = true;
885
886 // FIXME instead of PeriodicTask, use RunningTask.
887 if (not runningTask)
888 {
889 runningTask =
891 &Navigator::run,
892 config.general.tasks.replanningUpdatePeriod,
893 false,
894 "PeriodicTask");
895 runningTask->start();
896 }
897 else if (not runningTask->isRunning())
898 {
899 runningTask->start();
900 }
901 }
902
903 // FIXME create separate function for this.
904 if (globalPlan->useLocalPlanner(hasLocalPlanner()))
905 {
906 if (srv.executor != nullptr)
907 {
908 srv.executor->start(ExecutorInterface::ControllerType::LocalTrajectory);
909 }
910 }
911 else
912 {
913 if (srv.executor != nullptr)
914 {
915 srv.executor->start(ExecutorInterface::ControllerType::GlobalTrajectory);
916 }
917 }
918
919 // Could be required if pauseMovement() has been called in the past.
920 resume();
921 srv.publisher->movementStarted(core::MovementStartedEvent{
922 {.timestamp = armarx::Clock::Now()},
923 core::Pose(srv.sceneProvider->scene().robot->getGlobalPose())});
924 }
925
926 void
927 Navigator::moveToAbsolute(const std::vector<core::Pose>& waypoints, bool sceneUpdate)
928 {
930
931 // if this navigator is in use, stop the movement ...
932 pause();
933 // ... and all threads
934 stopAllThreads();
935
936 // FIXME remove
937 //std::this_thread::sleep_for(std::chrono::seconds(1));
938
940 ARMARX_CHECK_NOT_EMPTY(waypoints);
941
942 if (sceneUpdate)
943 {
944 updateScene(true);
945 }
946
947 ARMARX_INFO << "Request to move from " << srv.sceneProvider->scene().robot->getGlobalPose()
948 << " to " << waypoints.back().matrix();
949
950 // first we check if we are already at the goal position
951 goalReachedMonitor = std::nullopt;
952 goalReachedMonitor = GoalReachedMonitor(
953 waypoints.back(), srv.sceneProvider->scene(), config.goalReachedConfig);
954
955 if (goalReachedMonitor->goalReached(false))
956 {
957 ARMARX_INFO << "Already at goal position. Robot won't move.";
958 ARMARX_CHECK_NOT_NULL(srv.publisher);
959
960 ARMARX_VERBOSE << "Finalizing";
961 srv.publisher->goalReached(core::GoalReachedEvent{
963 core::Pose(srv.sceneProvider->scene().robot->getGlobalPose())});
964 ARMARX_VERBOSE << "Finalized";
965
966 return;
967 }
968
969 // global planner
970 ARMARX_INFO << "Planning global trajectory";
971 ARMARX_CHECK_NOT_NULL(config.stack.globalPlanner);
972 // TODO plan on multiple waypoints, ignoring waypoints for now
973 // idea: compute multiple global trajectories, one for each segment between waypoints.
974
975 srv.introspector->onGoal(waypoints.back());
976 globalPlan = config.stack.globalPlanner->plan(waypoints.back());
977
978 if (srv.drawer != nullptr)
979 {
980 srv.drawer->callGenericDrawFunction(
981 [&](viz::Client& client)
982 { config.stack.globalPlanner->visualizeDebugInfo(client); });
983 }
984 else
985 {
986 ARMARX_WARNING << "No drawer available. Cannot visualize global planner debug info.";
987 }
988
989
991
992 if (not globalPlan.has_value())
993 {
994 ARMARX_WARNING << "No global trajectory. Cannot move.";
995 srv.publisher->globalPlanningFailed(core::GlobalPlanningFailedEvent{
996 {.timestamp = armarx::Clock::Now()}, {"No global trajectory. Cannot move."}});
997
998 srv.introspector->failure();
999 return;
1000 }
1001
1002 // create subdivision and start first segment
1003 setupGlobalPlanSubvidision();
1004
1005 startGlobalPathSegment(false, false);
1006 }
1007
1008 void
1009 Navigator::updateAbsolute(const std::vector<core::Pose>& waypoints)
1010 {
1012 ARMARX_CHECK_NOT_EMPTY(waypoints);
1013
1014 // Assume nothing in static obstacles changed -> also don't update costmap
1015 updateScene(false);
1016
1017 // global planner
1018 ARMARX_INFO << "Planning global trajectory";
1019 ARMARX_CHECK_NOT_NULL(config.stack.globalPlanner);
1020 // TODO plan on multiple waypoints, ignoring waypoints for now
1021 // idea: compute multiple global trajectories, one for each segment between waypoints.
1022
1023 srv.introspector->onGoal(waypoints.back());
1024 globalPlan = config.stack.globalPlanner->plan(waypoints.back());
1025
1027
1028 if (not globalPlan.has_value())
1029 {
1030 ARMARX_WARNING << "No global trajectory. Cannot move.";
1031 srv.publisher->globalPlanningFailed(core::GlobalPlanningFailedEvent{
1032 {.timestamp = armarx::Clock::Now()}, {"No global trajectory. Cannot move."}});
1033
1034 srv.introspector->failure();
1035 return;
1036 }
1037
1038 // create subdivision and start first segment
1039 setupGlobalPlanSubvidision();
1040
1041 startGlobalPathSegment(false, true);
1042 }
1043
1044 void
1045 Navigator::moveToAbsoluteAlternatives(const std::vector<core::TargetAlternative>& targets)
1046 {
1048
1049 // if this navigator is in use, stop the movement ...
1050 pause();
1051 // ... and all threads
1052 stopAllThreads();
1053
1055 ARMARX_CHECK_NOT_EMPTY(targets);
1056
1057 updateScene(true);
1058
1059 ARMARX_INFO << "Request to move from " << srv.sceneProvider->scene().robot->getGlobalPose()
1060 << " to " << targets.size() << " alternatives";
1061
1062 lastAllAlternativesImpossible = std::nullopt;
1063
1064 // first, check whether we are already at any target
1065 for (const auto& t : targets)
1066 {
1067 GoalReachedMonitor monitor(
1068 t.target, srv.sceneProvider->scene(), config.goalReachedConfig);
1069
1070 if (monitor.goalReached(false))
1071 {
1072 ARMARX_INFO << "Already at a possible goal position. Robot won't move.";
1073
1074 srv.publisher->goalReached(core::GoalReachedEvent{
1076 core::Pose(srv.sceneProvider->scene().robot->getGlobalPose())});
1077
1078 return;
1079 }
1080 }
1081
1082 // sort alternatives by their priority (in descending order)
1083 targetAlternatives = targets;
1084 ranges::sort(targetAlternatives, std::greater{}, &core::TargetAlternative::priority);
1085
1086 if (setupTargetAlternatives())
1087 {
1088 // create subdivision and start first segment
1089 setupGlobalPlanSubvidision();
1090
1091 startGlobalPathSegment(false, false);
1092 }
1093 }
1094
1095 bool
1096 Navigator::setupTargetAlternatives()
1097 {
1098 ARMARX_CHECK_NOT_EMPTY(targetAlternatives);
1099
1100 globalPlan = std::nullopt;
1101
1102 // Spfa planner plans into the "whole room", i.e. by executing the algorithm with
1103 // a single start position, a path to any goal can be constructed.
1104 // Thus, we only use alternatives with the spfa planner to avoid repeatedly replanning the
1105 // global path.
1106 const auto spfaPlanner =
1107 std::dynamic_pointer_cast<global_planning::SPFA>(config.stack.globalPlanner);
1108 if (spfaPlanner != nullptr)
1109 {
1110 const core::Pose start(srv.sceneProvider->scene().robot->getGlobalPose());
1111 // expensive spfa planning only once for all alternatives
1112 const auto planningResult = spfaPlanner->executePlanner(start);
1113
1114 for (const auto& target : targetAlternatives)
1115 {
1116 // cheap path reconstruction for every alternative
1117 const auto path = spfaPlanner->calculatePath(planningResult, target.target);
1118 if (path.has_value() and verifyGlobalPathPossible(path->trajectory))
1119 {
1120 globalPlan = path;
1121 srv.introspector->onGoal(target.target);
1122 goalReachedMonitor = std::nullopt;
1123 goalReachedMonitor = GoalReachedMonitor(
1124 target.target, srv.sceneProvider->scene(), config.goalReachedConfig);
1125
1126 ARMARX_INFO << "Valid path found for target " << target.target.matrix()
1127 << " with priority " << target.priority;
1128 break;
1129 }
1130 else
1131 {
1132 ARMARX_VERBOSE << "Target with priority " << target.priority
1133 << " not reachable";
1134 }
1135 }
1136 }
1137 else
1138 {
1139 const auto goal = targetAlternatives.front().target;
1141 << "Current global planner is not an SPFA planner, discarding alternatives!";
1142
1143 globalPlan = config.stack.globalPlanner->plan(goal);
1144 srv.introspector->onGoal(goal);
1145 goalReachedMonitor = std::nullopt;
1146 goalReachedMonitor =
1147 GoalReachedMonitor(goal, srv.sceneProvider->scene(), config.goalReachedConfig);
1148
1149 // we only use target alternatives with the spfa planner
1150 targetAlternatives.clear();
1151 }
1152
1153 if (srv.drawer != nullptr)
1154 {
1155 srv.drawer->callGenericDrawFunction(
1156 [&](viz::Client& client)
1157 { config.stack.globalPlanner->visualizeDebugInfo(client); });
1158 }
1159 else
1160 {
1161 ARMARX_WARNING << "No drawer available. Cannot visualize global planner debug info.";
1162 }
1163
1164
1166
1167 if (not globalPlan.has_value())
1168 {
1169 ARMARX_WARNING << "No global trajectory. Cannot move.";
1170 srv.publisher->globalPlanningFailed(core::GlobalPlanningFailedEvent{
1171 {.timestamp = armarx::Clock::Now()}, {"No global trajectory. Cannot move."}});
1172
1173 srv.introspector->failure();
1174 return false;
1175 }
1176
1177 return true;
1178 }
1179
1180 void
1181 Navigator::setupGlobalPlanSubvidision()
1182 {
1183 ARMARX_CHECK(globalPlan.has_value());
1184 ARMARX_CHECK(globalPlan->subdivision.empty());
1185 const auto& globalPlanPoses = globalPlan->plan.trajectory.poses();
1186
1188 if (not globalPlan->plan.trajectory.points().empty())
1189 {
1190 ARMARX_INFO << "Trajectory final pose " << globalPlanPoses.back().matrix();
1191
1192 goalReachedMonitor->updateGoal(globalPlanPoses.back());
1193 }
1194 srv.publisher->globalTrajectoryUpdated(core::GlobalTrajectoryUpdatedEvent{
1195 {.timestamp = armarx::Clock::Now()}, globalPlan->plan.trajectory});
1196
1197
1198 ARMARX_INFO << "Global planning completed.";
1200 // only enable subdivision if local planner is actually enabled
1201 if ((not config.general.subdivision.enable) or (not hasLocalPlanner()) or
1202 globalPlan->plan.trajectory.points().empty())
1203 {
1204 // startGlobalPathSegment will hand trajectory over to introspection
1205 // after ramping has been performed; when subdivision is enabled
1206 // the introspection will receive the full subdivision (see below)
1207
1208 return;
1209 }
1210
1211 ARMARX_CHECK(hasLocalPlanner());
1212 ARMARX_INFO << "Starting subdividing global path.";
1213
1214 ARMARX_CHECK(srv.sceneProvider->scene().staticScene.has_value());
1216 srv.sceneProvider->scene().staticScene->distanceToObstaclesCostmap.has_value());
1217 const auto& costmap = srv.sceneProvider->scene().staticScene->distanceToObstaclesCostmap;
1218
1219 std::vector<bool> localPlanEligibility;
1220 localPlanEligibility.reserve(globalPlan->plan.trajectory.points().size());
1221 for (const auto& pose : globalPlanPoses)
1222 {
1223 Eigen::Vector2f pt = conv::to2D(pose.translation());
1224 localPlanEligibility.push_back(costmap->value(pt).value_or(0) >
1225 config.general.subdivision.localPlannerCostmapThreshold);
1226 }
1227
1228 // expand every sequence of false values in localPlanEligibility
1229 bool falseSequence = false;
1230 const int n = localPlanEligibility.size();
1231 const float expansion = config.general.subdivision.globalPlanExpansionDistance;
1232 if (expansion > 0)
1233 {
1234 for (int i = 0; i < n;)
1235 {
1236 if (falseSequence)
1237 {
1238 if (localPlanEligibility[i])
1239 {
1240 // false sequence ended, expand it appropriately to the back
1241 falseSequence = false;
1242
1243 // we expand until we have a distance >= expansion
1244 float totalDistance = 0;
1245 // false sequence ended -> there has to be an element before the current one
1247 for (; (totalDistance < expansion) and (i < n); i++)
1248 {
1249 localPlanEligibility[i] = false;
1250 totalDistance += (globalPlanPoses[i - 1].translation() -
1251 globalPlanPoses[i].translation())
1252 .norm();
1253 }
1254 continue; // we already incremented i
1255 }
1256
1257 // otherwise continue current false sequence
1258 }
1259 else
1260 {
1261 if (not localPlanEligibility[i])
1262 {
1263 // false sequence started, expand it appropriately to the front
1264 falseSequence = true;
1265
1266 // we expand until we have a distance >= expansion
1267 float totalDistance = 0;
1268 for (int j = i - 1; (totalDistance < expansion) and (j >= 0); j--)
1269 {
1270 localPlanEligibility[j] = false;
1271 totalDistance += (globalPlanPoses[j].translation() -
1272 globalPlanPoses[j + 1].translation())
1273 .norm();
1274 }
1275 }
1276
1277 // otherwise continue current true sequence
1278 }
1279
1280 i++;
1281 }
1282 }
1283
1285 // identify each consecutive true/false sequence and create subdivision
1286 // also check that each localPlanner segment is long enough
1287
1288 GlobalPathSubdivision::Subdivision segment;
1289 segment.s = 0;
1290 segment.useLocalPlanner = localPlanEligibility[0];
1291
1292 // returns true, iff the segment should be terminated and appended to the list of subdivisions
1293 const auto checkSegmentLength = [&]()
1294 {
1295 // only check the length if the current segment uses the local planner
1296 if (not segment.useLocalPlanner)
1297 {
1298 return true;
1299 }
1300
1301 const auto& subTrajectory =
1302 globalPlan->plan.trajectory.getSubTrajectory(segment.s, segment.t);
1303 const float segmentLength = subTrajectory.length();
1304 if (segmentLength >= config.general.subdivision.minSegmentDistance)
1305 {
1306 return true; // segment is long enough
1307 }
1308
1309 if (globalPlan->subdivision.empty() and static_cast<int>(segment.t) >= n)
1310 {
1311 // segment is too short but the only segment -> valid
1312 ARMARX_VERBOSE << "Segment " << globalPlan->subdivision.size() << ": [" << segment.s
1313 << ", " << segment.t
1314 << "); is the only segment. length=" << segmentLength;
1315 return true;
1316 }
1317
1318 ARMARX_VERBOSE << "Segment " << globalPlan->subdivision.size() << ": [" << segment.s
1319 << ", " << segment.t
1320 << "); was too short for local planner: " << segmentLength;
1321
1322 if (globalPlan->subdivision.empty())
1323 {
1324 // this is the first (but not the only) segment, join it to the next one
1325 segment.useLocalPlanner = false;
1327 << "First segment, convert to global planner segment and grow it further.";
1328 }
1329 else
1330 {
1331 if (static_cast<int>(segment.t) < n)
1332 {
1333 // there is a preceding segment, remove it and continue to grow it
1334 segment = globalPlan->subdivision.back();
1335 globalPlan->subdivision.pop_back();
1336 ARMARX_VERBOSE << "Removing preceding segment and grow it further.";
1337 }
1338 else
1339 {
1340 // this is the last segment, expand the preceding segment to include this one
1341 globalPlan->subdivision.back().t = segment.t;
1342 ARMARX_VERBOSE << "Last segment; extent previous segment to the end.";
1343 }
1344 }
1345 return false;
1346 };
1347
1348 for (int i = 0; i < n; i++)
1349 {
1350 // continue the current sequence until its end
1351 while (i < n and localPlanEligibility[i] == segment.useLocalPlanner)
1352 {
1353 i++;
1354 }
1355
1356 segment.t = i;
1357 if (checkSegmentLength())
1358 {
1359 globalPlan->subdivision.emplace_back(segment);
1360 ARMARX_VERBOSE << "Segment " << globalPlan->subdivision.size() << ": [" << segment.s
1361 << ", " << segment.t
1362 << "); localPlanner=" << segment.useLocalPlanner;
1363
1364 // start the next sequence
1365 segment.s = i;
1366 segment.useLocalPlanner = localPlanEligibility[i];
1367 }
1368 }
1369
1371
1372 // check subdivision is valid
1373 std::size_t lastSegmentEnd = 0;
1374 for (const auto& segment : globalPlan->subdivision)
1375 {
1376 ARMARX_CHECK_EQUAL(lastSegmentEnd, segment.s);
1377 const std::ptrdiff_t segmentLength = segment.t - segment.s;
1378 ARMARX_CHECK_POSITIVE(segmentLength);
1379 lastSegmentEnd = segment.t;
1380 }
1381 ARMARX_CHECK_EQUAL(lastSegmentEnd, globalPlan->plan.trajectory.points().size());
1382
1383 globalPlan->currentSegment = 0;
1384 srv.introspector->onGlobalPlannerSubdivision(globalPlan.value());
1385
1386 ARMARX_INFO << "Divided global path into " << globalPlan->subdivision.size() << " segments";
1387 }
1388
1389 void
1390 Navigator::applyParametrization(core::GlobalTrajectory& trajectory, const float startVelocity)
1391 {
1392 // Copied *before* apply(): in the non-subdivision branch `currentGlobalSegment()` hands
1393 // back `plan.trajectory` by reference, so apply() overwrites the planner's velocities in
1394 // place. Those velocities are the obstacle-aware limit, and the record below needs them.
1395 const core::GlobalTrajectory planned = trajectory;
1396
1397 core::TrajectoryParametrization applied = parametrizationMode;
1398
1399 // TOPP-RA fits a spline through the path and needs at least four distinct waypoints for
1400 // it. A pure rotation or a very short point-to-point motion has fewer, and would fail on
1401 // every invocation. Silent by design: this is an expected shape of request, not a fault.
1402 constexpr std::size_t minimumWaypointsForToppra = 4;
1403
1405 trajectory.points().size() < minimumWaypointsForToppra)
1406 {
1408 rampingFallback->apply(trajectory, startVelocity);
1409 }
1410 else
1411 {
1412 try
1413 {
1414 parametrization->apply(trajectory, startVelocity);
1415 }
1416 catch (const std::exception& e)
1417 {
1418 // An unparametrizable path must not abort the navigation request: ramping is
1419 // always executable, just slower.
1420 ARMARX_WARNING << "Trajectory parametrization failed (" << e.what()
1421 << "). Falling back to ramping for this request.";
1422
1424 trajectory = planned;
1425 rampingFallback->apply(trajectory, startVelocity);
1426 }
1427 }
1428
1429 // Which parametrization a request actually got is not otherwise visible in the log: the
1430 // requested one is announced when the navigator is built, but every degradation path
1431 // above is per-request. The two durations say what it bought -- both are the same
1432 // Riemann sum over the same path, so they are directly comparable.
1433 ARMARX_INFO << "Parametrized " << trajectory.points().size() << " waypoints with "
1434 << QUOTED(core::ToString(applied)) << ": "
1436 << " s (the planner's obstacle-aware velocities alone would take "
1438 << " s).";
1439
1440 if (config.general.parametrizationDump)
1441 {
1443 {.requested = config.stack.generalConfig.parametrization,
1444 .applied = applied,
1445 .planned = planned,
1446 .parametrized = trajectory,
1447 .toppra = dynamic_cast<const algorithms::Toppra*>(parametrization.get()),
1448 .driveParams = driveParams,
1449 .maxVelocity = config.stack.generalConfig.maxVel.linear,
1450 .maxAngularVelocity = config.stack.generalConfig.maxVel.angular},
1451 config.general.parametrizationDumpPath);
1452 }
1453 }
1454
1455 void
1456 Navigator::resumeAfterEmergencyStop()
1457 {
1458 if (not globalPlan.has_value() or isPaused() or isStopped())
1459 {
1460 return;
1461 }
1462
1463 if (globalPlan->useLocalPlanner(hasLocalPlanner()))
1464 {
1465 // The local planner re-plans from the robot every cycle, so its trajectory is
1466 // already anchored where the robot stands. Nothing to re-anchor.
1467 return;
1468 }
1469
1470 const core::GlobalTrajectory& trajectory = globalPlan->currentGlobalSegment();
1471 if (trajectory.points().empty())
1472 {
1473 return;
1474 }
1475
1476 const core::Position robotPosition =
1477 core::Pose(srv.sceneProvider->scene().robot->getGlobalPose()).translation();
1478
1479 // The whole remainder, not a lookahead window: this replaces what the executor runs
1480 // until the next segment starts, and cutting it short would end the motion early.
1481 auto [remaining, endsAtGoal] = trajectory.getSubTrajectory(
1482 robotPosition, std::numeric_limits<float>::infinity());
1483
1484 if (remaining.points().size() < 2)
1485 {
1486 ARMARX_WARNING << "Cannot re-anchor the trajectory after the emergency stop: only "
1487 << remaining.points().size()
1488 << " waypoints remain. Resuming on the trajectory as planned.";
1489 return;
1490 }
1491
1492 ARMARX_IMPORTANT << "Re-anchoring the trajectory at the robot's current pose after the "
1493 "emergency stop: "
1494 << remaining.points().size() << " of " << trajectory.points().size()
1495 << " waypoints remain.";
1496
1497 applyParametrization(remaining, config.stack.generalConfig.boundaryVelocity);
1498
1499 updateExecutor(remaining);
1500 }
1501
1502 bool
1503 Navigator::startGlobalPathSegment(bool incrementSegment, bool rampFromCurrentVelocity)
1504 {
1505 if (not globalPlan->subdivision.empty())
1506 {
1507 // subdivision enabled
1508
1509 if (incrementSegment)
1510 {
1511 globalPlan->currentSegment++;
1512 }
1513
1514 if (globalPlan->currentSegment >= globalPlan->subdivision.size())
1515 {
1516 // global path already finished
1517 return true;
1518 }
1519
1520 ARMARX_INFO << "Starting segment " << globalPlan->currentSegment;
1521 const auto& currentSegment = globalPlan->subdivision[globalPlan->currentSegment];
1522
1523 globalPlan->currentSegmentTrajectory =
1524 globalPlan->plan.trajectory.getSubTrajectory(currentSegment.s, currentSegment.t);
1525
1526 applyParametrization(globalPlan->currentGlobalSegment(),
1527 rampFromCurrentVelocity
1528 ? srv.sceneProvider->scene().platformVelocity.linear.norm()
1529 : config.stack.generalConfig.boundaryVelocity);
1530
1531 goalReachedMonitor->updateGoal(
1532 globalPlan->currentGlobalSegment().points().back().waypoint.pose);
1533 }
1534 else
1535 {
1536 if (incrementSegment)
1537 {
1538 return true;
1539 }
1540
1541 applyParametrization(globalPlan->currentGlobalSegment(),
1542 rampFromCurrentVelocity
1543 ? srv.sceneProvider->scene().platformVelocity.linear.norm()
1544 : config.stack.generalConfig.boundaryVelocity);
1545
1546 goalReachedMonitor->updateGoal(
1547 globalPlan->currentGlobalSegment().points().back().waypoint.pose);
1548
1549 // only visualize global trajectory once ramping has been performed
1550 srv.introspector->onGlobalPlannerResult(globalPlan->plan);
1551 }
1552
1553 if (globalPlan->useLocalPlanner(hasLocalPlanner()))
1554 {
1555 const auto localPlannerResult = updateLocalPlanner();
1556 updateExecutor(localPlannerResult);
1557 updateIntrospector(localPlannerResult);
1558 }
1559 else
1560 {
1561 updateExecutor(globalPlan->currentGlobalSegment());
1562 }
1563
1564 ARMARX_INFO << "Start executing global plan segment (local planner="
1565 << globalPlan->useLocalPlanner(hasLocalPlanner())
1566 << "). Will now start all required threads.";
1568
1569 startStack();
1570 ARMARX_INFO << "Movement started.";
1571
1572 return false;
1573 }
1574
1575 void
1577 {
1578 }
1579
1580 void
1582 const std::optional<std::string>& providerName)
1583 {
1584 // update static scene including locations
1585 updateScene(true);
1586
1587 const auto resolveLocation =
1588 [&](const std::string& location,
1589 const std::optional<std::string>& providerName) -> std::vector<core::Location>
1590 {
1591 const auto locations = srv.sceneProvider->scene().staticScene->locations;
1592
1593 ARMARX_VERBOSE << "Available locations";
1594 for (const auto& location : locations)
1595 {
1596 ARMARX_VERBOSE << QUOTED(location.name) << " from provider "
1597 << QUOTED(location.provider);
1598 }
1599
1600 const auto matchingLocs =
1601 core::util::findMatchingLocations(locations, location, providerName);
1602
1603 ARMARX_CHECK_NOT_EMPTY(matchingLocs)
1604 << "Unknown location " << QUOTED(location) << " and provider "
1605 << QUOTED(providerName.value_or("~unset~")) << ".";
1606 return matchingLocs;
1607 };
1608
1609 const auto split = simox::alg::split(location, ":");
1610 ARMARX_CHECK(0 < split.size() && split.size() <= 2)
1611 << "The given location does not match the format <location>(:<instance-id>)? '"
1612 << location << "'.";
1613
1614 std::string instanceID;
1615 if (split.size() == 2)
1616 {
1617 // An instance-id was given in the location
1618 instanceID = split.back();
1619 }
1620
1621 const auto matchingLocations = resolveLocation(split.front(), providerName);
1622
1623
1624 if (matchingLocations.empty())
1625 {
1626 ARMARX_WARNING << "Failed to resolve location " << QUOTED(location) << " from provider "
1627 << QUOTED(providerName.value_or("~unset~"));
1628 }
1629
1630 if (matchingLocations.size() > 1)
1631 {
1632 ARMARX_WARNING << "Found more than one matching location for " << QUOTED(location)
1633 << " from provider " << QUOTED(providerName.value_or("~unset~"));
1634 for (const auto& location : matchingLocations)
1635 {
1636 ARMARX_INFO << "- " << QUOTED(location.provider) << ": " << QUOTED(location.name);
1637 }
1638 }
1639
1640
1641 const auto goal = core::resolveLocation(srv.sceneProvider->scene().staticScene->objectMap,
1642 srv.sceneProvider->scene().staticScene->objectInfo,
1643 matchingLocations.front().framedPose,
1644 instanceID);
1645
1646 if (goal.pose.has_value())
1647 {
1648 moveToAbsolute({goal.pose.value()}, false);
1649 }
1650 else
1651 {
1652 ARMARX_ERROR << goal.errorMsg;
1653 }
1654 }
1655
1656 void
1657 Navigator::moveTowardsAbsolute(const core::Direction& direction)
1658 {
1659 }
1660
1661 void
1662 Navigator::run()
1663 {
1664 // Ensure only one thread can call this method at a time (This is not ensured by the PeriodicTask).
1665 // Given the lock in updateLocalPlanner, this is currently not fixing any bugs on its own,
1666 // however, running this function from different threads simultaneously does not provide any benefits
1667 // and could lead to further bugs.
1668 const std::scoped_lock<std::mutex> lock{runMtx};
1669
1670 if (not shouldRun)
1671 {
1673 << "Called Navigator::run() although shouldRun is false. Directly returning.";
1674 return;
1675 }
1676
1677 // TODO(fabian.reister): add debug observer logging
1678
1679 // scene update
1680 {
1681 ARMARX_DEBUG << "Updating scene";
1682
1683 // TODO: remove full update and only update costmap when required!!!
1684 // this will slow down the navigation loop significantly (>1s)
1685 const Duration duration = armarx::core::time::StopWatch::measure(
1686 [&]() { updateScene(true /* false (only dynamic and costmap)*/); });
1687
1689 << "Scene update: " << duration.toMilliSecondsDouble() << "ms.";
1690
1691 srv.debugObserverHelper->setDebugObserverDatafield("scene update [ms]",
1692 duration.toMilliSecondsDouble());
1693 }
1694
1695 // verify global path is still possible
1696 {
1697 // if we have target alternatives and the path is no longer valid, attempt to
1698 // navigate to a valid alternative
1699 if (globalPlan.has_value() and not targetAlternatives.empty())
1700 {
1701 checkGlobalPathAlternatives();
1702 }
1703 }
1704
1705 // eventually, draw
1706 if ((srv.introspector != nullptr) and (srv.sceneProvider != nullptr) and
1707 srv.sceneProvider->scene().robot)
1708 {
1709 ARMARX_DEBUG << "Drawing robot pose";
1710 srv.introspector->onRobotPose(
1711 core::Pose(srv.sceneProvider->scene().robot->getGlobalPose()));
1712 }
1713
1714 // global planner update if goal has changed
1715 // niklas: symbol globalPlanningRequest is not used, so this code is dead
1716 /*{
1717 std::lock_guard g{globalPlanningRequestMtx};
1718
1719 if (globalPlanningRequest.has_value())
1720 {
1721 const auto& waypoints = globalPlanningRequest.value();
1722
1723 // recreate goal monitor
1724 {
1725 // first we check if we are already at the goal position
1726 goalReachedMonitor = std::nullopt;
1727 goalReachedMonitor = GoalReachedMonitor(
1728 waypoints.back(), srv.sceneProvider->scene(), config.goalReachedConfig);
1729
1730 if (goalReachedMonitor->goalReached(false))
1731 {
1732 ARMARX_INFO << "Already at goal position. Robot won't move.";
1733
1734 srv.publisher->goalReached(core::GoalReachedEvent{
1735 {armarx::Clock::Now()},
1736 core::Pose(srv.sceneProvider->scene().robot->getGlobalPose())});
1737
1738 return;
1739 }
1740 }
1741
1742 // global planning
1743 {
1744 // global planner
1745 ARMARX_INFO << "Update/Planning global trajectory";
1746 ARMARX_CHECK_NOT_NULL(config.stack.globalPlanner);
1747 // TODO plan on multiple waypoints, ignoring waypoints for now
1748 // idea: compute multiple global trajectories, one for each segment between waypoints.
1749
1750 srv.introspector->onGoal(waypoints.back());
1751 globalPlan = config.stack.globalPlanner->plan(waypoints.back());
1752
1753 ARMARX_TRACE;
1754
1755 if (not globalPlan.has_value())
1756 {
1757 ARMARX_WARNING << "No global trajectory. Cannot move.";
1758 srv.publisher->globalPlanningFailed(core::GlobalPlanningFailedEvent{
1759 {.timestamp = armarx::Clock::Now()}, {""}});
1760
1761 srv.introspector->failure();
1762 return;
1763 }
1764
1765 ARMARX_TRACE;
1766 srv.publisher->globalTrajectoryUpdated(globalPlan.value());
1767 srv.introspector->onGlobalPlannerResult(globalPlan.value());
1768
1769 if (not hasLocalPlanner())
1770 {
1771 updateExecutor(globalPlan.value());
1772 }
1773 }
1774 }
1775 }*/
1776
1777 // Before anything is activated again: the executor withdrew its controller request when
1778 // the emergency stop engaged, so this is the one chance to replace the trajectory the
1779 // robot would otherwise resume on.
1780 if (srv.executor != nullptr and srv.executor->consumeEmergencyStopRelease())
1781 {
1782 resumeAfterEmergencyStop();
1783 }
1784
1785 // local planner update
1786 {
1787 ARMARX_VERBOSE << "Local planner update";
1788
1790 [&]()
1791 {
1792 if (globalPlan->useLocalPlanner(hasLocalPlanner()))
1793 {
1794 const auto localPlannerResult = updateLocalPlanner();
1795 updateExecutor(localPlannerResult);
1796 updateIntrospector(localPlannerResult);
1797
1798 if (srv.executor != nullptr && localPlannerResult.has_value() &&
1799 not isPaused() && not isStopped())
1800 {
1801 srv.executor->ensureIsActive(
1802 ExecutorInterface::ControllerType::LocalTrajectory);
1803 }
1804 }
1805 else if (srv.executor != nullptr && not isPaused() && not isStopped())
1806 {
1807 srv.executor->ensureIsActive(
1808 ExecutorInterface::ControllerType::GlobalTrajectory);
1809 }
1810 });
1812 << "Local planner update: " << duration.toMilliSecondsDouble() << "ms.";
1813
1814 srv.debugObserverHelper->setDebugObserverDatafield("local planner update [ms]",
1815 duration.toMilliSecondsDouble());
1816 }
1817
1818 // update velocity limits of underlying platform controller
1819 // - apply safety guard, if present
1820 // - ensure the limits of the currently active navigator are used
1821 {
1822 if (hasSafetyGuard())
1823 {
1824 ARMARX_VERBOSE << "Updating safety guard";
1825 const auto result = updateSafetyGuard();
1826 srv.debugObserverHelper->setDebugObserverDatafield("safety_guard.limit_linear",
1827 result.twistLimits.linear);
1828 srv.debugObserverHelper->setDebugObserverDatafield("safety_guard.limit_angular",
1829 result.twistLimits.angular);
1830
1831 if (srv.executor != nullptr)
1832 {
1833 srv.executor->updateVelocityLimits(result.twistLimits);
1834 }
1835
1836 // if the safety guard keeps the robot blocked for too long, re-run the
1837 // global planner
1838 checkRobotBlocked(result);
1839 }
1840 else
1841 // apply the maximum velocity limit from the general config, in case a different navigator applied other limits in between
1842 {
1843 if (srv.executor != nullptr)
1844 {
1845 srv.executor->updateVelocityLimits(config.stack.generalConfig.maxVel);
1846 }
1847 }
1848 }
1849
1850 // request to scale the calculated velocity by the velocity factor
1851 {
1853 srv.debugObserverHelper->setDebugObserverDatafield("velocity_factor",
1854 velocityFactor.load());
1855
1856 // scaling is done relatively; for typical absolute values: see SPFA, 500mm/s 0.4 rad/s
1857 if (srv.executor != nullptr)
1858 {
1859 srv.executor->updateVelocityFactor(velocityFactor);
1860 }
1861 }
1862
1863 // monitor update
1864 {
1866 ARMARX_DEBUG << "Monitor update";
1867
1868 const Duration duration =
1869 armarx::core::time::StopWatch::measure([&]() { updateMonitor(); });
1870
1872 << "Monitor update: " << duration.toMilliSecondsDouble() << "ms.";
1873
1874 srv.debugObserverHelper->setDebugObserverDatafield("monitor update [ms]",
1875 duration.toMilliSecondsDouble());
1876 }
1877 }
1878
1880 Navigator::updateSafetyGuard()
1881 {
1882 ARMARX_CHECK(hasSafetyGuard());
1883
1884 const core::Pose global_T_robot(srv.sceneProvider->scene().robot->getGlobalPose());
1885 const auto proj = globalPlan->currentGlobalSegment().getProjection(
1886 global_T_robot.translation(), core::VelocityInterpolation::LinearInterpolation);
1887 const Eigen::Vector3f global_V_movement = proj.wayPointAfter.waypoint.pose.translation() -
1888 proj.projection.waypoint.pose.translation();
1889
1890 return config.stack.safetyGuard->computeSafetyLimits(global_V_movement.head<2>());
1891 }
1892
1893 void
1894 Navigator::checkRobotBlocked(const safety_guard::SafetyGuardResult& result)
1895 {
1896 const auto& cfg = config.general.blockedReplanning;
1897
1898 if (not cfg.enabled)
1899 {
1900 return;
1901 }
1902
1903 // only relevant while actively navigating towards a goal
1904 if (isPaused() or isStopped() or not globalPlan.has_value() or
1905 not goalReachedMonitor.has_value())
1906 {
1907 blockedSince = std::nullopt;
1908 return;
1909 }
1910
1911 // the safety guard limits the platform velocity close to zero when the robot
1912 // cannot make progress (e.g. an obstacle blocks the planned path)
1913 const bool blocked = (result.twistLimits.linear < cfg.linearLimit) or
1914 (result.twistLimits.angular < cfg.angularLimit);
1915
1916 if (not blocked)
1917 {
1918 // robot is able to move again -> reset the timer
1919 blockedSince = std::nullopt;
1920 return;
1921 }
1922
1923 const armarx::DateTime now = armarx::Clock::Now();
1924 if (not blockedSince.has_value())
1925 {
1926 // start measuring how long the robot stays blocked
1927 blockedSince = now;
1928 return;
1929 }
1930
1931 const armarx::Duration blockedFor = now - blockedSince.value();
1932 srv.debugObserverHelper->setDebugObserverDatafield("safety_guard.blocked_for [ms]",
1933 blockedFor.toMilliSecondsDouble());
1934
1935 if (blockedFor < armarx::Duration::SecondsDouble(cfg.blockedTimeSeconds))
1936 {
1937 // not blocked long enough yet -> keep waiting (obstacle might clear)
1938 return;
1939 }
1940
1941 ARMARX_IMPORTANT << "Robot blocked for " << blockedFor.toMilliSecondsDouble()
1942 << "ms (safety guard limits near zero). Re-running the global planner.";
1943
1944 // reset so the freshly planned trajectory gets a chance before we re-check
1945 blockedSince = std::nullopt;
1946
1947 // re-run the global planner towards the current goal
1948 updateAbsolute({goalReachedMonitor->goal()});
1949 }
1950
1951 bool
1952 Navigator::hasSafetyGuard() const
1953 {
1954 return config.stack.safetyGuard != nullptr;
1955 }
1956
1957 bool
1958 Navigator::hasLocalPlanner() const noexcept
1959 {
1960 return config.stack.localPlanner != nullptr;
1961 }
1962
1963 void
1964 Navigator::updateScene(const bool fullUpdate)
1965 {
1966 ARMARX_CHECK_NOT_NULL(srv.sceneProvider);
1967 srv.sceneProvider->synchronize(armarx::Clock::Now(), fullUpdate);
1968 }
1969
1970 void
1971 Navigator::checkGlobalPathAlternatives()
1972 {
1973 ARMARX_CHECK_NOT_EMPTY(targetAlternatives);
1974
1975 const auto replanAlternatives = [&]()
1976 {
1977 lastAllAlternativesImpossible = std::nullopt;
1978
1979 // save current global plan to restore when no path is currently possible
1980 // i.e. if a person is standing in a doorway and no paths are possible anymore,
1981 // we restore the previous path and move along it until the safety guard stops the robot
1982 const auto previousGlobalPlan = globalPlan;
1983
1984 if (setupTargetAlternatives())
1985 {
1986 // an alternative path was found
1987 ARMARX_INFO << "An alternative path was found.";
1988 setupGlobalPlanSubvidision();
1989 }
1990 else
1991 {
1992 ARMARX_INFO << "No possible alternatives, restoring previous global path.";
1993 globalPlan = std::move(previousGlobalPlan);
1994 lastAllAlternativesImpossible = armarx::Clock::Now();
1995 }
1996
1997 // either start the new global path or continue on the old one
1998 startGlobalPathSegment(false, true);
1999 };
2000
2001 if (not verifyGlobalPathPossible(globalPlan->currentGlobalSegment()))
2002 {
2003 // global path is not possible -> either wait for timeout or check alternatives
2004
2005 if (lastAllAlternativesImpossible.has_value() and
2006 (armarx::Clock::Now() - lastAllAlternativesImpossible.value() <
2008 config.general.targetAlternativesFilterTimeSeconds)))
2009 {
2011 << "Current globalPlan invalid, waiting for timeout before replanning.";
2012 }
2013 else
2014 {
2015 ARMARX_INFO << "Global path no longer possible, trying other alternatives!";
2016
2017 replanAlternatives();
2018 }
2019 }
2020 else if (lastAllAlternativesImpossible.has_value())
2021 {
2022 // globalPlan was previously invalid but is valid now
2023 // -> recheck all possible alternatives and their priority
2025 << "Previously invalid global plan valid again -> replanning all alternatives";
2026
2027 replanAlternatives();
2028 }
2029 }
2030
2031 bool
2032 Navigator::verifyGlobalPathPossible(const core::GlobalTrajectory& plan)
2033 {
2034 const auto& costmap = srv.sceneProvider->scene().staticScene->distanceToObstaclesCostmap;
2035 ARMARX_CHECK(costmap.has_value());
2036
2037 for (const auto& pt : plan.points())
2038 {
2039 const auto& pose = pt.waypoint.pose;
2040 const Eigen::Vector2f position = conv::to2D(pose.translation());
2041 const auto vertex = costmap->toVertex(position);
2042 if (not costmap->isValid(vertex.index) or costmap->isInCollision(vertex.position))
2043 {
2044 return false;
2045 }
2046 }
2047
2048 return true;
2049 }
2050
2051 std::optional<local_planning::LocalPlannerResult>
2052 Navigator::updateLocalPlanner()
2053 {
2054 // We need to make sure the local planner is not simultaniously called from different threads
2055 const std::scoped_lock<std::mutex> lock{updateLocalPlannerMtx};
2056
2057 ARMARX_CHECK(hasLocalPlanner());
2058
2059 ARMARX_VERBOSE << "Updating local plan";
2060
2061 try
2062 {
2063 const auto& globalTrajectory = globalPlan->currentGlobalSegment();
2064 ARMARX_VERBOSE << globalTrajectory.points().size() << " points in global plan";
2065 localPlan = config.stack.localPlanner->plan(globalTrajectory);
2066 ARMARX_VERBOSE << "Local planning finished";
2067 if (localPlan.has_value())
2068 {
2069 srv.publisher->localTrajectoryUpdated(core::LocalTrajectoryUpdatedEvent{
2070 {.timestamp = armarx::Clock::Now()}, localPlan->trajectory});
2071 return localPlan;
2072 }
2073 // do not return here, so that localPlanningFailed event is published
2074 //return localPlan;
2075 }
2076 catch (...)
2077 {
2078 ARMARX_WARNING << "Failure in local planner: " << GetHandledExceptionString();
2079 srv.publisher->localPlanningFailed(core::LocalPlanningFailedEvent{
2080 {.timestamp = armarx::Clock::Now()}, {GetHandledExceptionString()}});
2081
2082 return std::nullopt;
2083 }
2084
2085 srv.publisher->localPlanningFailed(core::LocalPlanningFailedEvent{
2086 {.timestamp = armarx::Clock::Now()}, {"Unknown reason"}});
2087
2088 return std::nullopt;
2089 }
2090
2091 void
2092 Navigator::updateExecutor(const std::optional<local_planning::LocalPlannerResult>& localPlan)
2093 {
2094 if (srv.executor == nullptr)
2095 {
2096 return;
2097 }
2098
2099
2100 if (isPaused() or isStopped())
2101 {
2102 // [[unlikely]]
2103 ARMARX_VERBOSE << deactivateSpam(1) << "stopped or paused";
2104 return;
2105 }
2106
2107 if (not localPlan.has_value())
2108 {
2109 ARMARX_INFO << "Local plan is invalid!";
2110 ARMARX_CHECK_NOT_NULL(srv.executor);
2111 srv.executor->execute(core::LocalTrajectory{}, false);
2112 srv.executor->stop();
2113 return;
2114 }
2115
2116
2117 // const core::Rotation robot_R_world(srv.sceneProvider->scene().robot->getGlobalOrientation().inverse());
2118
2119 // ARMARX_VERBOSE
2120 // << deactivateSpam(100) << "Robot orientation "
2121 // << simox::math::mat3f_to_rpy(srv.sceneProvider->scene().robot->getGlobalOrientation()).z();
2122
2123 // core::Twist robotFrameVelocity;
2124
2125 // robotFrameVelocity.linear = robot_R_world * twist.linear;
2126 // // FIXME fix angular velocity
2127 // robotFrameVelocity.angular = twist.angular;
2128
2129 // ARMARX_VERBOSE << deactivateSpam(1) << "velocity in robot frame "
2130 // << robotFrameVelocity.linear;
2131
2132 ARMARX_CHECK_NOT_NULL(srv.executor);
2133 srv.executor->execute(localPlan->trajectory, true);
2134 }
2135
2136 void
2137 Navigator::updateExecutor(const core::GlobalTrajectory& globalTrajectory)
2138 {
2139 if (srv.executor == nullptr)
2140 {
2141 return;
2142 }
2143
2144 ARMARX_IMPORTANT << "Requested to execute global plan with "
2145 << globalTrajectory.points().size() << " points.";
2146
2147
2148 // if (isPaused() or isStopped())
2149 // {
2150 // // [[unlikely]]
2151 // ARMARX_VERBOSE << deactivateSpam(1) << "stopped or paused";
2152 // return;
2153 // }
2154
2155 if (globalTrajectory.points().empty())
2156 {
2157 ARMARX_INFO << "Global plan is invalid!";
2158 ARMARX_CHECK_NOT_NULL(srv.executor);
2159 srv.executor->stop();
2160 return;
2161 }
2162
2163 ARMARX_IMPORTANT << "Executing global plan with " << globalTrajectory.points().size()
2164 << " points.";
2165 ARMARX_CHECK_NOT_NULL(srv.executor);
2166 srv.executor->execute(globalTrajectory, false);
2167 }
2168
2169 void
2170 Navigator::updateIntrospector(
2171 const std::optional<local_planning::LocalPlannerResult>& localPlan)
2172 {
2173 ARMARX_CHECK_NOT_NULL(srv.introspector);
2174
2175 srv.introspector->onLocalPlannerResult(localPlan);
2176 }
2177
2178 void
2179 Navigator::updateMonitor()
2180 {
2182 //ARMARX_CHECK(goalReachedMonitor.has_value());
2183 if (not goalReachedMonitor.has_value())
2184 {
2185 // Note: I guess this can happen because of a race condition between the moveTo methods and the run method.
2186 // But I am not sure if a common mutex would work correctly.
2187 ARMARX_WARNING << "GoalReachedMonitor is not initialized.";
2188 return;
2189 }
2190
2191 const auto status = goalReachedMonitor->status();
2192 armarx::DebugObserverHelper* debugObserver = srv.debugObserverHelper;
2193
2194 if (debugObserver != nullptr)
2195 {
2196 debugObserver->setDebugObserverDatafield("goalPositionError", status.posError);
2197 debugObserver->setDebugObserverDatafield("goalOrientationError", status.oriError);
2198 debugObserver->setDebugObserverDatafield("goalPositionThreshold",
2199 goalReachedMonitor->getConfig().posTh);
2200 debugObserver->setDebugObserverDatafield("goalOrientationThreshold",
2201 goalReachedMonitor->getConfig().oriTh);
2202 }
2203
2204 if (not isPaused() and goalReachedMonitor->goalReached())
2205 {
2206 // [[unlikely]]
2207 ARMARX_INFO << "Current global path segment finished!";
2208
2209 // we finished the current segment -> start the new one
2210 if (startGlobalPathSegment(true, true))
2211 {
2212 // this segment was the last one, we are finished
2213 ARMARX_INFO << "Goal " << goalReachedMonitor->goal().translation().head<2>()
2214 << " reached!";
2215
2216 // Before `stop()`, and not relying on it: `stop()` deactivates the trajectory
2217 // controller only if it reports active, so the controller that holds this
2218 // request's execution record is not guaranteed to be told the request is over.
2219 // Asking here is what makes the record independent of that.
2220 if (srv.executor != nullptr)
2221 {
2222 srv.executor->dumpDiagnostics();
2223 }
2224
2225 stop();
2226
2227 srv.publisher->goalReached(core::GoalReachedEvent{
2229 {core::Pose(srv.sceneProvider->scene().robot->getGlobalPose())}});
2230
2231 srv.introspector->success();
2232 }
2233 }
2234 }
2235
2236 void
2237 Navigator::stopAllThreads()
2238 {
2239 const std::scoped_lock<std::mutex> lock{runningTaskMtx};
2240
2241 if (runningTask)
2242 {
2243 runningTask->stop();
2244 }
2245
2246 shouldRun = false;
2247 }
2248
2249 // bool
2250 // Navigator::isStackResultValid() const noexcept
2251 // {
2252
2253 // // global planner
2254 // if (config.stack.globalPlanner != nullptr)
2255 // {
2256 // if (not globalPlan.has_value())
2257 // {
2258 // ARMARX_VERBOSE << deactivateSpam(1) << "Global trajectory not yet set.";
2259 // return false;
2260 // }
2261 // }
2262
2263 // // local planner
2264 // if (config.stack.localPlanner != nullptr)
2265 // {
2266 // if (not localPlan.has_value())
2267 // {
2268 // ARMARX_VERBOSE << deactivateSpam(1) << "Local trajectory not yet set.";
2269 // return false;
2270 // }
2271 // }
2272
2273 // // [[likely]]
2274 // return true;
2275 // }
2276
2277 // const core::Trajectory& Navigator::currentTrajectory() const
2278 // {
2279 // ARMARX_CHECK(isStackResultValid());
2280
2281 // if(localPlan.has_value())
2282 // {
2283 // return localPlan->trajectory;
2284 // }
2285
2286 // return globalPlan->trajectory;
2287 // }
2288
2289 void
2290 Navigator::setVelocityFactor(const float velocityFactor)
2291 {
2292 ARMARX_CHECK_POSITIVE(velocityFactor)
2293 << "Scaling factor for velocity may not be negative, but is " << velocityFactor;
2294 ARMARX_CHECK_LESS_EQUAL(velocityFactor, 1)
2295 << "Scaling factor for velocity may not be > 1, but is " << velocityFactor;
2296
2297 this->velocityFactor = velocityFactor;
2298 }
2299
2300 void
2302 {
2303 ARMARX_INFO << "Paused.";
2304
2305 executorEnabled.store(false);
2306
2307 if (srv.executor != nullptr)
2308 {
2309 ARMARX_INFO << "Stopping executor.";
2310 srv.executor->stop();
2311 }
2312 }
2313
2314 void
2316 {
2317 ARMARX_INFO << "Resume.";
2318
2319 executorEnabled.store(true);
2320
2321 if (srv.executor != nullptr)
2322 {
2323 if (hasLocalPlanner())
2324 {
2326 }
2327 else
2328 {
2330 }
2331 }
2332 }
2333
2334 void
2336 {
2337 ARMARX_INFO << "Stopping.";
2338
2339 pause();
2340
2341 // stop all threads, including this one
2342 stopAllThreads();
2343
2344 goalReachedMonitor.reset();
2345 goalReachedMonitor = std::nullopt;
2346 }
2347
2348 bool
2349 Navigator::isPaused() const noexcept
2350 {
2351 return not executorEnabled.load();
2352 }
2353
2354 bool
2355 Navigator::isStopped() const noexcept
2356 {
2357 return (not shouldRun) or (not runningTask) or (not runningTask->isRunning());
2358 }
2359
2361 config{std::move(other.config)},
2362 srv{std::move(other.srv)},
2363 parametrization{std::move(other.parametrization)},
2364 executorEnabled{other.executorEnabled.load()}
2365 {
2366 }
2367
2368
2369} // namespace armarx::navigation::server
#define ARMARX_CHECK_NOT_EMPTY(c)
SpamFilterDataPtr deactivateSpam(SpamFilterDataPtr const &spamFilter, float deactivationDurationSec, const std::string &identifier, bool deactivate)
Definition Logging.cpp:75
#define QUOTED(x)
static DateTime Now()
Current time on the virtual clock.
Definition Clock.cpp:93
void setDebugObserverDatafield(const std::string &channelName, const std::string &datafieldName, const TimedVariantPtr &value) const
static Duration SecondsDouble(double seconds)
Constructs a duration in seconds.
Definition Duration.cpp:78
The periodic task executes one thread method repeatedly using the time period specified in the constr...
double toMilliSecondsDouble() const
Returns the amount of milliseconds.
Definition Duration.cpp:66
static Duration measure(std::function< void(void)> subjectToMeasure, ClockType clockType=ClockType::Virtual)
Measures the duration needed to execute the given lambda and returns it.
Definition StopWatch.cpp:31
Time-optimal reparametrization under per-motor torque and command-ramp limits.
Definition Toppra.h:52
static bool available()
Whether the python side was found at configure time. False means apply() throws.
Definition Toppra.cpp:217
static std::string unavailableReason()
Why it is unavailable, as recorded at configure time. Empty when available.
Definition Toppra.cpp:227
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.
Definition targets.h:39
#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.
Definition Logging.h:179
#define ARMARX_IMPORTANT
The logging level for always important information, but expected behaviour (in contrast to ARMARX_WAR...
Definition Logging.h:188
#define ARMARX_ERROR
The logging level for unexpected behaviour, that must be fixed.
Definition Logging.h:194
#define ARMARX_DEBUG
The logging level for output that is only interesting while debugging.
Definition Logging.h:182
#define ARMARX_WARNING
The logging level for unexpected behaviour, but not a serious problem.
Definition Logging.h:191
#define ARMARX_VERBOSE
The logging level for verbose information.
Definition Logging.h:185
armarx::core::time::Duration Duration
Toppra::DriveParams LoadDriveParams(const std::filesystem::path &configFile)
Read Toppra::DriveParams from a PlatformDynamics<Robot>.json.
Definition Toppra.cpp:127
std::shared_ptr< TrajectoryParametrization > TrajectoryParametrizationPtr
std::filesystem::path DriveParamsPath(const std::string &robot)
Resolve config/platform/PlatformDynamics<robot>.json inside the armarx_navigation package.
Definition Toppra.cpp:119
This file is part of ArmarX.
std::vector< Eigen::Vector2f > to2D(const std::vector< Eigen::Vector3f > &v)
Definition eigen.cpp:29
std::vector< core::Location > findMatchingLocations(const std::vector< core::Location > &locations, const std::string &locationName, const std::optional< std::string > &provider)
Definition location.cpp:46
Graph::ConstVertex getVertexByName(const std::string &vertexName, const Graph &graph)
Definition Graph.cpp:209
Eigen::Vector3f Direction
Definition basic_types.h:39
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)
Definition Graph.cpp:182
void resolveLocation(Graph::Vertex &vertex, const aron::data::DictPtr &locationData)
Definition Graph.cpp:267
Eigen::Isometry3f Pose
Definition basic_types.h:31
Eigen::Vector3f Position
Definition basic_types.h:36
const core::Graph & getSubgraph(const std::string &vertexName, const Graphs &graphs)
Definition Graph.cpp:234
This file is part of ArmarX.
This file is part of ArmarX.
Definition Visu.h:48
This file is part of ArmarX.
Definition constants.cpp:4
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.
Definition events.h:114
Event describing that the targeted goal was successfully reached.
Definition events.h:53
Event describing that the local trajectory was updated.
Definition events.h:122
GlobalPathSubdivision(const global_planning::GlobalPlannerResult &globalPlan)
Definition Navigator.cpp:72
global_planning::GlobalPlannerResult plan
Definition Navigator.h:79
const core::GlobalTrajectory & currentGlobalSegment() const
Definition Navigator.cpp:94
#define ARMARX_TRACE
Definition trace.h:75