diff --git a/data/RobotAPI/obstacle_avoidance/2d_scene_obstacles.json b/data/RobotAPI/obstacle_avoidance/2d_scene_obstacles.json index 2f86cf315a8612ceade3f1ddefecd3c1d9ee8b0e..caa034c2e9f9d21d6322f8e48c0483aa337eba42 100644 --- a/data/RobotAPI/obstacle_avoidance/2d_scene_obstacles.json +++ b/data/RobotAPI/obstacle_avoidance/2d_scene_obstacles.json @@ -77,11 +77,11 @@ "safetyMarginY": 400 },{ "name": "table_corner", - "posX": 3100, - "posY": 5216, + "posX": 3300, + "posY": 4916, "yaw": 0, "axisLengthX": 200, - "axisLengthY": 1000, + "axisLengthY": 500, "refPosX": 3400, "refPosY": 5216, "safetyMarginX": 500, diff --git a/source/RobotAPI/components/units/ObstacleAvoidingPlatformUnit/ObstacleAvoidingPlatformUnit.cpp b/source/RobotAPI/components/units/ObstacleAvoidingPlatformUnit/ObstacleAvoidingPlatformUnit.cpp index 950edbf74b1bdc1516614b7dfc8ac5c9a9598845..dcf0ebd846beb390d2bb1c29e511e14555e1cb42 100644 --- a/source/RobotAPI/components/units/ObstacleAvoidingPlatformUnit/ObstacleAvoidingPlatformUnit.cpp +++ b/source/RobotAPI/components/units/ObstacleAvoidingPlatformUnit/ObstacleAvoidingPlatformUnit.cpp @@ -26,15 +26,20 @@ // STD/STL #include <algorithm> +#include <cmath> #include <limits> #include <numeric> // Eigen +#include <Eigen/Core> #include <Eigen/Geometry> // Simox #include <SimoxUtility/math.h> +// ArmarX +#include <ArmarXCore/observers/variant/Variant.h> + namespace { @@ -62,6 +67,12 @@ namespace v.z() = std::numeric_limits<float>::infinity(); } + template<typename T> + void invalidate(std::deque<T>& d) + { + d.clear(); + } + std::string to_string(armarx::ObstacleAvoidingPlatformUnit::control_mode mode) @@ -85,16 +96,10 @@ const std::string armarx::ObstacleAvoidingPlatformUnit::default_name = "ObstacleAvoidingPlatformUnit"; -armarx::ObstacleAvoidingPlatformUnit::ObstacleAvoidingPlatformUnit() -{ - ; -} +armarx::ObstacleAvoidingPlatformUnit::ObstacleAvoidingPlatformUnit() = default; -armarx::ObstacleAvoidingPlatformUnit::~ObstacleAvoidingPlatformUnit() -{ - ; -} +armarx::ObstacleAvoidingPlatformUnit::~ObstacleAvoidingPlatformUnit() = default; void @@ -121,6 +126,7 @@ armarx::ObstacleAvoidingPlatformUnit::onStartPlatformUnit() invalidate(m_control_data.target_pos); invalidate(m_control_data.target_ori); invalidate(m_viz.start); + invalidate(m_control_data.vel_history); ARMARX_DEBUG << "Started " << getName() << "."; } @@ -154,13 +160,18 @@ armarx::ObstacleAvoidingPlatformUnit::moveTo( const float ori_reached_threshold, const Ice::Current&) { + using namespace simox::math; + std::scoped_lock l{m_control_data.mutex}; m_control_data.target_pos = Eigen::Vector2f{target_pos_x, target_pos_y}; - m_control_data.target_ori = target_ori; + m_control_data.target_ori = periodic_clamp<float>(target_ori, -M_PI, M_PI); m_control_data.pos_reached_threshold = pos_reached_threshold; m_control_data.ori_reached_threshold = ori_reached_threshold; + // clear the buffer to prevent any movement into arbitrary directions + invalidate(m_control_data.vel_history); + invalidate(m_control_data.target_vel); invalidate(m_control_data.target_rot_vel); @@ -295,15 +306,24 @@ armarx::ObstacleAvoidingPlatformUnit::high_level_control_loop() StringVariantBaseMap m; m["err_dist"] = new Variant{vels.err_dist}; m["err_angular_dist"] = new Variant{vels.err_angular_dist}; + m["target_global_x"] = new Variant{vels.target_global.x()}; m["target_global_y"] = new Variant{vels.target_global.y()}; + m["target_global_abs"] = new Variant{vels.target_global.norm()}; + m["target_local_x"] = new Variant{vels.target_local.x()}; m["target_local_y"] = new Variant{vels.target_local.y()}; + m["target_local_abs"] = new Variant(vels.target_local.norm()); m["target_rot"] = new Variant{vels.target_rot}; + m["modulated_global_x"] = new Variant{vels.modulated_global.x()}; m["modulated_global_y"] = new Variant{vels.modulated_global.y()}; + m["modulated_global_abs"] = new Variant{vels.modulated_global.norm()}; + m["modulated_local_x"] = new Variant{vels.modulated_local.x()}; m["modulated_local_y"] = new Variant{vels.modulated_local.y()}; + m["modulated_local_abs"] = new Variant{vels.modulated_local.norm()}; + setDebugObserverChannel("ObstacleAvoidingPlatformCtrl", m); m_platform->move(vels.modulated_local.x(), vels.modulated_local.y(), vels.target_rot); @@ -321,6 +341,9 @@ armarx::ObstacleAvoidingPlatformUnit::high_level_control_loop() ARMARX_ERROR << "Unknown error occured while running control loop."; } + // clear the buffer such that zero-velocity is set without delay + invalidate(m_control_data.vel_history); + m_platform->move(0, 0, 0); m_platform->stopPlatform(); m_control_loop.mode = control_mode::none; @@ -529,14 +552,47 @@ armarx::ObstacleAvoidingPlatformUnit::post_process_vel( // Smooth by calculating mean over the last `m_control_data.amount_smoothing` elements. const Eigen::Vector2f mean_modulated_vel = calculate_mean_modulated_vel(); + const bool is_near_target = this->is_near_target(mean_modulated_vel); + + // Min velocity is determined through distance to target. + float min_vel = is_near_target ? m_control_data.min_vel_near_target : m_control_data.min_vel_general; + if (target_vel.norm() < min_vel) + { + min_vel = target_vel.norm(); + } + // Determine max velocity by considering the coherency between the last // `m_control_data.amount_max_vel` pairs of desired and modulated velocities. - const float max_vel = calculate_adaptive_max_vel(); + // If the robot is almost at the goal position and no collision needs to be avoided + // (aka moving into the direction of the target position), we can prevent overshooting by + // clipping the velocity. The reference velocity is computed by a standard P-controller. + const float max_vel = is_near_target ? target_vel.norm() : calculate_adaptive_max_vel(); ARMARX_CHECK(mean_modulated_vel.allFinite()); + ARMARX_CHECK(std::isfinite(min_vel)); ARMARX_CHECK(std::isfinite(max_vel)); - return simox::math::norm_max(mean_modulated_vel, max_vel); + return simox::math::norm_clamp(mean_modulated_vel, min_vel, max_vel); +} + + +bool armarx::ObstacleAvoidingPlatformUnit::is_near_target(const Eigen::Vector2f& control_velocity) const noexcept +{ + if (m_control_data.target_dist < m_control_data.pos_near_threshold) + { + const Eigen::Vector2f target_direction = m_control_data.target_pos - m_control_data.agent_pos; + const Eigen::Vector2f control_direction = control_velocity / control_velocity.norm(); + + const float sim = simox::math::cosine_similarity(target_direction, control_direction); + + // if almost pointing into same direction + if (sim > cos(M_PI_4f32)) + { + return true; + } + } + + return false; } @@ -586,6 +642,9 @@ const (max_buffer_size_dist - min_buffer_size_dist); const float b = min_buffer_size - (m * min_buffer_size_dist); + + + // Calculate buffer size and clamp value if need be. return static_cast<unsigned>(std::clamp(m * m_control_data.target_dist + b, min_buffer_size, max_buffer_size)); @@ -805,6 +864,13 @@ armarx::ObstacleAvoidingPlatformUnit::createPropertyDefinitions() "Adaptive max vel exponent. This throttles the max_vel adaptively " "depending on the angle between target velocity and modulated velocity. " "0 = disable."); + def->optional(m_control_data.min_vel_near_target, "min_vel_near_target", "Velocity in [mm/s] " + "the robot should at least set when near the target"); + def->optional(m_control_data.min_vel_general, "min_vel_general", "Velocity in [mm/s] the robot " + "should at least set on general"); + def->optional(m_control_data.pos_near_threshold, "pos_near_threshold", "Distance in [mm] after " + "which the robot is considered to be near the target for min velocity, " + "smoothing, etc."); // Control parameters. def->optional(m_control_data.kp, "control.pos.kp"); diff --git a/source/RobotAPI/components/units/ObstacleAvoidingPlatformUnit/ObstacleAvoidingPlatformUnit.h b/source/RobotAPI/components/units/ObstacleAvoidingPlatformUnit/ObstacleAvoidingPlatformUnit.h index 2fa54737a94c647df0d3234873d194e567d40c07..1535fdc74c809fd43518095997a51e64aa31cc23 100644 --- a/source/RobotAPI/components/units/ObstacleAvoidingPlatformUnit/ObstacleAvoidingPlatformUnit.h +++ b/source/RobotAPI/components/units/ObstacleAvoidingPlatformUnit/ObstacleAvoidingPlatformUnit.h @@ -94,8 +94,11 @@ namespace armarx Eigen::Vector2f agent_pos; float agent_ori; double agent_safety_margin = 0; + float min_vel_near_target = 50; + float min_vel_general = 100; float max_vel = 200; float max_rot_vel = 0.3; + float pos_near_threshold = 250; float pos_reached_threshold = 8; float ori_reached_threshold = 0.1; float kp = 3.5; @@ -257,6 +260,11 @@ namespace armarx void visualize(const velocities& vels); + bool + is_near_target(const Eigen::Vector2f& control_velocity) + const + noexcept; + public: static const std::string default_name; diff --git a/source/RobotAPI/libraries/RobotStatechartHelpers/ObstacleAvoidingPlatformUnitHelper.cpp b/source/RobotAPI/libraries/RobotStatechartHelpers/ObstacleAvoidingPlatformUnitHelper.cpp index 03ed255120ccf8a5084ea824f7d8e2d790c1c485..0cbf7e730f56186618f92c0a74be2b5dc5ad0f5a 100644 --- a/source/RobotAPI/libraries/RobotStatechartHelpers/ObstacleAvoidingPlatformUnitHelper.cpp +++ b/source/RobotAPI/libraries/RobotStatechartHelpers/ObstacleAvoidingPlatformUnitHelper.cpp @@ -24,26 +24,28 @@ #include <RobotAPI/libraries/RobotStatechartHelpers/ObstacleAvoidingPlatformUnitHelper.h> -// STD/STL -#include <limits> - // Simox #include <SimoxUtility/math.h> +armarx::ObstacleAvoidingPlatformUnitHelper::ObstacleAvoidingPlatformUnitHelper( + armarx::PlatformUnitInterfacePrx platform_unit, + VirtualRobot::RobotPtr robot) : + ObstacleAvoidingPlatformUnitHelper{platform_unit, robot, Config{}} +{ + // pass +} + + armarx::ObstacleAvoidingPlatformUnitHelper::ObstacleAvoidingPlatformUnitHelper( armarx::PlatformUnitInterfacePrx platform_unit, VirtualRobot::RobotPtr robot, - const float pos_reached_threshold, - const float ori_reached_threshold) : + const Config& cfg) : m_platform_unit{platform_unit}, m_robot{robot}, - m_pos_reached_threshold{pos_reached_threshold}, - m_ori_reached_threshold{ori_reached_threshold} + m_cfg{cfg} { - const float inf = std::numeric_limits<float>::infinity(); - m_target.pos = Eigen::Vector2f{inf, inf}; - m_target.ori = inf; + // pass } @@ -58,36 +60,119 @@ armarx::ObstacleAvoidingPlatformUnitHelper::setTarget( const Eigen::Vector2f& target_pos, const float target_ori) { - m_target.pos = target_pos; - m_target.ori = target_ori; + setTarget(Target{target_pos, target_ori}); +} + + +void +armarx::ObstacleAvoidingPlatformUnitHelper::setTarget(const Target& target) +{ + m_waypoints.clear(); + m_waypoints.push_back(target); + m_current_waypoint_index = 0; + m_waypoint_changed = true; +} + + +armarx::ObstacleAvoidingPlatformUnitHelper::Target +armarx::ObstacleAvoidingPlatformUnitHelper::getCurrentTarget() +const +{ + return m_waypoints.at(m_current_waypoint_index); } void armarx::ObstacleAvoidingPlatformUnitHelper::update() { - m_platform_unit->moveTo(m_target.pos.x(), m_target.pos.y(), m_target.ori, - m_pos_reached_threshold, m_ori_reached_threshold); + // Skip to next waypoint if in proximity. + if (isCurrentTargetNear() and not isLastWaypoint()) + { + m_current_waypoint_index++; + m_waypoint_changed = true; + } + + // Only call `moveTo` on platform unit if the target actually changed, as `update` is designed + // to be called within a high-frequency loop. Prevents constantly invalidating the buffers in + // platform unit. + if (m_waypoint_changed) + { + // Use reached-thresholds regardless of whether this is the final target or just a waypoint. + // The near-thresholds are more unconstrained as the reached-thresholds, so this helper will + // change the position target before the unit will actually reach it, preventing slow downs + // or stops. + const float pos_thresh = m_cfg.pos_reached_threshold; + const float ori_thresh = m_cfg.ori_reached_threshold; + + // Control. + const Target target = getCurrentTarget(); + m_platform_unit->moveTo(target.pos.x(), target.pos.y(), target.ori, pos_thresh, ori_thresh); + + m_waypoint_changed = false; + } +} + + +void +armarx::ObstacleAvoidingPlatformUnitHelper::addWaypoint(const Eigen::Vector2f& waypoint_pos, float waypoint_ori) +{ + addWaypoint(Target{waypoint_pos, waypoint_ori}); +} + + +void +armarx::ObstacleAvoidingPlatformUnitHelper::setWaypoints(const std::vector<Target>& waypoints) +{ + m_waypoints = waypoints; + m_current_waypoint_index = 0; + m_waypoint_changed = true; +} + + +void +armarx::ObstacleAvoidingPlatformUnitHelper::addWaypoint(const Target& waypoint) +{ + m_waypoints.push_back(waypoint); } bool -armarx::ObstacleAvoidingPlatformUnitHelper::isFinalTargetReached() +armarx::ObstacleAvoidingPlatformUnitHelper::isLastWaypoint() const { - using namespace simox::math; + return m_current_waypoint_index + 1 >= m_waypoints.size(); +} - // Determine agent position. - const Eigen::Vector2f agent_pos = m_robot->getGlobalPosition().head<2>(); - const float agent_ori = - periodic_clamp<float>(mat4f_to_rpy(m_robot->getGlobalPose()).z(), -M_PI, M_PI); - // Determine distance and angular distance to goal position and orientation. - const float target_dist = (m_target.pos - agent_pos).norm(); - const float target_angular_dist = periodic_clamp<float>(m_target.ori - agent_ori, -M_PI, M_PI); +bool +armarx::ObstacleAvoidingPlatformUnitHelper::isCurrentTargetNear() +const +{ + return getPositionError() < m_cfg.pos_near_threshold and getOrientationError() < m_cfg.ori_near_threshold; +} + - return target_dist < m_pos_reached_threshold and - std::fabs(target_angular_dist) < m_ori_reached_threshold; +bool +armarx::ObstacleAvoidingPlatformUnitHelper::isCurrentTargetReached() +const +{ + return getPositionError() < m_cfg.pos_reached_threshold and getOrientationError() < m_cfg.ori_reached_threshold; +} + + +bool +armarx::ObstacleAvoidingPlatformUnitHelper::isFinalTargetNear() +const +{ + return isLastWaypoint() and isCurrentTargetNear(); +} + + +bool +armarx::ObstacleAvoidingPlatformUnitHelper::isFinalTargetReached() +const +{ + return isLastWaypoint() and isCurrentTargetReached(); } @@ -96,3 +181,24 @@ armarx::ObstacleAvoidingPlatformUnitHelper::setMaxVelocities(float max_vel, floa { m_platform_unit->setMaxVelocities(max_vel, max_angular_vel); } + + +float +armarx::ObstacleAvoidingPlatformUnitHelper::getPositionError() +const +{ + const Eigen::Vector2f agent_pos = m_robot->getGlobalPosition().head<2>(); + return (getCurrentTarget().pos - agent_pos).norm(); +} + + +float +armarx::ObstacleAvoidingPlatformUnitHelper::getOrientationError() +const +{ + using namespace simox::math; + + const float agent_ori = + periodic_clamp<float>(mat4f_to_rpy(m_robot->getGlobalPose()).z(), -M_PI, M_PI); + return std::fabs(periodic_clamp<float>(getCurrentTarget().ori - agent_ori, -M_PI, M_PI)); +} diff --git a/source/RobotAPI/libraries/RobotStatechartHelpers/ObstacleAvoidingPlatformUnitHelper.h b/source/RobotAPI/libraries/RobotStatechartHelpers/ObstacleAvoidingPlatformUnitHelper.h index 12d7100f997eeb818f6cf0aff989e42a92059df5..85d7543c956369817a0bb4df0c3dbda9fd22a576 100644 --- a/source/RobotAPI/libraries/RobotStatechartHelpers/ObstacleAvoidingPlatformUnitHelper.h +++ b/source/RobotAPI/libraries/RobotStatechartHelpers/ObstacleAvoidingPlatformUnitHelper.h @@ -42,11 +42,30 @@ namespace armarx public: + struct Config + { + float pos_reached_threshold = 10; // [mm] + float ori_reached_threshold = 0.1; // [rad] + float pos_near_threshold = 50; // [mm] + float ori_near_threshold = 0.2; // [rad] + }; + + struct Target + { + Eigen::Vector2f pos; + float ori; + }; + + public: + + ObstacleAvoidingPlatformUnitHelper( + armarx::PlatformUnitInterfacePrx platform_unit, + VirtualRobot::RobotPtr robot); + ObstacleAvoidingPlatformUnitHelper( armarx::PlatformUnitInterfacePrx platform_unit, VirtualRobot::RobotPtr robot, - float pos_reached_threshold, - float ori_reached_threshold); + const Config& cfg); virtual ~ObstacleAvoidingPlatformUnitHelper(); @@ -54,9 +73,40 @@ namespace armarx void setTarget(const Eigen::Vector2f& target_pos, float target_ori); + void + setTarget(const Target& target); + + Target + getCurrentTarget() const; + void update(); + void + setWaypoints(const std::vector<Target>& waypoints); + + void + addWaypoint(const Eigen::Vector2f& waypoint_pos, float waypoint_ori); + + void + addWaypoint(const Target& waypoint); + + bool + isLastWaypoint() + const; + + bool + isCurrentTargetNear() + const; + + bool + isCurrentTargetReached() + const; + + bool + isFinalTargetNear() + const; + bool isFinalTargetReached() const; @@ -64,22 +114,25 @@ namespace armarx void setMaxVelocities(float max_vel, float max_angular_vel); - private: + float + getPositionError() + const; - struct target - { - Eigen::Vector2f pos; - float ori; - }; + float + getOrientationError() + const; + + private: armarx::PlatformUnitInterfacePrx m_platform_unit; VirtualRobot::RobotPtr m_robot; - target m_target; + std::vector<Target> m_waypoints; + unsigned int m_current_waypoint_index = 0; + bool m_waypoint_changed = false; - float m_pos_reached_threshold; - float m_ori_reached_threshold; + Config m_cfg; };