Component.cpp
Go to the documentation of this file.
1/**
2 * This file is part of ArmarX.
3 *
4 * ArmarX is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 as
6 * published by the Free Software Foundation.
7 *
8 * ArmarX is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * @package navigation::ArmarXObjects::navigation_evaluator
17 * @date 2026
18 * @copyright http://www.gnu.org/licenses/gpl-2.0.txt
19 * GNU General Public License
20 */
21
22#include "Component.h"
23
24#include <cmath>
25#include <cstdint>
26#include <memory>
27#include <mutex>
28#include <optional>
29#include <string>
30#include <vector>
31
32#include <Eigen/Core>
33#include <Eigen/Geometry>
34
44
50
54
56{
57 const std::string Component::defaultName = "navigation_evaluator";
58
60 {
61 addPlugin(roomsReaderPlugin);
62
63 std::random_device rd;
64 randomGenerator.seed(rd());
65 }
66
69 {
72
73 def->component(manager, "SkillMemory");
74
75 def->optional(properties.roomsProviderName,
76 "p.roomsProviderName",
77 "Provider segment name for rooms in the navigation memory. "
78 "Leave empty to query all providers.");
79
80 def->optional(properties.skillProviderName,
81 "p.skillProviderName",
82 "Name of the skill provider that offers NavigateTo.");
83
84 def->optional(properties.failureTimeoutSeconds,
85 "p.failureTimeoutSeconds",
86 "Failures within this time [s] after skill start are tolerated; "
87 "later failures stop the evaluation.");
88
89 return def;
90 }
91
92 void
94 {
95 // Nothing special to initialize.
96 }
97
98 void
100 {
101 ARMARX_CHECK_NOT_NULL(manager) << "SkillMemory dependency is not configured.";
102
103 const armarx::skills::SkillID navigateToSkillId{
104 .providerId =
105 armarx::skills::ProviderID{.providerName = properties.skillProviderName},
107
108 try
109 {
110 navigateToSkillProxy =
111 std::make_unique<armarx::skills::SpecializedSkillProxy<skills::arondto::NavigateToParams>>(
112 manager, navigateToSkillId);
113 }
114 catch (const std::exception& e)
115 {
116 ARMARX_ERROR << "Failed to create NavigateTo skill proxy: " << e.what();
117 return;
118 }
119
122
123 evaluationTask = new RunningTask<Component>(this, &Component::run, "NavigationEvaluator");
124 evaluationTask->start();
125 }
126
127 void
129 {
130 stopRequested.store(true, std::memory_order_release);
131
132 if (evaluationTask)
133 {
134 evaluationTask->stop(true);
135 }
136
137 navigateToSkillProxy.reset();
138 }
139
140 void
144
145 std::string
147 {
148 return Component::defaultName;
149 }
150
151 std::string
153 {
154 return Component::defaultName;
155 }
156
157 void
159 {
160 using namespace armarx::RemoteGui::Client;
161
162 tab.refreshRoomsButton.setLabel("Refresh Rooms");
163 tab.roomSelector.setValue("");
164 tab.availableRoomsLabel.setText("Available rooms: <click Refresh>");
165 tab.algorithmSelector.setOptions({"SPFA", "AStar", "AStarWithOrientation", "Point2Point"});
166 tab.algorithmSelector.setValue("SPFA");
167 tab.startButton.setLabel("Start");
168 tab.stopButton.setLabel("Stop");
169 tab.successCounter.setText("Successful: 0");
170 tab.totalCounter.setText("Total: 0");
171 tab.failureCounter.setText("Failed: 0");
172 tab.statusLabel.setText("Status: stopped");
173
174 GridLayout grid;
175 int row = 0;
176
177 grid.add(Label("Room"), {row, 0}).add(tab.roomSelector, {row, 1});
178 ++row;
179 grid.add(tab.refreshRoomsButton, {row, 0}, {1, 2});
180 ++row;
181 grid.add(tab.availableRoomsLabel, {row, 0}, {1, 2});
182 ++row;
183
184 grid.add(Label("Algorithm"), {row, 0}).add(tab.algorithmSelector, {row, 1});
185 ++row;
186
187 grid.add(tab.startButton, {row, 0}).add(tab.stopButton, {row, 1});
188 ++row;
189
190 grid.add(tab.statusLabel, {row, 0}, {1, 2});
191 ++row;
192 grid.add(tab.successCounter, {row, 0}, {1, 2});
193 ++row;
194 grid.add(tab.totalCounter, {row, 0}, {1, 2});
195 ++row;
196 grid.add(tab.failureCounter, {row, 0}, {1, 2});
197 ++row;
198
199 VBoxLayout root = {grid, VSpacer()};
200 RemoteGui_createTab(getName(), root, &tab);
201 }
202
203 void
205 {
206 if (tab.refreshRoomsButton.wasClicked())
207 {
208 const auto rooms = queryRooms();
209 std::vector<std::string> roomNames;
210 for (const auto& room : rooms)
211 {
212 roomNames.push_back(room.name);
213 }
214
215 if (roomNames.empty())
216 {
217 tab.availableRoomsLabel.setText("Available rooms: <none>");
218 }
219 else
220 {
221 std::string joined;
222 for (std::size_t i = 0; i < roomNames.size(); ++i)
223 {
224 if (i > 0)
225 {
226 joined += ", ";
227 }
228 joined += roomNames[i];
229 }
230 tab.availableRoomsLabel.setText("Available rooms: " + joined);
231 }
232 }
233
234 if (tab.startButton.wasClicked() && not running.load(std::memory_order_acquire))
235 {
236 const std::string roomName = tab.roomSelector.getValue();
237 if (roomName.empty())
238 {
239 tab.statusLabel.setText("Status: enter a room name");
240 updateGuiCounters();
241 return;
242 }
243
244 const auto room = findRoom(roomName);
245 if (not room)
246 {
247 tab.statusLabel.setText("Status: room '" + roomName + "' not found");
248 updateGuiCounters();
249 return;
250 }
251
252 {
253 std::lock_guard g{settingsMutex};
254 selectedRoomName = roomName;
255 selectedAlgorithm = algorithmFromString(tab.algorithmSelector.getValue());
256 }
257
258 successCount.store(0, std::memory_order_release);
259 totalAttempts.store(0, std::memory_order_release);
260 failureCount.store(0, std::memory_order_release);
261 stopRequested.store(false, std::memory_order_release);
262 running.store(true, std::memory_order_release);
263
264 tab.statusLabel.setText("Status: running");
265 updateGuiCounters();
266 }
267
268 if (tab.stopButton.wasClicked() && running.load(std::memory_order_acquire))
269 {
270 stopRequested.store(true, std::memory_order_release);
271 }
272
273 if (not running.load(std::memory_order_acquire))
274 {
275 tab.statusLabel.setText("Status: stopped");
276 }
277
278 updateGuiCounters();
279 }
280
281 void
282 Component::updateGuiCounters()
283 {
284 tab.successCounter.setText("Successful: " + std::to_string(successCount.load(std::memory_order_acquire)));
285 tab.totalCounter.setText("Total: " + std::to_string(totalAttempts.load(std::memory_order_acquire)));
286 tab.failureCounter.setText("Failed: " + std::to_string(failureCount.load(std::memory_order_acquire)));
287 }
288
289 std::vector<algorithms::Room>
290 Component::queryRooms() const
291 {
292 ARMARX_CHECK_NOT_NULL(roomsReaderPlugin);
293
294 const std::optional<std::string> providerName =
295 properties.roomsProviderName.empty()
296 ? std::nullopt
297 : std::optional<std::string>{properties.roomsProviderName};
298
299 ARMARX_DEBUG << "Querying rooms from navigation memory"
300 << (providerName ? " for provider '" + *providerName + "'" : " (all providers)")
301 << ".";
302
304 .providerName = providerName,
305 .timestamp = armarx::Clock::Now()};
306
307 const auto result = roomsReaderPlugin->get().query(query);
308 if (not result)
309 {
310 if (providerName)
311 {
312 ARMARX_WARNING << "Failed to query rooms from provider '" << *providerName
313 << "': " << result.errorMessage;
314 }
315 else
316 {
317 ARMARX_WARNING << "Failed to query rooms from memory: " << result.errorMessage;
318 }
319 return {};
320 }
321
322 return result.rooms;
323 }
324
325 std::optional<algorithms::Room>
326 Component::findRoom(const std::string& name) const
327 {
328 const auto rooms = queryRooms();
329 for (const auto& room : rooms)
330 {
331 if (room.name == name)
332 {
333 return room;
334 }
335 }
336 return std::nullopt;
337 }
338
339 std::optional<Eigen::Isometry3f>
340 Component::samplePoseInRoom(const algorithms::Room& room) const
341 {
342 const auto [min, max] = room.aabb();
343
344 std::uniform_real_distribution<float> xDist(min.x(), max.x());
345 std::uniform_real_distribution<float> yDist(min.y(), max.y());
346 std::uniform_real_distribution<float> angleDist(-static_cast<float>(M_PI),
347 static_cast<float>(M_PI));
348
349 constexpr std::size_t maxSamples = 1000;
350 for (std::size_t i = 0; i < maxSamples; ++i)
351 {
352 const Eigen::Vector2f candidate(xDist(randomGenerator), yDist(randomGenerator));
353 if (room.isInside(candidate))
354 {
355 Eigen::Isometry3f pose = Eigen::Isometry3f::Identity();
356 pose.translation() << candidate.x(), candidate.y(), 0.0F;
357 pose.linear() = Eigen::AngleAxisf(angleDist(randomGenerator),
358 Eigen::Vector3f::UnitZ())
359 .toRotationMatrix();
360 return pose;
361 }
362 }
363
364 ARMARX_ERROR << "Could not sample a valid position inside room '" << room.name
365 << "' after " << maxSamples << " attempts.";
366 return std::nullopt;
367 }
368
369 skills::arondto::GlobalPlanningAlgorithm::ImplEnum
370 Component::algorithmFromString(const std::string& name) const
371 {
372 using ImplEnum = skills::arondto::GlobalPlanningAlgorithm::ImplEnum;
373 if (name == "AStar")
374 return ImplEnum::AStar;
375 if (name == "AStarWithOrientation")
376 return ImplEnum::AStarWithOrientation;
377 if (name == "Point2Point")
378 return ImplEnum::Point2Point;
379 return ImplEnum::SPFA;
380 }
381
382 std::string
383 Component::algorithmToString(skills::arondto::GlobalPlanningAlgorithm::ImplEnum algorithm) const
384 {
385 using ImplEnum = skills::arondto::GlobalPlanningAlgorithm::ImplEnum;
386 switch (algorithm)
387 {
388 case ImplEnum::AStar:
389 return "AStar";
390 case ImplEnum::AStarWithOrientation:
391 return "AStarWithOrientation";
392 case ImplEnum::Point2Point:
393 return "Point2Point";
394 case ImplEnum::SPFA:
395 return "SPFA";
396 default:
397 return "SPFA";
398 }
399 }
400
401 bool
402 Component::executeSingleNavigation(
403 const Eigen::Isometry3f& targetPose,
404 skills::arondto::GlobalPlanningAlgorithm::ImplEnum algorithm)
405 {
406 const auto startTime = armarx::Clock::Now();
407
408 skills::arondto::NavigateToParams params;
409 params.targetPose = targetPose.matrix();
410 params.navigatingSkillParams.globalPlanningAlgorithm = algorithm;
411 params.navigatingSkillParams.enableLocalPlanning = false;
412 params.navigatingSkillParams.enableSafetyGuard = true;
413
414 armarx::skills::TerminatedSkillStatusUpdate finalStatus;
415 try
416 {
417 finalStatus = navigateToSkillProxy->executeSkill(getName(), params);
418 }
419 catch (const std::exception& e)
420 {
421 ARMARX_ERROR << "Synchronous NavigateTo execution failed: " << e.what();
422 return tolerateFailure(armarx::Clock::Now() - startTime);
423 }
424
425 const auto elapsed = armarx::Clock::Now() - startTime;
426 const bool succeeded = finalStatus.status == armarx::skills::TerminatedSkillStatus::Succeeded;
427
428 if (succeeded)
429 {
430 ARMARX_INFO << "Navigation succeeded after " << elapsed.toSeconds() << " s.";
431 successCount.fetch_add(1, std::memory_order_acq_rel);
432 return true;
433 }
434
435 ARMARX_WARNING << "Navigation failed after " << elapsed.toSeconds()
436 << " s with status " << static_cast<int>(finalStatus.status);
437
438 return tolerateFailure(elapsed);
439 }
440
441 bool
442 Component::tolerateFailure(const armarx::Duration& elapsed)
443 {
444 failureCount.fetch_add(1, std::memory_order_acq_rel);
445
446 const bool tolerable =
447 elapsed < armarx::Duration::SecondsDouble(properties.failureTimeoutSeconds);
448
449 if (tolerable)
450 {
451 ARMARX_INFO << "Failure occurred within " << properties.failureTimeoutSeconds
452 << " s (elapsed " << elapsed.toSeconds()
453 << " s), continuing with next random location.";
454 return true;
455 }
456
457 ARMARX_ERROR << "Failure occurred after " << properties.failureTimeoutSeconds
458 << " s (elapsed " << elapsed.toSeconds() << " s), stopping evaluation.";
459 return false;
460 }
461
462 void
464 {
465 while (not evaluationTask->isStopped())
466 {
467 if (not running.load(std::memory_order_acquire))
468 {
470 continue;
471 }
472
473 std::string roomName;
474 skills::arondto::GlobalPlanningAlgorithm::ImplEnum algorithm;
475 {
476 std::lock_guard g{settingsMutex};
477 roomName = selectedRoomName;
478 algorithm = selectedAlgorithm;
479 }
480
481 const auto room = findRoom(roomName);
482 if (not room)
483 {
484 ARMARX_ERROR << "Room '" << roomName << "' not found in memory. Stopping.";
485 running.store(false, std::memory_order_release);
486 continue;
487 }
488
489 const auto targetPose = samplePoseInRoom(*room);
490 if (not targetPose)
491 {
492 ARMARX_ERROR << "Failed to sample a pose in room '" << roomName
493 << "'. Stopping.";
494 running.store(false, std::memory_order_release);
495 continue;
496 }
497
498 totalAttempts.fetch_add(1, std::memory_order_acq_rel);
499
500 const bool continueEvaluation = executeSingleNavigation(*targetPose, algorithm);
501 if (not continueEvaluation)
502 {
503 running.store(false, std::memory_order_release);
504 }
505 }
506 }
507
509
510} // namespace armarx::navigation::components::navigation_evaluator
int Label(int n[], int size, int *curLabel, MiscLib::Vector< std::pair< int, size_t > > *labels)
Definition Bitmap.cpp:801
#define ARMARX_REGISTER_COMPONENT_EXECUTABLE(ComponentT, applicationName)
Definition Decoupled.h:29
#define M_PI
Definition MathTools.h:17
static DateTime Now()
Current time on the virtual clock.
Definition Clock.cpp:93
static void WaitFor(const Duration &duration)
Wait for a certain duration on the virtual clock.
Definition Clock.cpp:99
Default component property definition container.
Definition Component.h:70
std::string getConfigIdentifier()
Retrieve config identifier for this component as set in constructor.
Definition Component.cpp:88
static Duration SecondsDouble(double seconds)
Constructs a duration in seconds.
Definition Duration.cpp:78
static Duration MilliSeconds(std::int64_t milliSeconds)
Constructs a duration in milliseconds.
Definition Duration.cpp:48
PluginT * addPlugin(const std::string prefix="", ParamsT &&... params)
std::string getName() const
Retrieve name of object.
std::int64_t toSeconds() const
Returns the amount of seconds.
Definition Duration.cpp:84
void onInitComponent() override
Pure virtual hook for the subclass.
Definition Component.cpp:93
armarx::PropertyDefinitionsPtr createPropertyDefinitions() override
Definition Component.cpp:68
void onConnectComponent() override
Pure virtual hook for the subclass.
Definition Component.cpp:99
std::string getDefaultName() const override
Retrieve default name of component.
#define ARMARX_CHECK_NOT_NULL(ptr)
This macro evaluates whether ptr is not null and if it turns out to be false it will throw an Express...
#define ARMARX_INFO
The normal logging level.
Definition Logging.h:179
#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
This file is part of ArmarX.
IceUtil::Handle< class PropertyDefinitionContainer > PropertyDefinitionsPtr
PropertyDefinitions smart pointer type.
std::vector< T > max(const std::vector< T > &v1, const std::vector< T > &v2)
std::vector< T > min(const std::vector< T > &v1, const std::vector< T > &v2)
void RemoteGui_createTab(std::string const &name, RemoteGui::Client::Widget const &rootWidget, RemoteGui::Client::Tab *tab)
GridLayout & add(Widget const &child, Pos pos, Span span=Span{1, 1})
Definition Widgets.cpp:438
void setText(std::string const &text)
Definition Widgets.cpp:40