Navigator.cpp
Go to the documentation of this file.
1#include "Navigator.h"
2
3#include <algorithm>
4#include <cmath>
5#include <cstddef>
6#include <iterator>
7#include <limits>
8#include <memory>
9#include <mutex>
10#include <optional>
11#include <string>
12#include <utility>
13#include <vector>
14
15#include <boost/graph/detail/adjacency_list.hpp>
16#include <boost/graph/dijkstra_shortest_paths.hpp>
17#include <boost/graph/named_function_params.hpp>
18#include <boost/graph/properties.hpp>
19#include <boost/property_map/property_map.hpp>
20
21#include <Eigen/Geometry>
22
23#include <range/v3/algorithm/sort.hpp>
24#include <range/v3/range/conversion.hpp>
25#include <range/v3/view/filter.hpp>
26#include <range/v3/view/reverse.hpp>
27
28#include <SimoxUtility/algorithm/string/string_tools.h>
29#include <VirtualRobot/Robot.h> // IWYU pragma: keep
30
41
62
63#include <SemanticObjectRelations/Shapes/Shape.h>
64
66{
67
73
74 bool
76 {
77 if (subdivision.empty())
78 {
79 // no subdivision
80 return hasLocalPlanner;
81 }
82
83 ARMARX_CHECK(hasLocalPlanner); // we always need local planner for segment subdivision
84 const bool segmentLocalPlanner = subdivision[currentSegment].useLocalPlanner;
85
86 return segmentLocalPlanner;
87 }
88
91 {
92 if (subdivision.empty())
93 {
94 // no subdivision
95 return plan.trajectory;
96 }
98 }
99
102 {
103 if (subdivision.empty())
104 {
105 // no subdivision
106 return plan.trajectory;
107 }
109 }
110
111 Navigator::Navigator(const Config& config, const InjectedServices& services) :
112 config{config}, srv{services}
113 {
114 ARMARX_CHECK_NOT_NULL(srv.sceneProvider) << "The scene provider must be set!";
115 // ARMARX_CHECK_NOT_NULL(services.executor) << "The executor service must be set!";
116 ARMARX_CHECK_NOT_NULL(services.publisher) << "The publisher service must be set!";
117 }
118
120 {
121 ARMARX_INFO << "Navigator destructor";
122 stop();
123
124 stopAllThreads();
125 }
126
127 void
128 Navigator::setGraphEdgeCosts(core::Graph& graph) const
129 {
130 const auto cost = [](const core::Pose& p1, const core::Pose& p2)
131 {
132 const core::Pose diff = p1.inverse() * p2;
133
134 // FIXME consider rotation as well
135 return diff.translation().norm();
136 };
137
138 // graph -> trajectory (set costs)
139 for (auto edge : graph.edges())
140 {
141 const core::Pose start = resolveGraphVertex(edge.source());
142 const core::Pose goal = resolveGraphVertex(edge.target());
143
144 switch (edge.attrib().strategy)
145 {
147 {
148 ARMARX_VERBOSE << "Global planning from " << start.translation() << " to "
149 << goal.translation();
151
152 try
153 {
154 const auto globalPlan = config.stack.globalPlanner->plan(start, goal);
155 if (globalPlan.has_value())
156 {
157 ARMARX_CHECK(globalPlan->trajectory.isValid());
158
159 edge.attrib().trajectory = globalPlan->trajectory;
160 ARMARX_VERBOSE << "Free path length: "
161 << globalPlan->trajectory.length();
162 edge.attrib().cost() = globalPlan->trajectory.length();
163 }
164 else
165 {
166 ARMARX_VERBOSE << "Global planning failed for this edge.";
167 edge.attrib().cost() = std::numeric_limits<float>::max();
168 }
169 }
170 catch (...)
171 {
172 ARMARX_VERBOSE << "Global planning failed due to exception "
174 edge.attrib().cost() = std::numeric_limits<float>::max();
175 }
176
177
178 break;
179 }
181 {
182 edge.attrib().cost() = cost(start, goal);
183 break;
184 }
185 }
186
187 ARMARX_VERBOSE << "Edge cost: " << edge.attrib().cost();
188 }
189 }
190
191 void
192 Navigator::moveTo(const std::vector<core::Pose>& waypoints,
193 core::NavigationFrame navigationFrame)
194 {
195 ARMARX_INFO << "Received moveTo() request.";
196
197 std::vector<core::Pose> globalWaypoints;
198 switch (navigationFrame)
199 {
201 globalWaypoints = waypoints;
202 break;
204 globalWaypoints.reserve(waypoints.size());
205
206 ARMARX_CHECK(srv.sceneProvider->synchronize(Clock::Now(), true));
207
208 const core::Pose global_T_robot(srv.sceneProvider->scene().robot->getGlobalPose());
209 ARMARX_VERBOSE << "Initial robot pose: " << global_T_robot.matrix();
210
211 std::transform(std::begin(waypoints),
212 std::end(waypoints),
213 std::back_inserter(globalWaypoints),
214 [&](const core::Pose& p) { return global_T_robot * p; });
215 break;
216 }
217
219 moveToAbsolute(globalWaypoints);
220 }
221
222 void
223 Navigator::moveToAlternatives(const std::vector<core::TargetAlternative>& targets,
224 core::NavigationFrame navigationFrame)
225 {
226 ARMARX_INFO << "Received moveToAlternatives() request.";
227
228 std::vector<core::TargetAlternative> globalTargets;
229 switch (navigationFrame)
230 {
232 globalTargets = targets;
233 break;
235 globalTargets.reserve(targets.size());
236
237 ARMARX_CHECK(srv.sceneProvider->synchronize(Clock::Now(), true));
238
239 const core::Pose global_T_robot(srv.sceneProvider->scene().robot->getGlobalPose());
240 ARMARX_VERBOSE << "Initial robot pose: " << global_T_robot.matrix();
241
242 std::transform(std::begin(targets),
243 std::end(targets),
244 std::back_inserter(globalTargets),
245 [&](const core::TargetAlternative& p) {
247 .target = global_T_robot * p.target, .priority = p.priority};
248 });
249 break;
250 }
251
253 moveToAbsoluteAlternatives(globalTargets);
254 }
255
256 void
257 Navigator::update(const std::vector<core::Pose>& waypoints,
258 core::NavigationFrame navigationFrame)
259 {
260 ARMARX_INFO << "Received update() request.";
261
262 std::vector<core::Pose> globalWaypoints;
263 switch (navigationFrame)
264 {
266 globalWaypoints = waypoints;
267 break;
269 globalWaypoints.reserve(waypoints.size());
270 std::transform(
271 std::begin(waypoints),
272 std::end(waypoints),
273 std::back_inserter(globalWaypoints),
274 [&](const core::Pose& p)
275 { return core::Pose(srv.sceneProvider->scene().robot->getGlobalPose()) * p; });
276 break;
277 }
278
280 updateAbsolute(globalWaypoints);
281 }
282
283 using GraphPath = std::vector<semrel::ShapeID>;
284
287 const core::Graph::ConstVertex& startVertex,
288 const core::Graph::ConstVertex& goalVertex)
289 {
290 ARMARX_VERBOSE << "Graph consists of " << graph.numVertices() << " vertices and "
291 << graph.numEdges() << " edges.";
292
293 std::vector<core::Graph::VertexDescriptor> predecessors(graph.numVertices());
294 std::vector<int> d(num_vertices(graph));
295
296 // FIXME ARMARX_CHECK(graphBuilder.startVertex.has_value());
297
298 auto weightMap = boost::get(&core::EdgeAttribs::m_value, graph);
299
300 core::Graph::VertexDescriptor start = startVertex.descriptor();
301
302 auto predecessorMap = boost::make_iterator_property_map(
303 predecessors.begin(), boost::get(boost::vertex_index, graph));
304
305 auto params = boost::predecessor_map(predecessorMap)
306 .distance_map(boost::make_iterator_property_map(
307 d.begin(), boost::get(boost::vertex_index, graph)))
308 .weight_map(weightMap);
309
313
314 ARMARX_VERBOSE << "Edge weights:";
315 for (const auto& edge : graph.edges())
316 {
317 ARMARX_VERBOSE << edge.sourceObjectID() << " -> " << edge.targetObjectID() << ": "
318 << edge.attrib().m_value;
319 }
320
321 std::vector<core::Graph::EdgeDescriptor> edgesToBeRemoved;
322 for (const auto edge : graph.edges())
323 {
324 if (edge.attrib().m_value == std::numeric_limits<float>::max())
325 {
326 edgesToBeRemoved.push_back(edge.descriptor());
327 }
328 }
329
330 for (const auto edge : edgesToBeRemoved)
331 {
332 boost::remove_edge(edge, graph);
333 }
334 ARMARX_VERBOSE << "Edge weights after removal of inf edges:";
335
336 for (const auto& edge : graph.edges())
337 {
338 ARMARX_VERBOSE << edge.sourceObjectID() << " -> " << edge.targetObjectID() << ": "
339 << edge.attrib().m_value;
340 ARMARX_VERBOSE << "Edge: " << edge << "cost: " << edge.attrib().cost()
341 << ", type: " << static_cast<int>(edge.attrib().strategy)
342 << " , has traj " << edge.attrib().trajectory.has_value();
343 }
344
345 ARMARX_VERBOSE << "Searching shortest path from vertex with id `"
346 << graph.vertex(start).objectID() << "` to vertex `"
347 << graph.vertex(goalVertex.descriptor()).objectID() << "`";
348
349 boost::dijkstra_shortest_paths(graph, start, params);
350
351 // find shortest path
353
354 // WARNING: shortest path will be from goal to start first
355 GraphPath shortestPath;
356
357 core::Graph::VertexDescriptor currentVertex = goalVertex.descriptor();
358 while (currentVertex != startVertex.descriptor())
359 {
360 shortestPath.push_back(graph.vertex(currentVertex).objectID());
361
363
364 auto parent = predecessorMap[currentVertex];
365 ARMARX_VERBOSE << "Parent id: " << parent;
366
368
369 // find edge between parent and currentVertex
370 auto outEdges = graph.vertex(parent).outEdges();
371 ARMARX_VERBOSE << "Parent has " << std::distance(outEdges.begin(), outEdges.end())
372 << " out edges";
373
374 ARMARX_CHECK_GREATER(std::distance(outEdges.begin(), outEdges.end()), 0)
375 << "Cannot reach another vertex from vertex `"
376 << graph.vertex(parent).objectID(); // not empty
377
378 auto edgeIt = std::find_if(outEdges.begin(),
379 outEdges.end(),
380 [&currentVertex](const auto& edge) -> bool
381 { return edge.target().descriptor() == currentVertex; });
382
383 ARMARX_CHECK(edgeIt != outEdges.end());
384 // shortestPath.edges.push_back(edgeIt->descriptor());
385
386 currentVertex = parent;
387 }
388
389 shortestPath.push_back(startVertex.objectID());
390
391 // ARMARX_CHECK_EQUAL(shortestPath.vertices.size() - 1, shortestPath.edges.size());
392
394 // reverse the range => now it is from start to goal again
395 shortestPath = shortestPath | ranges::views::reverse | ranges::to_vector;
396 // shortestPath.edges = shortestPath.edges | ranges::views::reverse | ranges::to_vector;
397
399 return shortestPath;
400 }
401
403 Navigator::convertToTrajectory(const GraphPath& shortestPath, const core::Graph& graph) const
404 {
406
407 ARMARX_CHECK_GREATER_EQUAL(shortestPath.size(), 2)
408 << "At least start and goal vertices must be available";
409
410 // ARMARX_CHECK_EQUAL(shortestPath.size() - 1, shortestPath.edges.size());
411
412 std::vector<core::GlobalTrajectoryPoint> trajectoryPoints;
413
414 // TODO add the start
415 // trajectoryPoints.push_back(core::TrajectoryPoint{
416 // .waypoint = {.pose = graph.vertex(shortestPath.front()).attrib().getPose()},
417 // .velocity = 0.F});
418
419 ARMARX_VERBOSE << "Shortest path with " << shortestPath.size() << " vertices";
421
422 for (size_t i = 0; i < shortestPath.size() - 1; i++)
423 {
424 // ARMARX_CHECK(graph.hasEdge(shortestPath.edges.at(i).m_source,
425 // shortestPath.edges.at(i).m_target));
426
427 // TODO add method edge(shapeId, shapeId)
428 const core::Graph::ConstEdge edge =
429 graph.edge(graph.vertex(shortestPath.at(i)), graph.vertex(shortestPath.at(i + 1)));
430
432 ARMARX_VERBOSE << "Index " << i;
433 ARMARX_VERBOSE << static_cast<int>(edge.attrib().strategy);
434 ARMARX_VERBOSE << edge;
435 switch (edge.attrib().strategy)
436 {
438 {
439 ARMARX_INFO << "Free navigation on edge";
440
442 ARMARX_CHECK(edge.attrib().trajectory.has_value());
443
445 ARMARX_INFO << "Length: " << edge.attrib().trajectory->length();
446
447
449 ARMARX_INFO << "Free navigation with "
450 << edge.attrib().trajectory->points().size() << " waypoints";
451
452 // we have a trajectory
453 // FIXME trajectory points can be invalid => more points than expected. Why?
455 const std::vector<core::GlobalTrajectoryPoint> edgeTrajectoryPoints =
456 edge.attrib().trajectory->points();
457
458 // if trajectory is being initialized, include the start, otherwise skip it
459 const int offset = trajectoryPoints.empty() ? 0 : 1;
460
461 if (edgeTrajectoryPoints.size() > 2) // not only start and goal position
462 {
464 // append `edge trajectory` to `trajectory` (without start and goal points)
465 trajectoryPoints.insert(trajectoryPoints.end(),
466 edgeTrajectoryPoints.begin() + offset,
467 edgeTrajectoryPoints.end());
468 }
469
470
472 // clang-format off
473 // trajectoryPoints.push_back(
474 // core::TrajectoryPoint
475 // {
476 // .waypoint =
477 // {
478 // .pose = graph.vertex(shortestPath.at(i+1)).attrib().getPose()
479 // },
480 // .velocity = std::numeric_limits<float>::max()
481 // });
482 // clang-format on
483
484 break;
485 }
486
488 {
489 ARMARX_INFO << "Point2Point navigation on edge";
490
491 // FIXME variable
492 const float point2pointVelocity = 400;
493
494 const core::GlobalTrajectoryPoint currentTrajPt = {
495 .waypoint = {.pose = resolveGraphVertex(graph.vertex(shortestPath.at(i)))},
496 .velocity = point2pointVelocity};
497
498 const core::GlobalTrajectoryPoint nextTrajPt{
499 .waypoint = {.pose =
500 resolveGraphVertex(graph.vertex(shortestPath.at(i + 1)))},
501 .velocity = 0};
502
503 // resample event straight lines
504 // ARMARX_CHECK_NOT_EMPTY(trajectoryPoints);
505
506 // const core::Trajectory segmentTraj({trajectoryPoints.back(), nextTrajPt});
507 // ARMARX_INFO << "Segment trajectory length: " << segmentTraj.length();
508
509 // const auto resampledTrajectory = segmentTraj.resample(500); // FIXME param
510
511 // ARMARX_INFO << "Resampled trajectory contains "
512 // << resampledTrajectory.points().size() << " points";
513
514 // this is the same pose as the goal of the previous segment but with a different velocity
515 // FIXME MUST set velocity here.
516 // trajectoryPoints.push_back(
517 // core::TrajectoryPoint{.waypoint = trajectoryPoints.back().waypoint,
518 // .velocity = std::numeric_limits<float>::max()});
519
521 // trajectoryPoints.insert(trajectoryPoints.end(),
522 // resampledTrajectory.points().begin(),
523 // resampledTrajectory.points().end());
524 trajectoryPoints.push_back(currentTrajPt);
525 trajectoryPoints.push_back(nextTrajPt);
526
527 break;
528 }
529 default:
530 {
531 ARMARX_ERROR << "Boom.";
532 }
533 }
534 }
535
536 ARMARX_INFO << "Trajectory consists of " << trajectoryPoints.size() << " points";
537
538 for (const auto& pt : trajectoryPoints)
539 {
540 ARMARX_INFO << pt.waypoint.pose.translation();
541 }
542
543 return {trajectoryPoints};
544 }
545
547 Navigator::convertToGraph(const std::vector<client::WaypointTarget>& targets) const
548 {
549 //
550 GraphBuilder graphBuilder;
551 graphBuilder.initialize(core::Pose(srv.sceneProvider->scene().robot->getGlobalPose()));
552
553 // std::optional<Graph*> activeSubgraph;
554 for (const auto& target : targets)
555 {
556 ARMARX_INFO << "Adding target " << QUOTED(target) << " to graph";
557 // if the last location was on a subgraph, that we are about to leave
558 // const bool leavingSubgraph = [&]() -> bool
559 // {
560 // if (not activeSubgraph.has_value())
561 // {
562 // return false;
563 // }
564
565 // // if a user specifies a pose, then this pose is not meant to be part of a graph
566 // // -> can be reached directly
567 // if (target.pose.has_value())
568 // {
569 // return true;
570 // }
571
572 // if (not target.locationId->empty())
573 // {
574 // const auto& subgraph = core::getSubgraph(target.locationId.value(),srv.sceneProvider->scene().graph->subgraphs);
575 // return subgraph.name() != activeSubgraph;
576 // }
577
578 // throw LocalException("this line should not be reachable");
579 // }();
580
581 // if(leavingSubgraph)
582 // {
583 // const auto activeNodes = graph.activeVertices();
584 // ARMARX_CHECK(activeNodes.size() == 1);
585 // graph.connect(activeSubgraph->getRoutesFrom(activeNodes.front()));
586 // }
587
588
589 // if a user specifies a pose, then this pose is not meant to be part of a graph
590 // -> can be reached directly
591 if (target.pose.has_value())
592 {
593 graphBuilder.connect(target.pose.value(), target.strategy);
594 continue;
595 }
596
597 // if a user specified a vertex of a subgraph (aka location), then this vertex
598 // might not be reachable directly. It might be needed to navigate on the graph
599 // instead. Thus, we collect all routes to reach the node.
600 if (not target.locationId->empty())
601 {
602 const auto& subgraph = core::getSubgraph(
603 target.locationId.value(), srv.sceneProvider->scene().graph->subgraphs);
604
605 const auto vertex = core::getVertexByName(target.locationId.value(), subgraph);
606
607 ARMARX_INFO << "Vertex " << QUOTED(target.locationId.value()) << " resolved to "
608 << vertex.attrib().getPose();
609
610 const std::vector<core::GraphPath> routes = core::findPathsTo(vertex, subgraph);
611 // const auto routes = subgraph->getRoutesTo(target.locationId);
612
613 ARMARX_INFO << "Found " << routes.size() << " routes to location `"
614 << target.locationId.value();
615
616 ARMARX_CHECK(not routes.empty()) << "The location `" << target.locationId.value()
617 << "` is not a reachable vertex on the graph!";
618
619 // we now add all routes to the graph that take us to the desired location
620 graphBuilder.connect(routes,
621 target.strategy); // TODO: all routes to the same target
622
623 continue;
624 }
625
626 ARMARX_ERROR << "Either `location_id` or `pose` has to be provided!";
627 }
628
629 const auto goalVertex = graphBuilder.getGraph().vertex(graphBuilder.goalVertex());
630 ARMARX_INFO << "Goal vertex is " << QUOTED(goalVertex.attrib().getLocationName());
631
632 return graphBuilder;
633 }
634
636 Navigator::resolveGraphVertex(const core::Graph::ConstVertex& vertex) const
637 {
638 const auto goal = core::resolveLocation(srv.sceneProvider->scene().staticScene->objectMap,
639 srv.sceneProvider->scene().staticScene->objectInfo,
640 vertex.attrib().getPose());
641 ARMARX_CHECK(goal.pose.has_value())
642 << "The location of vertex " << vertex.attrib().getLocationName()
643 << " couldn't be resolved (" << goal.errorMsg << ")";
644 return goal.pose.value();
645 }
646
647 void
648 Navigator::setupRamping(core::GlobalTrajectory& trajectory, float startVelocity) const
649 {
650 std::vector<std::pair<std::size_t, float>> fixedVelocities;
651
652 if (config.stack.generalConfig.enableRampingStart)
653 {
654 fixedVelocities.emplace_back(0, startVelocity);
655 }
656 if (config.stack.generalConfig.enableRampingEnd)
657 {
658 fixedVelocities.emplace_back(trajectory.points().size() - 1,
659 config.stack.generalConfig.boundaryVelocity);
660 }
661 if (config.stack.generalConfig.enableRampingCorners)
662 {
663 const auto& corners =
664 trajectory.calculateCorners(config.stack.generalConfig.cornerLimit);
665
666 for (const auto& [idx, angle] : corners)
667 {
668 fixedVelocities.emplace_back(idx, config.stack.generalConfig.cornerVelocity);
669 }
670 }
671
672 trajectory.applyRamping(fixedVelocities, config.stack.generalConfig.rampLength);
673 }
674
675 void
676 Navigator::moveTo(const std::vector<client::WaypointTarget>& targets,
677 core::NavigationFrame navigationFrame)
678 {
679 // arlt: Is this function deprecated?
681 << "only absolute movement implemented atm.";
682
684
685 validate(targets);
686
687 ARMARX_CHECK_NOT_EMPTY(targets) << "At least the goal has to be provided!";
688 ARMARX_INFO << "Navigating to " << targets.back();
689
690 // update static scene including locations
691 updateScene(true);
692
693 auto graphBuilder = convertToGraph(targets);
694
696
697 core::Graph graph = graphBuilder.getGraph();
698 auto startVertex = graphBuilder.startVertex;
699 auto goalVertex = graphBuilder.getGraph().vertex(graphBuilder.goalVertex());
700
701 ARMARX_INFO << "Goal pose according to graph is " << graphBuilder.goalPose().matrix();
702
703 ARMARX_CHECK(startVertex.has_value());
704
706
707 goalReachedMonitor = std::nullopt;
708 goalReachedMonitor = GoalReachedMonitor(
709 graphBuilder.goalPose(), srv.sceneProvider->scene(), config.goalReachedConfig);
710
711 if (goalReachedMonitor->goalReached(false))
712 {
713 ARMARX_IMPORTANT << "Already at goal position "
714 << goalReachedMonitor->goal().translation().head<2>()
715 << ". Robot won't move.";
716
717 srv.publisher->goalReached(core::GoalReachedEvent{
719 core::Pose(srv.sceneProvider->scene().robot->getGlobalPose())});
720
721 return;
722 }
723
725 setGraphEdgeCosts(graph);
726
728
729 srv.introspector->onGlobalGraph(graph);
730
732 const auto shortestPath = findShortestPath(graph, startVertex.value(), goalVertex);
733
734 // print
736
737 std::vector<core::Pose> vertexPoses;
738 vertexPoses.emplace_back(srv.sceneProvider->scene().robot->getGlobalPose());
739
740 ARMARX_INFO << "Navigating along the following nodes:";
741 for (const semrel::ShapeID& vertex : shortestPath)
742 {
743 ARMARX_INFO << " - " << graph.vertex(vertex).attrib().getLocationName();
744 vertexPoses.push_back(resolveGraphVertex(graph.vertex(vertex)));
745 }
746
747 srv.introspector->onGlobalShortestPath(vertexPoses);
748
749
750 // convert graph / vertex chain to trajectory
752 core::GlobalTrajectory globalPlanTrajectory = convertToTrajectory(shortestPath, graph);
753
754 // globalPlanTrajectory.setMaxVelocity(1000); // FIXME
755
756 // move ...
757
758 // this is our `global plan`
760
761 globalPlan = global_planning::GlobalPlannerResult{.trajectory = globalPlanTrajectory,
762 .helperTrajectory = std::nullopt};
763
764 // the following is similar to moveToAbsolute
765 // TODO(fabian.reister): remove code duplication
766
767 srv.executor->execute(globalPlan->currentGlobalSegment());
768
770 srv.publisher->globalTrajectoryUpdated(core::GlobalTrajectoryUpdatedEvent{
771 {.timestamp = armarx::Clock::Now()}, globalPlan->currentGlobalSegment()});
772
774 srv.introspector->onGlobalPlannerResult(globalPlan->plan);
775
776 ARMARX_INFO << "Global planning completed. Will now start all required threads";
778
779 startStack();
780 }
781
782 void
783 Navigator::startStack()
784 {
785
787
788 ARMARX_INFO << "Starting stack.";
789
790 {
791 // ensure running task is never started and stopped simultaneously
792 const std::scoped_lock<std::mutex> lock{runningTaskMtx};
793
794 shouldRun = true;
795
796 // FIXME instead of PeriodicTask, use RunningTask.
797 if (not runningTask)
798 {
799 runningTask =
801 &Navigator::run,
803 false,
804 "PeriodicTask");
805 runningTask->start();
806 }
807 else if (not runningTask->isRunning())
808 {
809 runningTask->start();
810 }
811 }
812
813 // FIXME create separate function for this.
814 if (globalPlan->useLocalPlanner(hasLocalPlanner()))
815 {
816 if (srv.executor != nullptr)
817 {
819 }
820 }
821 else
822 {
823 if (srv.executor != nullptr)
824 {
826 }
827 }
828
829 // Could be required if pauseMovement() has been called in the past.
830 resume();
831 srv.publisher->movementStarted(core::MovementStartedEvent{
832 {.timestamp = armarx::Clock::Now()},
833 core::Pose(srv.sceneProvider->scene().robot->getGlobalPose())});
834 }
835
836 void
837 Navigator::moveToAbsolute(const std::vector<core::Pose>& waypoints, bool sceneUpdate)
838 {
840
841 // if this navigator is in use, stop the movement ...
842 pause();
843 // ... and all threads
844 stopAllThreads();
845
846 // FIXME remove
847 //std::this_thread::sleep_for(std::chrono::seconds(1));
848
850 ARMARX_CHECK_NOT_EMPTY(waypoints);
851
852 if (sceneUpdate)
853 {
854 updateScene(true);
855 }
856
857 ARMARX_INFO << "Request to move from " << srv.sceneProvider->scene().robot->getGlobalPose()
858 << " to " << waypoints.back().matrix();
859
860 // first we check if we are already at the goal position
861 goalReachedMonitor = std::nullopt;
862 goalReachedMonitor = GoalReachedMonitor(
863 waypoints.back(), srv.sceneProvider->scene(), config.goalReachedConfig);
864
865 if (goalReachedMonitor->goalReached(false))
866 {
867 ARMARX_INFO << "Already at goal position. Robot won't move.";
868 ARMARX_CHECK_NOT_NULL(srv.publisher);
869
870 ARMARX_VERBOSE << "Finalizing";
871 srv.publisher->goalReached(core::GoalReachedEvent{
873 core::Pose(srv.sceneProvider->scene().robot->getGlobalPose())});
874 ARMARX_VERBOSE << "Finalized";
875
876 return;
877 }
878
879 // global planner
880 ARMARX_INFO << "Planning global trajectory";
881 ARMARX_CHECK_NOT_NULL(config.stack.globalPlanner);
882 // TODO plan on multiple waypoints, ignoring waypoints for now
883 // idea: compute multiple global trajectories, one for each segment between waypoints.
884
885 srv.introspector->onGoal(waypoints.back());
886 globalPlan = config.stack.globalPlanner->plan(waypoints.back());
887
888 if (srv.drawer != nullptr)
889 {
890 srv.drawer->callGenericDrawFunction(
891 [&](viz::Client& client)
892 { config.stack.globalPlanner->visualizeDebugInfo(client); });
893 }
894 else
895 {
896 ARMARX_WARNING << "No drawer available. Cannot visualize global planner debug info.";
897 }
898
899
901
902 if (not globalPlan.has_value())
903 {
904 ARMARX_WARNING << "No global trajectory. Cannot move.";
905 srv.publisher->globalPlanningFailed(core::GlobalPlanningFailedEvent{
906 {.timestamp = armarx::Clock::Now()}, {"No global trajectory. Cannot move."}});
907
908 srv.introspector->failure();
909 return;
910 }
911
912 // create subdivision and start first segment
913 setupGlobalPlanSubvidision();
914
915 startGlobalPathSegment(false, false);
916 }
917
918 void
919 Navigator::updateAbsolute(const std::vector<core::Pose>& waypoints)
920 {
922 ARMARX_CHECK_NOT_EMPTY(waypoints);
923
924 // Assume nothing in static obstacles changed -> also don't update costmap
925 updateScene(false);
926
927 // global planner
928 ARMARX_INFO << "Planning global trajectory";
929 ARMARX_CHECK_NOT_NULL(config.stack.globalPlanner);
930 // TODO plan on multiple waypoints, ignoring waypoints for now
931 // idea: compute multiple global trajectories, one for each segment between waypoints.
932
933 srv.introspector->onGoal(waypoints.back());
934 globalPlan = config.stack.globalPlanner->plan(waypoints.back());
935
937
938 if (not globalPlan.has_value())
939 {
940 ARMARX_WARNING << "No global trajectory. Cannot move.";
941 srv.publisher->globalPlanningFailed(core::GlobalPlanningFailedEvent{
942 {.timestamp = armarx::Clock::Now()}, {"No global trajectory. Cannot move."}});
943
944 srv.introspector->failure();
945 return;
946 }
947
948 // create subdivision and start first segment
949 setupGlobalPlanSubvidision();
950
951 startGlobalPathSegment(false, true);
952 }
953
954 void
955 Navigator::moveToAbsoluteAlternatives(const std::vector<core::TargetAlternative>& targets)
956 {
958
959 // if this navigator is in use, stop the movement ...
960 pause();
961 // ... and all threads
962 stopAllThreads();
963
965 ARMARX_CHECK_NOT_EMPTY(targets);
966
967 updateScene(true);
968
969 ARMARX_INFO << "Request to move from " << srv.sceneProvider->scene().robot->getGlobalPose()
970 << " to " << targets.size() << " alternatives";
971
972 lastAllAlternativesImpossible = std::nullopt;
973
974 // first, check whether we are already at any target
975 for (const auto& t : targets)
976 {
977 GoalReachedMonitor monitor(
978 t.target, srv.sceneProvider->scene(), config.goalReachedConfig);
979
980 if (monitor.goalReached(false))
981 {
982 ARMARX_INFO << "Already at a possible goal position. Robot won't move.";
983
984 srv.publisher->goalReached(core::GoalReachedEvent{
986 core::Pose(srv.sceneProvider->scene().robot->getGlobalPose())});
987
988 return;
989 }
990 }
991
992 // sort alternatives by their priority (in descending order)
993 targetAlternatives = targets;
994 ranges::sort(targetAlternatives, std::greater{}, &core::TargetAlternative::priority);
995
996 if (setupTargetAlternatives())
997 {
998 // create subdivision and start first segment
999 setupGlobalPlanSubvidision();
1000
1001 startGlobalPathSegment(false, false);
1002 }
1003 }
1004
1005 bool
1006 Navigator::setupTargetAlternatives()
1007 {
1008 ARMARX_CHECK_NOT_EMPTY(targetAlternatives);
1009
1010 globalPlan = std::nullopt;
1011
1012 // Spfa planner plans into the "whole room", i.e. by executing the algorithm with
1013 // a single start position, a path to any goal can be constructed.
1014 // Thus, we only use alternatives with the spfa planner to avoid repeatedly replanning the
1015 // global path.
1016 const auto spfaPlanner =
1017 std::dynamic_pointer_cast<global_planning::SPFA>(config.stack.globalPlanner);
1018 if (spfaPlanner != nullptr)
1019 {
1020 const core::Pose start(srv.sceneProvider->scene().robot->getGlobalPose());
1021 // expensive spfa planning only once for all alternatives
1022 const auto planningResult = spfaPlanner->executePlanner(start);
1023
1024 for (const auto& target : targetAlternatives)
1025 {
1026 // cheap path reconstruction for every alternative
1027 const auto path = spfaPlanner->calculatePath(planningResult, target.target);
1028 if (path.has_value() and verifyGlobalPathPossible(path->trajectory))
1029 {
1030 globalPlan = path;
1031 srv.introspector->onGoal(target.target);
1032 goalReachedMonitor = std::nullopt;
1033 goalReachedMonitor = GoalReachedMonitor(
1034 target.target, srv.sceneProvider->scene(), config.goalReachedConfig);
1035
1036 ARMARX_INFO << "Valid path found for target " << target.target.matrix()
1037 << " with priority " << target.priority;
1038 break;
1039 }
1040 else
1041 {
1042 ARMARX_VERBOSE << "Target with priority " << target.priority
1043 << " not reachable";
1044 }
1045 }
1046 }
1047 else
1048 {
1049 const auto goal = targetAlternatives.front().target;
1051 << "Current global planner is not an SPFA planner, discarding alternatives!";
1052
1053 globalPlan = config.stack.globalPlanner->plan(goal);
1054 srv.introspector->onGoal(goal);
1055 goalReachedMonitor = std::nullopt;
1056 goalReachedMonitor =
1057 GoalReachedMonitor(goal, srv.sceneProvider->scene(), config.goalReachedConfig);
1058
1059 // we only use target alternatives with the spfa planner
1060 targetAlternatives.clear();
1061 }
1062
1063 if (srv.drawer != nullptr)
1064 {
1065 srv.drawer->callGenericDrawFunction(
1066 [&](viz::Client& client)
1067 { config.stack.globalPlanner->visualizeDebugInfo(client); });
1068 }
1069 else
1070 {
1071 ARMARX_WARNING << "No drawer available. Cannot visualize global planner debug info.";
1072 }
1073
1074
1076
1077 if (not globalPlan.has_value())
1078 {
1079 ARMARX_WARNING << "No global trajectory. Cannot move.";
1080 srv.publisher->globalPlanningFailed(core::GlobalPlanningFailedEvent{
1081 {.timestamp = armarx::Clock::Now()}, {"No global trajectory. Cannot move."}});
1082
1083 srv.introspector->failure();
1084 return false;
1085 }
1086
1087 return true;
1088 }
1089
1090 void
1091 Navigator::setupGlobalPlanSubvidision()
1092 {
1093 ARMARX_CHECK(globalPlan.has_value());
1094 ARMARX_CHECK(globalPlan->subdivision.empty());
1095 const auto& globalPlanPoses = globalPlan->plan.trajectory.poses();
1096
1098 if (not globalPlan->plan.trajectory.points().empty())
1099 {
1100 ARMARX_INFO << "Trajectory final pose " << globalPlanPoses.back().matrix();
1101
1102 goalReachedMonitor->updateGoal(globalPlanPoses.back());
1103 }
1104 srv.publisher->globalTrajectoryUpdated(core::GlobalTrajectoryUpdatedEvent{
1105 {.timestamp = armarx::Clock::Now()}, globalPlan->plan.trajectory});
1106
1107
1108 ARMARX_INFO << "Global planning completed.";
1110 // only enable subdivision if local planner is actually enabled
1111 if ((not config.general.subdivision.enable) or (not hasLocalPlanner()) or
1112 globalPlan->plan.trajectory.points().empty())
1113 {
1114 // startGlobalPathSegment will hand trajectory over to introspection
1115 // after ramping has been performed; when subdivision is enabled
1116 // the introspection will receive the full subdivision (see below)
1117
1118 return;
1119 }
1120
1121 ARMARX_CHECK(hasLocalPlanner());
1122 ARMARX_INFO << "Starting subdividing global path.";
1123
1124 ARMARX_CHECK(srv.sceneProvider->scene().staticScene.has_value());
1126 srv.sceneProvider->scene().staticScene->distanceToObstaclesCostmap.has_value());
1127 const auto& costmap = srv.sceneProvider->scene().staticScene->distanceToObstaclesCostmap;
1128
1129 std::vector<bool> localPlanEligibility;
1130 localPlanEligibility.reserve(globalPlan->plan.trajectory.points().size());
1131 for (const auto& pose : globalPlanPoses)
1132 {
1133 Eigen::Vector2f pt = conv::to2D(pose.translation());
1134 localPlanEligibility.push_back(costmap->value(pt).value_or(0) >
1135 config.general.subdivision.localPlannerCostmapThreshold);
1136 }
1137
1138 // expand every sequence of false values in localPlanEligibility
1139 bool falseSequence = false;
1140 const int n = localPlanEligibility.size();
1141 const float expansion = config.general.subdivision.globalPlanExpansionDistance;
1142 if (expansion > 0)
1143 {
1144 for (int i = 0; i < n;)
1145 {
1146 if (falseSequence)
1147 {
1148 if (localPlanEligibility[i])
1149 {
1150 // false sequence ended, expand it appropriately to the back
1151 falseSequence = false;
1152
1153 // we expand until we have a distance >= expansion
1154 float totalDistance = 0;
1155 // false sequence ended -> there has to be an element before the current one
1157 for (; (totalDistance < expansion) and (i < n); i++)
1158 {
1159 localPlanEligibility[i] = false;
1160 totalDistance += (globalPlanPoses[i - 1].translation() -
1161 globalPlanPoses[i].translation())
1162 .norm();
1163 }
1164 continue; // we already incremented i
1165 }
1166
1167 // otherwise continue current false sequence
1168 }
1169 else
1170 {
1171 if (not localPlanEligibility[i])
1172 {
1173 // false sequence started, expand it appropriately to the front
1174 falseSequence = true;
1175
1176 // we expand until we have a distance >= expansion
1177 float totalDistance = 0;
1178 for (int j = i - 1; (totalDistance < expansion) and (j >= 0); j--)
1179 {
1180 localPlanEligibility[j] = false;
1181 totalDistance += (globalPlanPoses[j].translation() -
1182 globalPlanPoses[j + 1].translation())
1183 .norm();
1184 }
1185 }
1186
1187 // otherwise continue current true sequence
1188 }
1189
1190 i++;
1191 }
1192 }
1193
1195 // identify each consecutive true/false sequence and create subdivision
1196 // also check that each localPlanner segment is long enough
1197
1198 GlobalPathSubdivision::Subdivision segment;
1199 segment.s = 0;
1200 segment.useLocalPlanner = localPlanEligibility[0];
1201
1202 // returns true, iff the segment should be terminated and appended to the list of subdivisions
1203 const auto checkSegmentLength = [&]()
1204 {
1205 // only check the length if the current segment uses the local planner
1206 if (not segment.useLocalPlanner)
1207 {
1208 return true;
1209 }
1210
1211 const auto& subTrajectory =
1212 globalPlan->plan.trajectory.getSubTrajectory(segment.s, segment.t);
1213 const float segmentLength = subTrajectory.length();
1214 if (segmentLength >= config.general.subdivision.minSegmentDistance)
1215 {
1216 return true; // segment is long enough
1217 }
1218
1219 if (globalPlan->subdivision.empty() and static_cast<int>(segment.t) >= n)
1220 {
1221 // segment is too short but the only segment -> valid
1222 ARMARX_VERBOSE << "Segment " << globalPlan->subdivision.size() << ": [" << segment.s
1223 << ", " << segment.t
1224 << "); is the only segment. length=" << segmentLength;
1225 return true;
1226 }
1227
1228 ARMARX_VERBOSE << "Segment " << globalPlan->subdivision.size() << ": [" << segment.s
1229 << ", " << segment.t
1230 << "); was too short for local planner: " << segmentLength;
1231
1232 if (globalPlan->subdivision.empty())
1233 {
1234 // this is the first (but not the only) segment, join it to the next one
1235 segment.useLocalPlanner = false;
1237 << "First segment, convert to global planner segment and grow it further.";
1238 }
1239 else
1240 {
1241 if (static_cast<int>(segment.t) < n)
1242 {
1243 // there is a preceding segment, remove it and continue to grow it
1244 segment = globalPlan->subdivision.back();
1245 globalPlan->subdivision.pop_back();
1246 ARMARX_VERBOSE << "Removing preceding segment and grow it further.";
1247 }
1248 else
1249 {
1250 // this is the last segment, expand the preceding segment to include this one
1251 globalPlan->subdivision.back().t = segment.t;
1252 ARMARX_VERBOSE << "Last segment; extent previous segment to the end.";
1253 }
1254 }
1255 return false;
1256 };
1257
1258 for (int i = 0; i < n; i++)
1259 {
1260 // continue the current sequence until its end
1261 while (i < n and localPlanEligibility[i] == segment.useLocalPlanner)
1262 {
1263 i++;
1264 }
1265
1266 segment.t = i;
1267 if (checkSegmentLength())
1268 {
1269 globalPlan->subdivision.emplace_back(segment);
1270 ARMARX_VERBOSE << "Segment " << globalPlan->subdivision.size() << ": [" << segment.s
1271 << ", " << segment.t
1272 << "); localPlanner=" << segment.useLocalPlanner;
1273
1274 // start the next sequence
1275 segment.s = i;
1276 segment.useLocalPlanner = localPlanEligibility[i];
1277 }
1278 }
1279
1281
1282 // check subdivision is valid
1283 std::size_t lastSegmentEnd = 0;
1284 for (const auto& segment : globalPlan->subdivision)
1285 {
1286 ARMARX_CHECK_EQUAL(lastSegmentEnd, segment.s);
1287 const std::ptrdiff_t segmentLength = segment.t - segment.s;
1288 ARMARX_CHECK_POSITIVE(segmentLength);
1289 lastSegmentEnd = segment.t;
1290 }
1291 ARMARX_CHECK_EQUAL(lastSegmentEnd, globalPlan->plan.trajectory.points().size());
1292
1293 globalPlan->currentSegment = 0;
1294 srv.introspector->onGlobalPlannerSubdivision(globalPlan.value());
1295
1296 ARMARX_INFO << "Divided global path into " << globalPlan->subdivision.size() << " segments";
1297 }
1298
1299 bool
1300 Navigator::startGlobalPathSegment(bool incrementSegment, bool rampFromCurrentVelocity)
1301 {
1302 if (not globalPlan->subdivision.empty())
1303 {
1304 // subdivision enabled
1305
1306 if (incrementSegment)
1307 {
1308 globalPlan->currentSegment++;
1309 }
1310
1311 if (globalPlan->currentSegment >= globalPlan->subdivision.size())
1312 {
1313 // global path already finished
1314 return true;
1315 }
1316
1317 ARMARX_INFO << "Starting segment " << globalPlan->currentSegment;
1318 const auto& currentSegment = globalPlan->subdivision[globalPlan->currentSegment];
1319
1320 globalPlan->currentSegmentTrajectory =
1321 globalPlan->plan.trajectory.getSubTrajectory(currentSegment.s, currentSegment.t);
1322
1323 setupRamping(globalPlan->currentGlobalSegment(),
1324 rampFromCurrentVelocity
1325 ? srv.sceneProvider->scene().platformVelocity.linear.norm()
1326 : config.stack.generalConfig.boundaryVelocity);
1327
1328 goalReachedMonitor->updateGoal(
1329 globalPlan->currentGlobalSegment().points().back().waypoint.pose);
1330 }
1331 else
1332 {
1333 if (incrementSegment)
1334 {
1335 return true;
1336 }
1337
1338 setupRamping(globalPlan->currentGlobalSegment(),
1339 rampFromCurrentVelocity
1340 ? srv.sceneProvider->scene().platformVelocity.linear.norm()
1341 : config.stack.generalConfig.boundaryVelocity);
1342
1343 goalReachedMonitor->updateGoal(
1344 globalPlan->currentGlobalSegment().points().back().waypoint.pose);
1345
1346 // only visualize global trajectory once ramping has been performed
1347 srv.introspector->onGlobalPlannerResult(globalPlan->plan);
1348 }
1349
1350 if (globalPlan->useLocalPlanner(hasLocalPlanner()))
1351 {
1352 const auto localPlannerResult = updateLocalPlanner();
1353 updateExecutor(localPlannerResult);
1354 updateIntrospector(localPlannerResult);
1355 }
1356 else
1357 {
1358 updateExecutor(globalPlan->currentGlobalSegment());
1359 }
1360
1361 ARMARX_INFO << "Start executing global plan segment (local planner="
1362 << globalPlan->useLocalPlanner(hasLocalPlanner())
1363 << "). Will now start all required threads.";
1365
1366 startStack();
1367 ARMARX_INFO << "Movement started.";
1368
1369 return false;
1370 }
1371
1372 void
1374 {
1375 }
1376
1377 void
1379 const std::optional<std::string>& providerName)
1380 {
1381 // update static scene including locations
1382 updateScene(true);
1383
1384 const auto resolveLocation =
1385 [&](const std::string& location,
1386 const std::optional<std::string>& providerName) -> std::vector<core::Location>
1387 {
1388 const auto locations = srv.sceneProvider->scene().staticScene->locations;
1389
1390 ARMARX_VERBOSE << "Available locations";
1391 for (const auto& location : locations)
1392 {
1393 ARMARX_VERBOSE << QUOTED(location.name) << " from provider "
1394 << QUOTED(location.provider);
1395 }
1396
1397 const auto matchingLocs =
1398 core::util::findMatchingLocations(locations, location, providerName);
1399
1400 ARMARX_CHECK_NOT_EMPTY(matchingLocs)
1401 << "Unknown location " << QUOTED(location) << " and provider "
1402 << QUOTED(providerName.value_or("~unset~")) << ".";
1403 return matchingLocs;
1404 };
1405
1406 const auto split = simox::alg::split(location, ":");
1407 ARMARX_CHECK(0 < split.size() && split.size() <= 2)
1408 << "The given location does not match the format <location>(:<instance-id>)? '"
1409 << location << "'.";
1410
1411 std::string instanceID;
1412 if (split.size() == 2)
1413 {
1414 // An instance-id was given in the location
1415 instanceID = split.back();
1416 }
1417
1418 const auto matchingLocations = resolveLocation(split.front(), providerName);
1419
1420
1421 if (matchingLocations.empty())
1422 {
1423 ARMARX_WARNING << "Failed to resolve location " << QUOTED(location) << " from provider "
1424 << QUOTED(providerName.value_or("~unset~"));
1425 }
1426
1427 if (matchingLocations.size() > 1)
1428 {
1429 ARMARX_WARNING << "Found more than one matching location for " << QUOTED(location)
1430 << " from provider " << QUOTED(providerName.value_or("~unset~"));
1431 for (const auto& location : matchingLocations)
1432 {
1433 ARMARX_INFO << "- " << QUOTED(location.provider) << ": " << QUOTED(location.name);
1434 }
1435 }
1436
1437
1438 const auto goal = core::resolveLocation(srv.sceneProvider->scene().staticScene->objectMap,
1439 srv.sceneProvider->scene().staticScene->objectInfo,
1440 matchingLocations.front().framedPose,
1441 instanceID);
1442
1443 if (goal.pose.has_value())
1444 {
1445 moveToAbsolute({goal.pose.value()}, false);
1446 }
1447 else
1448 {
1449 ARMARX_ERROR << goal.errorMsg;
1450 }
1451 }
1452
1453 void
1454 Navigator::moveTowardsAbsolute(const core::Direction& direction)
1455 {
1456 }
1457
1458 void
1459 Navigator::run()
1460 {
1461 // Ensure only one thread can call this method at a time (This is not ensured by the PeriodicTask).
1462 // Given the lock in updateLocalPlanner, this is currently not fixing any bugs on its own,
1463 // however, running this function from different threads simultaneously does not provide any benefits
1464 // and could lead to further bugs.
1465 const std::scoped_lock<std::mutex> lock{runMtx};
1466
1467 if (not shouldRun)
1468 {
1470 << "Called Navigator::run() although shouldRun is false. Directly returning.";
1471 return;
1472 }
1473
1474 // TODO(fabian.reister): add debug observer logging
1475
1476 // scene update
1477 {
1478 ARMARX_DEBUG << "Updating scene";
1479
1480 // TODO: remove full update and only update costmap when required!!!
1481 // this will slow down the navigation loop significantly (>1s)
1482 const Duration duration = armarx::core::time::StopWatch::measure(
1483 [&]() { updateScene(true /* false (only dynamic and costmap)*/); });
1484
1486 << "Scene update: " << duration.toMilliSecondsDouble() << "ms.";
1487
1488 srv.debugObserverHelper->setDebugObserverDatafield("scene update [ms]",
1489 duration.toMilliSecondsDouble());
1490 }
1491
1492 // verify global path is still possible
1493 {
1494 // if we have target alternatives and the path is no longer valid, attempt to
1495 // navigate to a valid alternative
1496 if (globalPlan.has_value() and not targetAlternatives.empty())
1497 {
1498 checkGlobalPathAlternatives();
1499 }
1500 }
1501
1502 // eventually, draw
1503 if ((srv.introspector != nullptr) and (srv.sceneProvider != nullptr) and
1504 srv.sceneProvider->scene().robot)
1505 {
1506 ARMARX_DEBUG << "Drawing robot pose";
1507 srv.introspector->onRobotPose(
1508 core::Pose(srv.sceneProvider->scene().robot->getGlobalPose()));
1509 }
1510
1511 // global planner update if goal has changed
1512 // niklas: symbol globalPlanningRequest is not used, so this code is dead
1513 /*{
1514 std::lock_guard g{globalPlanningRequestMtx};
1515
1516 if (globalPlanningRequest.has_value())
1517 {
1518 const auto& waypoints = globalPlanningRequest.value();
1519
1520 // recreate goal monitor
1521 {
1522 // first we check if we are already at the goal position
1523 goalReachedMonitor = std::nullopt;
1524 goalReachedMonitor = GoalReachedMonitor(
1525 waypoints.back(), srv.sceneProvider->scene(), config.goalReachedConfig);
1526
1527 if (goalReachedMonitor->goalReached(false))
1528 {
1529 ARMARX_INFO << "Already at goal position. Robot won't move.";
1530
1531 srv.publisher->goalReached(core::GoalReachedEvent{
1532 {armarx::Clock::Now()},
1533 core::Pose(srv.sceneProvider->scene().robot->getGlobalPose())});
1534
1535 return;
1536 }
1537 }
1538
1539 // global planning
1540 {
1541 // global planner
1542 ARMARX_INFO << "Update/Planning global trajectory";
1543 ARMARX_CHECK_NOT_NULL(config.stack.globalPlanner);
1544 // TODO plan on multiple waypoints, ignoring waypoints for now
1545 // idea: compute multiple global trajectories, one for each segment between waypoints.
1546
1547 srv.introspector->onGoal(waypoints.back());
1548 globalPlan = config.stack.globalPlanner->plan(waypoints.back());
1549
1550 ARMARX_TRACE;
1551
1552 if (not globalPlan.has_value())
1553 {
1554 ARMARX_WARNING << "No global trajectory. Cannot move.";
1555 srv.publisher->globalPlanningFailed(core::GlobalPlanningFailedEvent{
1556 {.timestamp = armarx::Clock::Now()}, {""}});
1557
1558 srv.introspector->failure();
1559 return;
1560 }
1561
1562 ARMARX_TRACE;
1563 srv.publisher->globalTrajectoryUpdated(globalPlan.value());
1564 srv.introspector->onGlobalPlannerResult(globalPlan.value());
1565
1566 if (not hasLocalPlanner())
1567 {
1568 updateExecutor(globalPlan.value());
1569 }
1570 }
1571 }
1572 }*/
1573
1574 // local planner update
1575 {
1576 ARMARX_VERBOSE << "Local planner update";
1577
1579 [&]()
1580 {
1581 if (globalPlan->useLocalPlanner(hasLocalPlanner()))
1582 {
1583 const auto localPlannerResult = updateLocalPlanner();
1584 updateExecutor(localPlannerResult);
1585 updateIntrospector(localPlannerResult);
1586
1587 if (srv.executor != nullptr && localPlannerResult.has_value())
1588 {
1589 srv.executor->ensureIsActive(
1591 }
1592 }
1593 else if (srv.executor != nullptr)
1594 {
1595 srv.executor->ensureIsActive(
1597 }
1598 });
1600 << "Local planner update: " << duration.toMilliSecondsDouble() << "ms.";
1601
1602 srv.debugObserverHelper->setDebugObserverDatafield("local planner update [ms]",
1603 duration.toMilliSecondsDouble());
1604 }
1605
1606 // update velocity limits of underlying platform controller
1607 // - apply safety guard, if present
1608 // - ensure the limits of the currently active navigator are used
1609 {
1610 if (hasSafetyGuard())
1611 {
1612 ARMARX_VERBOSE << "Updating safety guard";
1613 const auto result = updateSafetyGuard();
1614 srv.debugObserverHelper->setDebugObserverDatafield("safety_guard.limit_linear",
1615 result.twistLimits.linear);
1616 srv.debugObserverHelper->setDebugObserverDatafield("safety_guard.limit_angular",
1617 result.twistLimits.angular);
1618
1619 if (srv.executor != nullptr)
1620 {
1621 srv.executor->updateVelocityLimits(result.twistLimits);
1622 }
1623 }
1624 else
1625 // apply the maximum velocity limit from the general config, in case a different navigator applied other limits in between
1626 {
1627 if (srv.executor != nullptr)
1628 {
1629 srv.executor->updateVelocityLimits(config.stack.generalConfig.maxVel);
1630 }
1631 }
1632 }
1633
1634 // request to scale the calculated velocity by the velocity factor
1635 {
1637 srv.debugObserverHelper->setDebugObserverDatafield("velocity_factor",
1638 velocityFactor.load());
1639
1640 // scaling is done relatively; for typical absolute values: see SPFA, 500mm/s 0.4 rad/s
1641 if (srv.executor != nullptr)
1642 {
1643 srv.executor->updateVelocityFactor(velocityFactor);
1644 }
1645 }
1646
1647 // monitor update
1648 {
1650 ARMARX_DEBUG << "Monitor update";
1651
1652 const Duration duration =
1653 armarx::core::time::StopWatch::measure([&]() { updateMonitor(); });
1654
1656 << "Monitor update: " << duration.toMilliSecondsDouble() << "ms.";
1657
1658 srv.debugObserverHelper->setDebugObserverDatafield("monitor update [ms]",
1659 duration.toMilliSecondsDouble());
1660 }
1661 }
1662
1663 safety_guard::SafetyGuardResult
1664 Navigator::updateSafetyGuard()
1665 {
1666 ARMARX_CHECK(hasSafetyGuard());
1667
1668 const core::Pose global_T_robot(srv.sceneProvider->scene().robot->getGlobalPose());
1669 const auto proj = globalPlan->currentGlobalSegment().getProjection(
1670 global_T_robot.translation(), core::VelocityInterpolation::LinearInterpolation);
1671 const Eigen::Vector3f global_V_movement = proj.wayPointAfter.waypoint.pose.translation() -
1672 proj.projection.waypoint.pose.translation();
1673
1674 return config.stack.safetyGuard->computeSafetyLimits(global_V_movement.head<2>());
1675 }
1676
1677 bool
1678 Navigator::hasSafetyGuard() const
1679 {
1680 return config.stack.safetyGuard != nullptr;
1681 }
1682
1683 bool
1684 Navigator::hasLocalPlanner() const noexcept
1685 {
1686 return config.stack.localPlanner != nullptr;
1687 }
1688
1689 void
1690 Navigator::updateScene(const bool fullUpdate)
1691 {
1692 ARMARX_CHECK_NOT_NULL(srv.sceneProvider);
1693 srv.sceneProvider->synchronize(armarx::Clock::Now(), fullUpdate);
1694 }
1695
1696 void
1697 Navigator::checkGlobalPathAlternatives()
1698 {
1699 ARMARX_CHECK_NOT_EMPTY(targetAlternatives);
1700
1701 const auto replanAlternatives = [&]()
1702 {
1703 lastAllAlternativesImpossible = std::nullopt;
1704
1705 // save current global plan to restore when no path is currently possible
1706 // i.e. if a person is standing in a doorway and no paths are possible anymore,
1707 // we restore the previous path and move along it until the safety guard stops the robot
1708 const auto previousGlobalPlan = globalPlan;
1709
1710 if (setupTargetAlternatives())
1711 {
1712 // an alternative path was found
1713 ARMARX_INFO << "An alternative path was found.";
1714 setupGlobalPlanSubvidision();
1715 }
1716 else
1717 {
1718 ARMARX_INFO << "No possible alternatives, restoring previous global path.";
1719 globalPlan = std::move(previousGlobalPlan);
1720 lastAllAlternativesImpossible = armarx::Clock::Now();
1721 }
1722
1723 // either start the new global path or continue on the old one
1724 startGlobalPathSegment(false, true);
1725 };
1726
1727 if (not verifyGlobalPathPossible(globalPlan->currentGlobalSegment()))
1728 {
1729 // global path is not possible -> either wait for timeout or check alternatives
1730
1731 if (lastAllAlternativesImpossible.has_value() and
1732 (armarx::Clock::Now() - lastAllAlternativesImpossible.value() <
1734 config.general.targetAlternativesFilterTimeSeconds)))
1735 {
1737 << "Current globalPlan invalid, waiting for timeout before replanning.";
1738 }
1739 else
1740 {
1741 ARMARX_INFO << "Global path no longer possible, trying other alternatives!";
1742
1743 replanAlternatives();
1744 }
1745 }
1746 else if (lastAllAlternativesImpossible.has_value())
1747 {
1748 // globalPlan was previously invalid but is valid now
1749 // -> recheck all possible alternatives and their priority
1751 << "Previously invalid global plan valid again -> replanning all alternatives";
1752
1753 replanAlternatives();
1754 }
1755 }
1756
1757 bool
1758 Navigator::verifyGlobalPathPossible(const core::GlobalTrajectory& plan)
1759 {
1760 const auto& costmap = srv.sceneProvider->scene().staticScene->distanceToObstaclesCostmap;
1761 ARMARX_CHECK(costmap.has_value());
1762
1763 for (const auto& pt : plan.points())
1764 {
1765 const auto& pose = pt.waypoint.pose;
1766 const Eigen::Vector2f position = conv::to2D(pose.translation());
1767 const auto vertex = costmap->toVertex(position);
1768 if (not costmap->isValid(vertex.index) or costmap->isInCollision(vertex.position))
1769 {
1770 return false;
1771 }
1772 }
1773
1774 return true;
1775 }
1776
1777 std::optional<local_planning::LocalPlannerResult>
1778 Navigator::updateLocalPlanner()
1779 {
1780 // We need to make sure the local planner is not simultaniously called from different threads
1781 const std::scoped_lock<std::mutex> lock{updateLocalPlannerMtx};
1782
1783 ARMARX_CHECK(hasLocalPlanner());
1784
1785 ARMARX_VERBOSE << "Updating local plan";
1786
1787 try
1788 {
1789 const auto& globalTrajectory = globalPlan->currentGlobalSegment();
1790 ARMARX_VERBOSE << globalTrajectory.points().size() << " points in global plan";
1791 localPlan = config.stack.localPlanner->plan(globalTrajectory);
1792 ARMARX_VERBOSE << "Local planning finished";
1793 if (localPlan.has_value())
1794 {
1795 srv.publisher->localTrajectoryUpdated(core::LocalTrajectoryUpdatedEvent{
1796 {.timestamp = armarx::Clock::Now()}, localPlan->trajectory});
1797 return localPlan;
1798 }
1799 // do not return here, so that localPlanningFailed event is published
1800 //return localPlan;
1801 }
1802 catch (...)
1803 {
1804 ARMARX_WARNING << "Failure in local planner: " << GetHandledExceptionString();
1805 srv.publisher->localPlanningFailed(core::LocalPlanningFailedEvent{
1806 {.timestamp = armarx::Clock::Now()}, {GetHandledExceptionString()}});
1807
1808 return std::nullopt;
1809 }
1810
1811 srv.publisher->localPlanningFailed(core::LocalPlanningFailedEvent{
1812 {.timestamp = armarx::Clock::Now()}, {"Unknown reason"}});
1813
1814 return std::nullopt;
1815 }
1816
1817 void
1818 Navigator::updateExecutor(const std::optional<local_planning::LocalPlannerResult>& localPlan)
1819 {
1820 if (srv.executor == nullptr)
1821 {
1822 return;
1823 }
1824
1825
1826 if (isPaused() or isStopped())
1827 {
1828 // [[unlikely]]
1829 ARMARX_VERBOSE << deactivateSpam(1) << "stopped or paused";
1830 return;
1831 }
1832
1833 if (not localPlan.has_value())
1834 {
1835 ARMARX_INFO << "Local plan is invalid!";
1836 ARMARX_CHECK_NOT_NULL(srv.executor);
1837 srv.executor->execute(core::LocalTrajectory{}, false);
1838 srv.executor->stop();
1839 return;
1840 }
1841
1842
1843 // const core::Rotation robot_R_world(srv.sceneProvider->scene().robot->getGlobalOrientation().inverse());
1844
1845 // ARMARX_VERBOSE
1846 // << deactivateSpam(100) << "Robot orientation "
1847 // << simox::math::mat3f_to_rpy(srv.sceneProvider->scene().robot->getGlobalOrientation()).z();
1848
1849 // core::Twist robotFrameVelocity;
1850
1851 // robotFrameVelocity.linear = robot_R_world * twist.linear;
1852 // // FIXME fix angular velocity
1853 // robotFrameVelocity.angular = twist.angular;
1854
1855 // ARMARX_VERBOSE << deactivateSpam(1) << "velocity in robot frame "
1856 // << robotFrameVelocity.linear;
1857
1858 ARMARX_CHECK_NOT_NULL(srv.executor);
1859 srv.executor->execute(localPlan->trajectory, true);
1860 }
1861
1862 void
1863 Navigator::updateExecutor(const core::GlobalTrajectory& globalTrajectory)
1864 {
1865 if (srv.executor == nullptr)
1866 {
1867 return;
1868 }
1869
1870 ARMARX_IMPORTANT << "Requested to execute global plan with "
1871 << globalTrajectory.points().size() << " points.";
1872
1873
1874 // if (isPaused() or isStopped())
1875 // {
1876 // // [[unlikely]]
1877 // ARMARX_VERBOSE << deactivateSpam(1) << "stopped or paused";
1878 // return;
1879 // }
1880
1881 if (globalTrajectory.points().empty())
1882 {
1883 ARMARX_INFO << "Global plan is invalid!";
1884 ARMARX_CHECK_NOT_NULL(srv.executor);
1885 srv.executor->stop();
1886 return;
1887 }
1888
1889 ARMARX_IMPORTANT << "Executing global plan with " << globalTrajectory.points().size()
1890 << " points.";
1891 ARMARX_CHECK_NOT_NULL(srv.executor);
1892 srv.executor->execute(globalTrajectory, false);
1893 }
1894
1895 void
1896 Navigator::updateIntrospector(
1897 const std::optional<local_planning::LocalPlannerResult>& localPlan)
1898 {
1899 ARMARX_CHECK_NOT_NULL(srv.introspector);
1900
1901 srv.introspector->onLocalPlannerResult(localPlan);
1902 }
1903
1904 void
1905 Navigator::updateMonitor()
1906 {
1908 //ARMARX_CHECK(goalReachedMonitor.has_value());
1909 if (not goalReachedMonitor.has_value())
1910 {
1911 // Note: I guess this can happen because of a race condition between the moveTo methods and the run method.
1912 // But I am not sure if a common mutex would work correctly.
1913 ARMARX_WARNING << "GoalReachedMonitor is not initialized.";
1914 return;
1915 }
1916
1917 const auto status = goalReachedMonitor->status();
1918 armarx::DebugObserverHelper* debugObserver = srv.debugObserverHelper;
1919
1920 if (debugObserver != nullptr)
1921 {
1922 debugObserver->setDebugObserverDatafield("goalPositionError", status.posError);
1923 debugObserver->setDebugObserverDatafield("goalOrientationError", status.oriError);
1924 debugObserver->setDebugObserverDatafield("goalPositionThreshold",
1925 goalReachedMonitor->getConfig().posTh);
1926 debugObserver->setDebugObserverDatafield("goalOrientationThreshold",
1927 goalReachedMonitor->getConfig().oriTh);
1928 }
1929
1930 if (not isPaused() and goalReachedMonitor->goalReached())
1931 {
1932 // [[unlikely]]
1933 ARMARX_INFO << "Current global path segment finished!";
1934
1935 // we finished the current segment -> start the new one
1936 if (startGlobalPathSegment(true, true))
1937 {
1938 // this segment was the last one, we are finished
1939 ARMARX_INFO << "Goal " << goalReachedMonitor->goal().translation().head<2>()
1940 << " reached!";
1941 stop();
1942
1943 srv.publisher->goalReached(core::GoalReachedEvent{
1945 {core::Pose(srv.sceneProvider->scene().robot->getGlobalPose())}});
1946
1947 srv.introspector->success();
1948 }
1949 }
1950 }
1951
1952 void
1953 Navigator::stopAllThreads()
1954 {
1955 const std::scoped_lock<std::mutex> lock{runningTaskMtx};
1956
1957 if (runningTask)
1958 {
1959 runningTask->stop();
1960 }
1961
1962 shouldRun = false;
1963 }
1964
1965 // bool
1966 // Navigator::isStackResultValid() const noexcept
1967 // {
1968
1969 // // global planner
1970 // if (config.stack.globalPlanner != nullptr)
1971 // {
1972 // if (not globalPlan.has_value())
1973 // {
1974 // ARMARX_VERBOSE << deactivateSpam(1) << "Global trajectory not yet set.";
1975 // return false;
1976 // }
1977 // }
1978
1979 // // local planner
1980 // if (config.stack.localPlanner != nullptr)
1981 // {
1982 // if (not localPlan.has_value())
1983 // {
1984 // ARMARX_VERBOSE << deactivateSpam(1) << "Local trajectory not yet set.";
1985 // return false;
1986 // }
1987 // }
1988
1989 // // [[likely]]
1990 // return true;
1991 // }
1992
1993 // const core::Trajectory& Navigator::currentTrajectory() const
1994 // {
1995 // ARMARX_CHECK(isStackResultValid());
1996
1997 // if(localPlan.has_value())
1998 // {
1999 // return localPlan->trajectory;
2000 // }
2001
2002 // return globalPlan->trajectory;
2003 // }
2004
2005 void
2006 Navigator::setVelocityFactor(const float velocityFactor)
2007 {
2008 ARMARX_CHECK_POSITIVE(velocityFactor)
2009 << "Scaling factor for velocity may not be negative, but is " << velocityFactor;
2010 ARMARX_CHECK_LESS_EQUAL(velocityFactor, 1)
2011 << "Scaling factor for velocity may not be > 1, but is " << velocityFactor;
2012
2013 this->velocityFactor = velocityFactor;
2014 }
2015
2016 void
2018 {
2019 ARMARX_INFO << "Paused.";
2020
2021 executorEnabled.store(false);
2022
2023 if (srv.executor != nullptr)
2024 {
2025 ARMARX_INFO << "Stopping executor.";
2026 srv.executor->stop();
2027 }
2028 }
2029
2030 void
2032 {
2033 ARMARX_INFO << "Resume.";
2034
2035 executorEnabled.store(true);
2036
2037 if (srv.executor != nullptr)
2038 {
2039 if (hasLocalPlanner())
2040 {
2042 }
2043 else
2044 {
2046 }
2047 }
2048 }
2049
2050 void
2052 {
2053 ARMARX_INFO << "Stopping.";
2054
2055 pause();
2056
2057 // stop all threads, including this one
2058 stopAllThreads();
2059
2060 goalReachedMonitor.reset();
2061 goalReachedMonitor = std::nullopt;
2062 }
2063
2064 bool
2065 Navigator::isPaused() const noexcept
2066 {
2067 return not executorEnabled.load();
2068 }
2069
2070 bool
2071 Navigator::isStopped() const noexcept
2072 {
2073 return (not shouldRun) or (not runningTask) or (not runningTask->isRunning());
2074 }
2075
2077 config{std::move(other.config)},
2078 srv{std::move(other.srv)},
2079 executorEnabled{other.executorEnabled.load()}
2080 {
2081 }
2082
2083
2084} // 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
void initialize(const core::Pose &pose)
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:181
#define ARMARX_IMPORTANT
The logging level for always important information, but expected behaviour (in contrast to ARMARX_WAR...
Definition Logging.h:190
#define ARMARX_ERROR
The logging level for unexpected behaviour, that must be fixed.
Definition Logging.h:196
#define ARMARX_DEBUG
The logging level for output that is only interesting while debugging.
Definition Logging.h:184
#define ARMARX_WARNING
The logging level for unexpected behaviour, but not a serious problem.
Definition Logging.h:193
#define ARMARX_VERBOSE
The logging level for verbose information.
Definition Logging.h:187
armarx::core::time::Duration Duration
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
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
const core::Graph & getSubgraph(const std::string &vertexName, const Graphs &graphs)
Definition Graph.cpp:234
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)
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
double angle(const Point &a, const Point &b, const Point &c)
Definition point.hpp:109
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
GlobalPathSubdivision(const global_planning::GlobalPlannerResult &globalPlan)
Definition Navigator.cpp:68
global_planning::GlobalPlannerResult plan
Definition Navigator.h:77
const core::GlobalTrajectory & currentGlobalSegment() const
Definition Navigator.cpp:90
global_planning::GlobalPlannerPtr globalPlanner
struct armarx::navigation::server::Navigator::Config::General::@344207070160307005105315017266073311106207125254 tasks
#define ARMARX_TRACE
Definition trace.h:77