8#include <Ice/Current.h>
9#include <IceUtil/Time.h>
11#include <SimoxUtility/math/convert/mat4f_to_rpy.h>
12#include <VirtualRobot/VirtualRobot.h>
18#include <ArmarXCore/interface/core/ManagedIceObjectDefinitions.h>
19#include <ArmarXCore/interface/observers/ObserverInterface.h>
20#include <ArmarXCore/interface/observers/VariantBase.h>
30#include <RobotAPI/interface/units/RobotUnit/NJointController.h>
31#include <RobotAPI/interface/visualization/DebugDrawerInterface.h>
33#include <armarx/control/interface/ConfigurableNJointControllerInterface.h>
35#include <armarx/navigation/platform_controller/aron/PlatformGlobalTrajectoryControllerConfig.aron.generated.h>
50 constexpr float rtRate = 1000.F;
51 constexpr float controlRate = 100.F;
54 yawOf(
const Eigen::Isometry3f& pose)
56 return std::atan2(pose.linear()(1, 0), pose.linear()(0, 0));
61 const NJointControllerConfigPtr& config,
68 ConfigPtrT cfg = ConfigPtrT::dynamicCast(config);
78 const std::string controlTargetName = robotUnit->getRobotPlatformName();
79 platformName_ = controlTargetName;
81 ARMARX_INFO <<
"Using control target " << controlTargetName;
82 auto* ct =
useControlTarget(controlTargetName, ControlModes::HolonomicPlatformVelocity);
88 <<
"The actuator " << controlTargetName <<
" has no control mode "
89 << ControlModes::HolonomicPlatformVelocity;
96 <<
"The sensor value for " << controlTargetName <<
" has no platform velocity";
101 const auto trajectoryFollowingControllerParams = configData.params;
104 configBuffer_updateConfigToAdditionalTask.reinitAllBuffers(configData);
105 configBuffer_updateConfigToOnPublish.reinitAllBuffers(configData);
106 configBuffer_updateConfigToDiagnostics.reinitAllBuffers(configData);
108 trajectoryFollowingController.emplace(trajectoryFollowingControllerParams);
110 rtMaxLinearAcceleration.store(trajectoryFollowingControllerParams.maxLinearAcceleration);
111 rtMaxAngularAcceleration.store(trajectoryFollowingControllerParams.maxAngularAcceleration);
117 isSimulation_ = robotUnit->isSimulation();
118 rtMaxSlewDeltaT = isSimulation_ ? 1.0F : 0.002F;
120 ARMARX_INFO <<
"Slew dt bound: " << rtMaxSlewDeltaT <<
" s ("
121 << (isSimulation_ ?
"simulation" :
"real robot") <<
").";
126 diagnosticsParams_ = configData.diagnostics;
128 if (diagnosticsParams_.enabled)
130 const auto capacity = [](
const float seconds,
const float rate) -> std::size_t
131 {
return static_cast<std::size_t
>(std::max(0.F, seconds) * rate); };
133 rtSamples_.init(capacity(diagnosticsParams_.rtDurationSeconds, rtRate));
134 controlSamples_.init(capacity(diagnosticsParams_.controlDurationSeconds, controlRate));
136 recordedTrajectories.reserve(
137 static_cast<std::size_t
>(std::max(0, diagnosticsParams_.maxTrajectoryRevisions)));
140 << rtSamples_.capacity() <<
" real-time and "
141 << controlSamples_.capacity() <<
" control samples, dumping to "
142 << diagnosticsParams_.outputDirectory
143 <<
" when a navigation request finishes.";
150 "`diagnostics.enabled` in the controller config this controller "
151 "was built from and restart the navigator to record a run.";
166 const IceUtil::Time& timeSinceLastIteration)
179 const float dt =
static_cast<float>(timeSinceLastIteration.toSecondsDouble());
185 constexpr float staleAfterSeconds = 0.1F;
187 const std::uint64_t sequence = additionalTaskSequence.load(std::memory_order_acquire);
189 if (sequence != rtLastSequence)
191 rtLastSequence = sequence;
192 rtSecondsSinceTargetUpdate = 0.F;
193 controlTargetStaleCycles.store(0, std::memory_order_relaxed);
197 rtSecondsSinceTargetUpdate += std::max(0.F,
dt);
200 const bool targetIsStale = rtSecondsSinceTargetUpdate > staleAfterSeconds;
205 controlTargetStaleCycles.fetch_add(1, std::memory_order_relaxed);
214 platformTarget->velocityX = rtCommandedTwist.linear.x();
215 platformTarget->velocityY = rtCommandedTwist.linear.y();
216 platformTarget->velocityRotation = rtCommandedTwist.angular;
219 const std::int64_t timestampUs = sensorValuesTimestamp.toMicroSeconds();
223 Eigen::Isometry3f global_T_robot;
224 global_T_robot.matrix() =
rtGetRobot()->getGlobalPose();
226 robotStateBuffer_rtToAdditionalTask.getWriteBuffer().global_T_robot = global_T_robot;
227 robotStateBuffer_rtToAdditionalTask.getWriteBuffer().timestampUs = timestampUs;
228 robotStateBuffer_rtToAdditionalTask.commitWrite();
233 if (rtSamples_.capacity() > 0)
241 sample.
episode = diagnosticsEpisode.load(std::memory_order_relaxed);
243 sample.
x = global_T_robot.translation().x();
244 sample.
y = global_T_robot.translation().y();
245 sample.
yaw = yawOf(global_T_robot);
246 sample.
vMeasX = platformSensor->velocityX;
247 sample.
vMeasY = platformSensor->velocityY;
248 sample.
wMeas = platformSensor->velocityRotation;
249 sample.
vTgtX = target.linear.x();
250 sample.
vTgtY = target.linear.y();
251 sample.
wTgt = target.angular;
252 sample.
vCmdX = rtCommandedTwist.linear.x();
253 sample.
vCmdY = rtCommandedTwist.linear.y();
254 sample.
wCmd = rtCommandedTwist.angular;
256 sample.
stale = targetIsStale;
259 rtSamples_.record(sample);
273 dt = std::max(0.F, std::min(
dt, rtMaxSlewDeltaT));
280 const float linearBound =
281 std::max(0.F, rtMaxLinearAcceleration.load(std::memory_order_relaxed) *
dt);
282 const float angularBound =
283 std::max(0.F, rtMaxAngularAcceleration.load(std::memory_order_relaxed) *
dt);
288 const Eigen::Vector2f linearDelta = target.linear - rtCommandedTwist.linear;
289 const float linearDistance = linearDelta.norm();
291 rtCommandedTwist.linear +=
292 linearDistance > linearBound
293 ? Eigen::Vector2f{linearDelta * (linearBound / linearDistance)}
298 const float angularDelta = target.angular - rtCommandedTwist.angular;
300 rtCommandedTwist.angular += std::max(-angularBound, std::min(angularDelta, angularBound));
304 rtSlewLimited = linearDistance > linearBound or std::abs(angularDelta) > angularBound;
309 const Ice::Current& iceCurrent)
315 rtMaxLinearAcceleration.store(
updateConfig.params.maxLinearAcceleration);
316 rtMaxAngularAcceleration.store(
updateConfig.params.maxAngularAcceleration);
318 configBuffer_updateConfigToAdditionalTask.getWriteBuffer() =
updateConfig;
319 configBuffer_updateConfigToAdditionalTask.commitWrite();
321 configBuffer_updateConfigToOnPublish.getWriteBuffer() =
updateConfig;
322 configBuffer_updateConfigToOnPublish.commitWrite();
324 configBuffer_updateConfigToDiagnostics.getWriteBuffer() =
updateConfig;
325 configBuffer_updateConfigToDiagnostics.commitWrite();
329 trajectoryRevision.fetch_add(1, std::memory_order_release);
335 if (diagnosticsParams_.enabled and
updateConfig.diagnostics.dumpRequest != lastDumpRequest)
338 diagnosticsDumpRequested.store(
true, std::memory_order_release);
348 ARMARX_CHECK(trajectoryFollowingController.has_value());
350 const auto& configBuffer =
351 configBuffer_updateConfigToAdditionalTask.getUpToDateReadBuffer();
355 if (configBuffer.targets.trajectory.points().empty())
359 filteredTwist.reset();
363 additionalTaskSequence.fetch_add(1, std::memory_order_release);
372 trajectoryFollowingController->updateParams(configBuffer.params);
376 trajectoryFollowingController->control(
377 configBuffer.targets.trajectory,
378 robotStateBuffer_rtToAdditionalTask.getUpToDateReadBuffer().global_T_robot);
382 const float alpha = configBuffer.params.alpha;
383 filteredTwist.linear =
384 alpha * filteredTwist.linear + (1. - alpha) * result.
twist.
linear.head<2>();
385 filteredTwist.angular =
386 alpha * filteredTwist.angular + (1. - alpha) * result.
twist.
angular.z();
396 additionalTaskSequence.fetch_add(1, std::memory_order_release);
399 targetBuffer_additionalTaskToOnPublish.getWriteBuffer().target = filteredTwist;
400 targetBuffer_additionalTaskToOnPublish.getWriteBuffer().dropPointVelocity =
402 targetBuffer_additionalTaskToOnPublish.getWriteBuffer().currentOrientation =
404 targetBuffer_additionalTaskToOnPublish.getWriteBuffer().desiredOrientation =
406 targetBuffer_additionalTaskToOnPublish.getWriteBuffer().orientationError =
408 targetBuffer_additionalTaskToOnPublish.getWriteBuffer().positionError =
410 targetBuffer_additionalTaskToOnPublish.getWriteBuffer().isFinalSegment =
412 targetBuffer_additionalTaskToOnPublish.getWriteBuffer().ffAngular = result.
ffAngular;
413 targetBuffer_additionalTaskToOnPublish.getWriteBuffer().cappedFfVel = result.
cappedFfVel;
414 targetBuffer_additionalTaskToOnPublish.getWriteBuffer().projectionIndex =
416 targetBuffer_additionalTaskToOnPublish.getWriteBuffer().angularFeedforwardSaturated =
418 targetBuffer_additionalTaskToOnPublish.getWriteBuffer().global_T_robot =
419 robotStateBuffer_rtToAdditionalTask.getReadBuffer().global_T_robot;
420 targetBuffer_additionalTaskToOnPublish.commitWrite();
423 configBuffer.params);
433 if (controlSamples_.capacity() == 0)
438 const RobotState& robotState = robotStateBuffer_rtToAdditionalTask.getReadBuffer();
443 const std::uint64_t revision = trajectoryRevision.load(std::memory_order_acquire);
447 const std::scoped_lock lock{diagnosticsTrajectoryMutex};
449 if (revision != lastRecordedTrajectoryRevision or recordedTrajectories.empty())
451 lastRecordedTrajectoryRevision = revision;
457 const std::size_t size =
trajectory.points().size();
458 const Eigen::Vector3f end =
460 ? Eigen::Vector3f{Eigen::Vector3f::Zero()}
461 : Eigen::Vector3f{
trajectory.points().back().waypoint.pose.translation()};
463 const bool unchanged = not recordedTrajectories.empty() and
464 size == lastRecordedTrajectorySize and
465 end == lastRecordedTrajectoryEnd;
470 lastRecordedTrajectoryRevision = recordedTrajectories.back().revision;
472 else if (recordedTrajectories.size() <
473 static_cast<std::size_t
>(
474 std::max(0, diagnosticsParams_.maxTrajectoryRevisions)))
476 recordedTrajectories.push_back({.revision = revision, .trajectory =
trajectory});
477 lastRecordedTrajectorySize = size;
478 lastRecordedTrajectoryEnd = end;
482 droppedTrajectoryRevisions++;
486 const std::uint64_t sampleRevision =
487 recordedTrajectories.empty() ? revision : recordedTrajectories.back().revision;
493 sample.
episode = diagnosticsEpisode.load(std::memory_order_relaxed);
494 sample.
sequence = additionalTaskSequence.load(std::memory_order_relaxed);
496 sample.
x = robotState.global_T_robot.translation().x();
497 sample.
y = robotState.global_T_robot.translation().y();
498 sample.
yaw = yawOf(robotState.global_T_robot);
499 sample.
refX = reference.translation().x();
500 sample.
refY = reference.translation().y();
501 sample.
refYaw = yawOf(reference);
512 sample.
guard =
static_cast<std::int32_t
>(result.
guard);
523 controlSamples_.record(sample);
535 const std::uint64_t episode = [&]
537 const std::scoped_lock lock{diagnosticsTrajectoryMutex};
539 const std::uint64_t current = diagnosticsEpisode.load(std::memory_order_acquire);
544 recordedTrajectories.clear();
545 lastRecordedTrajectoryRevision = 0;
546 lastRecordedTrajectorySize = 0;
547 lastRecordedTrajectoryEnd = Eigen::Vector3f::Zero();
548 droppedTrajectoryRevisions = 0;
553 diagnosticsEpisode.fetch_add(1, std::memory_order_release);
560 diagnosticsDumpedSinceActivation.store(
true, std::memory_order_release);
568 record.
params = configBuffer_updateConfigToDiagnostics.getUpToDateReadBuffer().params;
570 rtSamples_.snapshot(episode, record.
rtSamples);
575 record.
rtTruncated = rtSamples_.truncated(episode);
587 ARMARX_INFO <<
"Execution diagnostics: episode " << episode
588 <<
" recorded no samples, nothing written.";
604 const auto& debugStuff = targetBuffer_additionalTaskToOnPublish.getUpToDateReadBuffer();
605 const auto& config = configBuffer_updateConfigToOnPublish.getUpToDateReadBuffer();
608 datafields[
"vx"] =
new Variant(debugStuff.target.linear.x());
609 datafields[
"vy"] =
new Variant(debugStuff.target.linear.y());
610 datafields[
"v_linear"] =
new Variant(debugStuff.target.linear.norm());
611 datafields[
"vyaw"] =
new Variant(debugStuff.target.angular);
612 datafields[
"trajectory_points"] =
new Variant(config.targets.trajectory.points().size());
614 datafields[
"drop_point_velocity"] =
new Variant(debugStuff.dropPointVelocity);
616 datafields[
"orientationError"] =
new Variant(debugStuff.orientationError);
617 datafields[
"desiredOrientation"] =
new Variant(debugStuff.desiredOrientation);
618 datafields[
"currentOrientation"] =
new Variant(debugStuff.currentOrientation);
619 datafields[
"isFinalSegment"] =
new Variant(debugStuff.isFinalSegment);
621 datafields[
"positionError"] =
new Variant(debugStuff.positionError);
623 datafields[
"ffAngular"] =
new Variant(debugStuff.ffAngular);
624 datafields[
"cappedFfVel"] =
new Variant(debugStuff.cappedFfVel);
626 datafields[
"global_T_robot.x"] =
new Variant(debugStuff.global_T_robot.translation().x());
627 datafields[
"global_T_robot.y"] =
new Variant(debugStuff.global_T_robot.translation().y());
628 datafields[
"global_T_robot.o"] =
629 new Variant(simox::math::mat4f_to_rpy(debugStuff.global_T_robot.matrix()).z());
631 datafields[
"limitLinear"] =
new Variant(config.params.limits.linear);
632 datafields[
"limitAngular"] =
new Variant(config.params.limits.angular);
634 datafields[
"projectionIndex"] =
new Variant(
static_cast<int>(debugStuff.projectionIndex));
635 datafields[
"angularFeedforwardSaturated"] =
636 new Variant(debugStuff.angularFeedforwardSaturated);
638 const std::uint32_t staleCycles = controlTargetStaleCycles.load(std::memory_order_relaxed);
639 const std::uint64_t taskExceptions = controlTaskExceptions.load(std::memory_order_relaxed);
641 datafields[
"controlTargetStaleCycles"] =
new Variant(
static_cast<int>(staleCycles));
642 datafields[
"controlTaskExceptions"] =
new Variant(
static_cast<int>(taskExceptions));
646 if (diagnosticsParams_.enabled)
648 datafields[
"diagnosticsRtOverwritten"] =
649 new Variant(
static_cast<int>(rtSamples_.overwritten()));
650 datafields[
"diagnosticsControlOverwritten"] =
651 new Variant(
static_cast<int>(controlSamples_.overwritten()));
655 if (staleCycles > 0 and not publishedStaleWarning)
657 publishedStaleWarning =
true;
658 ARMARX_WARNING <<
"[nav-guard] control-target-stale: the 100 Hz control task stopped "
659 "publishing, so the platform is being ramped to a stop. "
660 <<
"Swallowed control-task exceptions so far: " << taskExceptions
661 <<
". The robot would previously have kept executing the last "
662 "commanded twist indefinitely.";
664 else if (staleCycles == 0)
666 publishedStaleWarning =
false;
669 debugObservers->setDebugChannel(
678 ARMARX_INFO <<
"PlatformGlobalTrajectoryController::onInitNJointController";
681 "PlatformGlobalTrajectoryControllerAdditionalTask",
687 <<
"Create a new thread alone PlatformGlobalTrajectoryController controller";
688 while (
getState() == eManagedIceObjectStarted)
702 catch (
const std::exception& e)
704 const std::uint64_t count =
705 controlTaskExceptions.fetch_add(1, std::memory_order_relaxed) + 1;
710 <<
"[nav-guard] control-task-exception: " << e.what()
711 <<
". The control cycle was skipped; the RT watchdog will "
712 "ramp the platform to a stop if this persists.";
717 <<
"[nav-guard] control-task-exception: " << count
718 <<
" so far, latest: " << e.what();
723 controlTaskExceptions.fetch_add(1, std::memory_order_relaxed);
725 <<
"[nav-guard] control-task-exception: non-standard "
726 "exception. The control cycle was skipped.";
729 c.waitForCycleDuration();
739 if (diagnosticsParams_.enabled)
741 runTask(
"PlatformGlobalTrajectoryControllerDiagnosticsTask",
747 while (
getState() == eManagedIceObjectStarted)
749 if (diagnosticsDumpRequested.exchange(
false, std::memory_order_acquire))
757 catch (
const std::exception& e)
765 "diagnostics: non-standard exception.";
769 c.waitForCycleDuration();
774 ARMARX_INFO <<
"PlatformGlobalTrajectoryController::onInitNJointController done.";
781 filteredTwist.reset();
786 rtCommandedTwist.linear = {platformSensor->velocityX, platformSensor->velocityY};
787 rtCommandedTwist.angular = platformSensor->velocityRotation;
789 robotStateBuffer_rtToAdditionalTask.getWriteBuffer().global_T_robot.matrix() =
791 robotStateBuffer_rtToAdditionalTask.commitWrite();
793 rtSlewLimited =
false;
797 diagnosticsEpisode.fetch_add(1, std::memory_order_release);
798 diagnosticsDumpedSinceActivation.store(
false, std::memory_order_release);
806 rtReady.store(
false);
817 if (diagnosticsParams_.enabled and
818 not diagnosticsDumpedSinceActivation.load(std::memory_order_acquire))
820 diagnosticsDumpRequested.store(
true, std::memory_order_release);
This util class helps with keeping a cycle time during a control cycle.
SpamFilterDataPtr deactivateSpam(float deactivationDurationSec=10.0f, const std::string &identifier="", bool deactivate=true) const
disables the logging for the current line for the given amount of seconds.
ArmarXObjectSchedulerPtr getObjectScheduler() const
int getState() const
Retrieve current state of the ManagedIceObject.
bool isControllerActive(const Ice::Current &=Ice::emptyCurrent) const final override
const SensorValueBase * useSensorValue(const std::string &sensorDeviceName) const
Get a const ptr to the given SensorDevice's SensorValue.
void runTask(const std::string &taskName, Task &&task)
Executes a given task in a separate thread from the Application ThreadPool.
const VirtualRobot::RobotPtr & useSynchronizedRtRobot(bool updateCollisionModel=false)
Requests a VirtualRobot for use in rtRun *.
std::string getInstanceName(const Ice::Current &=Ice::emptyCurrent) const final override
const VirtualRobot::RobotPtr & rtGetRobot()
TODO make protected and use attorneys.
ControlTargetBase * useControlTarget(const std::string &deviceName, const std::string &controlMode)
Declares to calculate the ControlTarget for the given ControlDevice in the given ControlMode when rtR...
const Target & rtGetControlStruct() const
void writeControlStruct()
bool rtUpdateControlStruct()
Target & getWriterControlStruct()
void reinitTripleBuffer(const Target &initial)
The Variant class is described here: Variants.
#define ARMARX_CHECK_EXPRESSION(expression)
This macro evaluates the expression and if it turns out to be false it will throw an ExpressionExcept...
#define ARMARX_CHECK(expression)
Shortcut for ARMARX_CHECK_EXPRESSION.
#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.
#define ARMARX_IMPORTANT
The logging level for always important information, but expected behaviour (in contrast to ARMARX_WAR...
#define ARMARX_ERROR
The logging level for unexpected behaviour, that must be fixed.
#define ARMARX_WARNING
The logging level for unexpected behaviour, but not a serious problem.
#define ARMARX_VERBOSE
The logging level for verbose information.
std::shared_ptr< class Robot > RobotPtr
::IceInternal::Handle< Dict > DictPtr
const simox::meta::EnumNames< ControllerType > ControllerTypeNames
@ PlatformGlobalTrajectory
::IceInternal::ProxyHandle<::IceProxy::armarx::DebugObserverInterface > DebugObserverInterfacePrx
std::map< std::string, VariantBasePtr > StringVariantBaseMap
void fromAron(const arondto::PackagePath &dto, PackageFileLocation &bo)
IceUtil::Handle< class RobotUnit > RobotUnitPtr
::IceInternal::ProxyHandle<::IceProxy::armarx::DebugDrawerInterface > DebugDrawerInterfacePrx
detail::ControlThreadOutputBufferEntry SensorAndControl
bool angularFeedforwardSaturated
True while the angular feed-forward is pinned at the limit, which leaves the orientation feedback no ...
core::GlobalTrajectoryPoint dropPoint
std::size_t projectionIndex
Index of the trajectory segment the controller is tracking.