From 518cd5adebb85b4ef2ac0d83487b7223791d3477 Mon Sep 17 00:00:00 2001
From: "Christian R. G. Dreher" <c.dreher@kit.edu>
Date: Thu, 30 Apr 2020 15:43:50 +0200
Subject: [PATCH] Implement an obstacle avoiding platform controller component.

---
 .../DSObstacleAvoidance/CMakeLists.txt        |   5 +-
 .../DSObstacleAvoidance/arviz_agent.h         | 184 ------
 .../RobotAPI/components/units/CMakeLists.txt  |   1 +
 .../CMakeLists.txt                            |  13 +
 .../ObstacleAvoidingPlatformUnit.cpp          | 619 ++++++++++++++++++
 .../ObstacleAvoidingPlatformUnit.h            | 246 +++++++
 .../ObstacleAvoidingPlatformUnit/main.cpp     |  40 ++
 7 files changed, 920 insertions(+), 188 deletions(-)
 delete mode 100644 source/RobotAPI/components/DSObstacleAvoidance/arviz_agent.h
 create mode 100644 source/RobotAPI/components/units/ObstacleAvoidingPlatformUnit/CMakeLists.txt
 create mode 100644 source/RobotAPI/components/units/ObstacleAvoidingPlatformUnit/ObstacleAvoidingPlatformUnit.cpp
 create mode 100644 source/RobotAPI/components/units/ObstacleAvoidingPlatformUnit/ObstacleAvoidingPlatformUnit.h
 create mode 100644 source/RobotAPI/components/units/ObstacleAvoidingPlatformUnit/main.cpp

diff --git a/source/RobotAPI/components/DSObstacleAvoidance/CMakeLists.txt b/source/RobotAPI/components/DSObstacleAvoidance/CMakeLists.txt
index a32de2577..ff53d0508 100644
--- a/source/RobotAPI/components/DSObstacleAvoidance/CMakeLists.txt
+++ b/source/RobotAPI/components/DSObstacleAvoidance/CMakeLists.txt
@@ -23,10 +23,7 @@ set(COMPONENT_LIBS
 )
 
 set(SOURCES DSObstacleAvoidance.cpp)
-set(HEADERS
-    DSObstacleAvoidance.h
-    arviz_agent.h
-)
+set(HEADERS DSObstacleAvoidance.h)
 
 armarx_add_component("${SOURCES}" "${HEADERS}")
 armarx_add_component_executable("main.cpp")
diff --git a/source/RobotAPI/components/DSObstacleAvoidance/arviz_agent.h b/source/RobotAPI/components/DSObstacleAvoidance/arviz_agent.h
deleted file mode 100644
index ddc8ad896..000000000
--- a/source/RobotAPI/components/DSObstacleAvoidance/arviz_agent.h
+++ /dev/null
@@ -1,184 +0,0 @@
-/*
- * This file is part of ArmarX.
- *
- * ArmarX is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- *
- * ArmarX is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- * @package    Armar6Skills::ArmarXObjects::ObstacleAvoidance
- * @author     Christian R. G. Dreher <c.dreher@kit.edu>
- * @date       2020
- * @copyright  http://www.gnu.org/licenses/gpl-2.0.txt
- *             GNU General Public License
- */
-
-
-#pragma once
-
-
-// STD/STL
-#include <string>
-
-// Eigen
-#include <Eigen/Geometry>
-
-// Simox
-#include <SimoxUtility/math/convert/pos_rpy_to_mat4f.h>
-
-// ArmarX
-#include <RobotAPI/interface/visualization/DebugDrawerInterface.h>
-#include <RobotAPI/libraries/core/Pose.h>
-#include <RobotAPI/components/ArViz/Client/Client.h>
-
-// RobotAPI
-#include <RobotAPI/interface/components/DSObstacleAvoidanceInterface.h>
-
-
-namespace armarx::viz
-{
-
-    class obstacle_avoidance
-    {
-
-    public:
-
-        obstacle_avoidance(const Eigen::Vector2f& start, const Eigen::Vector2f& goal) :
-            m_start{start(0), start(1), 0},
-            m_goal{goal(0), goal(1), 0}
-        {
-            ;
-        }
-
-        obstacle_avoidance(const Eigen::Vector3f& start, const Eigen::Vector3f& goal) :
-            m_start{start},
-            m_goal{goal}
-        {
-            ;
-        }
-
-        void update(
-            const obstacleavoidance::Agent& agent,
-            const Eigen::Vector2f& agent_modulated_vel)
-        {
-            update(agent, Eigen::Vector3f{agent_modulated_vel(0), agent_modulated_vel(1), 0});
-        }
-
-        void update(
-            const obstacleavoidance::Agent& agent,
-            const Eigen::Vector3f& agent_modulated_vel)
-        {
-            const float min_keypoint_dist = 50;
-            const Eigen::Vector3f& last_keypoint_pos = m_path.size() >= 1 ? m_path.back() : m_start;
-
-            // Keep a certain distance between path keypoints.
-            if ((last_keypoint_pos - agent.pos).norm() > min_keypoint_dist)
-            {
-                m_path.push_back(agent.pos);
-                m_redraw_path = true;
-            }
-
-            m_agent_desired_vel = agent.desired_vel;
-            m_agent_modulated_vel = agent_modulated_vel;
-            m_agent_safety_margin = agent.safety_margin;
-            m_agent_pos = agent.pos;
-        }
-
-        bool viz_progress(armarx::viz::Layer& layer)
-        {
-            using namespace armarx::viz;
-
-            bool is_redrawn = false;
-
-            if (m_redraw_path)
-            {
-                // Start.
-                layer.add(Sphere{"start"}
-                          .position(m_start)
-                          .color(Color::cyan(255, 64))
-                          .radius(40));
-
-                // Path.
-                for (unsigned i = 0; i < m_path.size(); ++i)
-                {
-                    layer.add(Sphere{"path_" + std::to_string(i + 1)}
-                              .position(m_path[i])
-                              .color(Color::magenta(255, 128))
-                              .radius(20));
-                }
-
-                // Goal.
-                layer.add(Arrow{"goal"}
-                          .fromTo(m_goal + Eigen::Vector3f{0, 0, 220},
-                                  m_goal + Eigen::Vector3f{0, 0, 40})
-                          .color(Color::red(255, 128))
-                          .width(20));
-
-                is_redrawn = true;
-                m_redraw_path = false;
-            }
-
-            return is_redrawn;
-        }
-
-        void viz_velocities(armarx::viz::Layer& layer)
-        {
-            using namespace armarx::viz;
-
-            const Eigen::Vector3f from1{m_agent_pos + Eigen::Vector3f{0, 0, 2000}};
-            const Eigen::Vector3f from2 = from1 + Eigen::Vector3f{0, 0, 200};
-
-            layer.add(Arrow{"desired_vel"}
-                      .fromTo(from1, from1 + m_agent_desired_vel * 5)
-                      .color(Color::green(255, 128))
-                      .width(25));
-
-            layer.add(Arrow{"modulated_vel"}
-                      .fromTo(from2, from2 + m_agent_modulated_vel * 5)
-                      .color(Color::cyan(255, 128))
-                      .width(25));
-        }
-
-        void viz_agent(armarx::viz::Layer& layer)
-        {
-            using namespace armarx::viz;
-
-            // Agent.
-            layer.add(Sphere{"agent"}
-                      .position(m_agent_pos)
-                      .color(Color::red(255, 128))
-                      .radius(50));
-
-            // Agent safety margin.
-            if (m_agent_safety_margin > 0)
-            {
-                layer.add(Cylinder{"agent_safety_margin"}
-                          .pose(simox::math::pos_rpy_to_mat4f(m_agent_pos, -M_PI_2, 0, 0))
-                          .color(Color::red(255, 64))
-                          .radius(m_agent_safety_margin)
-                          .height(2));
-            }
-        }
-
-    private:
-
-        bool m_redraw_path = true;
-
-        Eigen::Vector3f m_start;
-        Eigen::Vector3f m_goal;
-        Eigen::Vector3f m_agent_desired_vel;
-        Eigen::Vector3f m_agent_modulated_vel;
-        Eigen::Vector3f m_agent_pos;
-        float m_agent_safety_margin;
-        std::vector<Eigen::Vector3f> m_path;
-
-    };
-
-}
diff --git a/source/RobotAPI/components/units/CMakeLists.txt b/source/RobotAPI/components/units/CMakeLists.txt
index ce1deee81..430741e73 100644
--- a/source/RobotAPI/components/units/CMakeLists.txt
+++ b/source/RobotAPI/components/units/CMakeLists.txt
@@ -74,5 +74,6 @@ set(LIB_FILES
 
 armarx_add_library("${LIB_NAME}"  "${LIB_FILES}" "${LIB_HEADERS}" "${LIBS}")
 
+add_subdirectory(ObstacleAvoidingPlatformUnit)
 add_subdirectory(relays)
 add_subdirectory(RobotUnit)
diff --git a/source/RobotAPI/components/units/ObstacleAvoidingPlatformUnit/CMakeLists.txt b/source/RobotAPI/components/units/ObstacleAvoidingPlatformUnit/CMakeLists.txt
new file mode 100644
index 000000000..eace3006c
--- /dev/null
+++ b/source/RobotAPI/components/units/ObstacleAvoidingPlatformUnit/CMakeLists.txt
@@ -0,0 +1,13 @@
+armarx_component_set_name("ObstacleAvoidingPlatformUnit")
+
+set(COMPONENT_LIBS
+    RobotAPICore
+    RobotAPIComponentPlugins
+    RobotUnit
+)
+
+set(SOURCES ObstacleAvoidingPlatformUnit.cpp)
+set(HEADERS ObstacleAvoidingPlatformUnit.h)
+
+armarx_add_component("${SOURCES}" "${HEADERS}")
+armarx_add_component_executable("main.cpp")
diff --git a/source/RobotAPI/components/units/ObstacleAvoidingPlatformUnit/ObstacleAvoidingPlatformUnit.cpp b/source/RobotAPI/components/units/ObstacleAvoidingPlatformUnit/ObstacleAvoidingPlatformUnit.cpp
new file mode 100644
index 000000000..954ad2ab8
--- /dev/null
+++ b/source/RobotAPI/components/units/ObstacleAvoidingPlatformUnit/ObstacleAvoidingPlatformUnit.cpp
@@ -0,0 +1,619 @@
+/*
+ * This file is part of ArmarX.
+ *
+ * ArmarX is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * ArmarX is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package    RobotAPI::ArmarXObjects::ObstacleAvoidingPlatformUnit
+ * @author     Christian R. G. Dreher <c.dreher@kit.edu>
+ * @date       2020
+ * @copyright  http://www.gnu.org/licenses/gpl-2.0.txt
+ *             GNU General Public License
+ */
+
+
+#include <RobotAPI/components/units/ObstacleAvoidingPlatformUnit/ObstacleAvoidingPlatformUnit.h>
+
+
+// STD/STL
+#include <algorithm>
+
+// Eigen
+#include <Eigen/Geometry>
+
+// Simox
+#include <SimoxUtility/math.h>
+
+
+const std::string
+armarx::ObstacleAvoidingPlatformUnit::default_name = "ObstacleAvoidingPlatformUnit";
+
+
+armarx::ObstacleAvoidingPlatformUnit::ObstacleAvoidingPlatformUnit()
+{
+    m_agent.safety_margin = 0;
+}
+
+
+armarx::ObstacleAvoidingPlatformUnit::~ObstacleAvoidingPlatformUnit()
+{
+    ;
+}
+
+
+void
+armarx::ObstacleAvoidingPlatformUnit::onInitPlatformUnit()
+{
+    ARMARX_DEBUG << "Initializing " << getName() << ".";
+
+    ARMARX_DEBUG << "Initialized " << getName() << ".";
+}
+
+
+void
+armarx::ObstacleAvoidingPlatformUnit::onStartPlatformUnit()
+{
+    ARMARX_DEBUG << "Starting " << getName() << ".";
+
+    m_robot = addRobot("robot", VirtualRobot::RobotIO::eStructure);
+
+    ARMARX_DEBUG << "Started " << getName() << ".";
+}
+
+
+void
+armarx::ObstacleAvoidingPlatformUnit::onExitPlatformUnit()
+{
+    ARMARX_DEBUG << "Exiting " << getName() << ".";
+
+    schedule_high_level_control_loop(control_mode::none);
+    stopPlatform();
+
+    ARMARX_DEBUG << "Exited " << getName() << ".";
+}
+
+
+std::string
+armarx::ObstacleAvoidingPlatformUnit::getDefaultName()
+const
+{
+    return default_name;
+}
+
+
+void
+armarx::ObstacleAvoidingPlatformUnit::moveTo(
+    const float target_pos_x,
+    const float target_pos_y,
+    const float target_ori,
+    const float pos_reached_threshold,
+    const float ori_reached_threshold,
+    const Ice::Current&)
+{
+    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.pos_reached_threshold = pos_reached_threshold;
+    m_control_data.ori_reached_threshold = ori_reached_threshold;
+
+    schedule_high_level_control_loop(control_mode::position);
+}
+
+
+void
+armarx::ObstacleAvoidingPlatformUnit::move(
+    const float target_vel_x,
+    const float target_vel_y,
+    const float target_rot_vel,
+    const Ice::Current&)
+{
+    std::scoped_lock l{m_control_data.mutex};
+
+    m_control_data.target_vel = Eigen::Vector2f{target_vel_x, target_vel_y};
+    m_control_data.target_rot_vel = target_rot_vel;
+
+    ARMARX_CHECK(m_control_data.target_vel.allFinite());
+    ARMARX_CHECK(std::isfinite(m_control_data.target_rot_vel));
+
+    schedule_high_level_control_loop(control_mode::velocity);
+}
+
+
+void
+armarx::ObstacleAvoidingPlatformUnit::moveRelative(
+    const float target_pos_delta_x,
+    const float target_pos_delta_y,
+    const float target_delta_ori,
+    const float pos_reached_threshold,
+    const float ori_reached_threshold,
+    const Ice::Current& current)
+{
+    using namespace simox::math;
+
+    // Transform relative to global.
+    std::unique_lock lock{m_control_data.mutex};
+    synchronizeLocalClone(m_robot);
+    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);
+    lock.unlock();
+
+    // Use moveTo.
+    moveTo(
+        agent_pos.x() + target_pos_delta_x,
+        agent_pos.y() + target_pos_delta_y,
+        agent_ori + target_delta_ori,
+        pos_reached_threshold,
+        ori_reached_threshold,
+        current);
+}
+
+
+void
+armarx::ObstacleAvoidingPlatformUnit::setMaxVelocities(
+    const float max_pos_vel,
+    const float max_rot_vel,
+    const Ice::Current&)
+{
+    std::scoped_lock l{m_control_data.mutex};
+    m_control_data.max_vel = max_pos_vel;
+    m_control_data.max_rot_vel = max_rot_vel;
+    m_platform->setMaxVelocities(max_pos_vel, max_rot_vel);
+}
+
+
+void
+armarx::ObstacleAvoidingPlatformUnit::stopPlatform(const Ice::Current&)
+{
+    m_platform->move(0, 0, 0);
+    m_platform->stopPlatform();
+}
+
+
+void
+armarx::ObstacleAvoidingPlatformUnit::schedule_high_level_control_loop(control_mode mode)
+{
+    std::scoped_lock l{m_control_loop.mutex};
+
+    // If the control mode didn't change, there's nothing to do.
+    if (m_control_loop.mode == mode)
+    {
+        return;
+    }
+
+    // If a control loop is runnung, stop it and wait for termination.
+    if (m_control_loop.mode != mode and m_control_loop.task)
+    {
+        const bool join = true;
+        m_control_loop.task->stop(join);
+    }
+
+    // If the new control mode is none, no new control loop needs to be created.
+    if (mode == control_mode::none)
+    {
+        return;
+    }
+
+    // Start next control loop.
+    m_control_loop.mode = mode;
+    m_control_loop.task = new RunningTask<ObstacleAvoidingPlatformUnit>(
+        this,
+        &ObstacleAvoidingPlatformUnit::high_level_control_loop);
+    m_control_loop.task->start();
+}
+
+
+void
+armarx::ObstacleAvoidingPlatformUnit::high_level_control_loop()
+{
+    try
+    {
+        CycleUtil cu{m_control_loop.cycle_time};
+        while (not m_control_loop.task->isStopped())
+        {
+            if (target_reached())
+            {
+                break;
+            }
+
+            run_control_loop();
+            cu.waitForCycleDuration();
+        }
+    }
+    catch (const std::exception& e)
+    {
+        ARMARX_ERROR << "Error occured while running control loop.\n"
+                     << e.what();
+    }
+    catch (...)
+    {
+        ARMARX_ERROR << "Unknown error occured while running control loop.";
+    }
+
+    clean_up();
+    stopPlatform();
+    m_control_loop.mode = control_mode::none;
+}
+
+
+void
+armarx::ObstacleAvoidingPlatformUnit::run_control_loop()
+{
+    using namespace simox::math;
+
+    // Acquire control_data mutex to ensure data remains consistent.
+    std::scoped_lock l{m_control_data.mutex};
+
+    // Update agent pos and ori.
+    synchronizeLocalClone(m_robot);
+    m_control_data.agent_pos = m_robot->getGlobalPosition().head<2>();
+    m_control_data.agent_ori =
+        periodic_clamp<float>(mat4f_to_rpy(m_robot->getGlobalPose()).z(), -M_PI, M_PI);
+
+    // Calculate target velocity and target angular velocity if in position control mode.
+    if (m_control_loop.mode == control_mode::position)
+    {
+        ARMARX_CHECK(m_control_data.target_pos.allFinite());
+        ARMARX_CHECK(std::isfinite(m_control_data.target_ori));
+
+        // Determine distance and angular distance to target position and orientation.
+        m_control_data.target_dist =
+            (m_control_data.target_pos - m_control_data.agent_pos).norm();
+        m_control_data.target_angular_dist =
+            periodic_clamp<float>(m_control_data.target_ori - m_control_data.agent_ori,
+                                  -M_PI, M_PI);
+        ARMARX_DEBUG << "Distance to target:  " << m_control_data.target_dist << " mm and "
+                     << m_control_data.target_angular_dist << " rad.";
+
+        // Calculate target velocity.
+        m_control_data.target_vel =
+            (m_control_data.target_pos - m_control_data.agent_pos) * m_control_data.kp;
+
+        // Calculate target rotational velocity.
+        m_rot_pid_controller.update(m_control_data.target_angular_dist, 0);
+        m_control_data.target_rot_vel = m_rot_pid_controller.getControlValue();
+    }
+
+    ARMARX_CHECK(m_control_data.target_vel.allFinite());
+    ARMARX_CHECK(std::isfinite(m_control_data.target_rot_vel));
+
+    // Cap velocities if they exceed maximum values.
+    const Eigen::Vector2f target_vel =
+        norm_max(m_control_data.target_vel, m_control_data.max_vel);
+    const float target_rot_vel = std::copysign(
+        std::min(std::fabs(m_control_data.target_rot_vel), m_control_data.max_rot_vel),
+                        m_control_data.target_rot_vel);
+
+    // Apply obstacle avoidance and apply post processing.
+    m_agent.pos = Eigen::Vector3f{m_control_data.agent_pos.x(), m_control_data.agent_pos.y(), 0};
+    m_agent.desired_vel = Eigen::Vector3f{target_vel.x(), target_vel.y(), 0};
+    ARMARX_CHECK(m_agent.pos.allFinite());
+    ARMARX_CHECK(m_agent.desired_vel.allFinite());
+    const Eigen::Vector2f raw_modulated_vel =
+        m_obstacle_avoidance->modulateVelocity(m_agent).head<2>();
+    ARMARX_CHECK(raw_modulated_vel.allFinite());
+    const Eigen::Vector2f modulated_vel =
+        target_pos_reached() ?
+            Eigen::Vector2f::Zero() :
+            post_process_vel(target_vel, norm_max(raw_modulated_vel, m_control_data.max_vel));
+    ARMARX_CHECK(modulated_vel.allFinite());
+
+    if (m_viz.enabled)
+    {
+        visualize(modulated_vel);
+    }
+
+    // Move platform now.
+    const Eigen::Vector2f local_vel =
+        Eigen::Rotation2D(-m_control_data.agent_ori).toRotationMatrix() * modulated_vel;
+    ARMARX_CHECK(local_vel.allFinite()) << "Velocity contains non-finite values.";
+    ARMARX_CHECK(std::isfinite(target_rot_vel)) << "Rotation velocity is non-finite.";
+    m_platform->move(local_vel.x(), local_vel.y(), target_rot_vel);
+}
+
+
+bool
+armarx::ObstacleAvoidingPlatformUnit::target_pos_reached()
+{
+    if (m_control_loop.mode == control_mode::position)
+    {
+        return m_control_data.target_dist < m_control_data.pos_reached_threshold;
+    }
+
+    // Cannot reach any target in non-position mode.
+    return false;
+}
+
+
+bool
+armarx::ObstacleAvoidingPlatformUnit::target_reached()
+{
+    if (m_control_loop.mode == control_mode::position)
+    {
+        // Determine whether the goal position was reached.
+        if (target_pos_reached())
+        {
+            ARMARX_VERBOSE << "Goal position reached.";
+
+            // Determine whether goal orientation was reached.
+            if (std::fabs(m_control_data.target_angular_dist) <
+                m_control_data.ori_reached_threshold)
+            {
+                ARMARX_VERBOSE << "Goal orientation reached.";
+
+                return true;
+            }
+        }
+    }
+
+    return false;
+}
+
+
+Eigen::Vector2f
+armarx::ObstacleAvoidingPlatformUnit::post_process_vel(
+    const Eigen::Vector2f& target_vel,
+    const Eigen::Vector2f& modulated_vel)
+{
+    ARMARX_CHECK(target_vel.allFinite());
+    ARMARX_CHECK(modulated_vel.allFinite());
+
+    // Update buffer.
+    m_control_data.vel_history.push_front(std::make_tuple(target_vel, modulated_vel));
+    const unsigned max_real_buffer_size =
+        std::max(m_control_data.amount_smoothing, m_control_data.amount_max_vel);
+    if (m_control_data.vel_history.size() > max_real_buffer_size)
+    {
+        m_control_data.vel_history.resize(max_real_buffer_size);
+    }
+
+    // Smooth by calculating mean over the last `m_control_data.amount_smoothing` elements.
+    const Eigen::Vector2f mean_modulated_vel = calculate_mean_modulated_vel();
+    ARMARX_CHECK(mean_modulated_vel.allFinite());
+
+    // 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();
+    ARMARX_CHECK(std::isfinite(max_vel));
+
+    return simox::math::norm_max(mean_modulated_vel, max_vel);
+}
+
+
+Eigen::Vector2f
+armarx::ObstacleAvoidingPlatformUnit::calculate_mean_modulated_vel()
+const
+{
+    const unsigned adaptive_buffer_size = calculate_adaptive_smoothing_buffer_size();
+
+    std::vector<Eigen::Vector2f> last_modulated_vels;
+    const unsigned max_i =
+        std::min<unsigned>(m_control_data.vel_history.size(), adaptive_buffer_size);
+
+    ARMARX_CHECK_GREATER(max_i, 0);
+
+    for (unsigned i = 0; i < max_i; ++i)
+    {
+        const Eigen::Vector2f modulated_vel = std::get<1>(m_control_data.vel_history.at(i));
+        ARMARX_CHECK(modulated_vel.allFinite());
+        last_modulated_vels.push_back(std::move(modulated_vel));
+    }
+
+    return simox::math::mean(last_modulated_vels);
+}
+
+
+unsigned
+armarx::ObstacleAvoidingPlatformUnit::calculate_adaptive_smoothing_buffer_size()
+const
+{
+    // buffer_size(min_buffer_size_dist) = min_buffer_size
+    const float min_buffer_size_dist = 200;
+    const float min_buffer_size = 3;
+    // buffer_size(max_buffer_size_dist) = max_buffer_size
+    const float max_buffer_size_dist = 1500;
+    const float max_buffer_size = m_control_data.amount_smoothing;
+
+    ARMARX_CHECK_LESS(min_buffer_size, max_buffer_size);
+
+    // Given the two points, calculate m and b in:  f(x) = m * x + b
+    const float m = (max_buffer_size - min_buffer_size) /
+                        (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));
+}
+
+
+float
+armarx::ObstacleAvoidingPlatformUnit::calculate_adaptive_max_vel()
+const
+{
+    using namespace simox::math;
+
+    std::vector<float> angular_similarities;
+    const unsigned max_i =
+        std::min<unsigned>(m_control_data.vel_history.size(), m_control_data.amount_max_vel);
+
+    for (unsigned int i = 0; i < max_i; ++i)
+    {
+        auto& [desired_vel, modulated_vel] = m_control_data.vel_history.at(i);
+        float angular_sim = -1;
+
+        if (desired_vel.isZero() and modulated_vel.isZero())
+        {
+            angular_sim = 1;
+        }
+        else if (desired_vel.isZero() xor modulated_vel.isZero())
+        {
+            angular_sim = -1;
+        }
+        else
+        {
+            angular_sim = angular_similarity(desired_vel, modulated_vel);
+        }
+
+        angular_similarities.push_back(angular_sim);
+    }
+
+    const float mean_angular_similarity = mean(angular_similarities);
+    const float max_vel_factor = std::pow(mean_angular_similarity, 2);
+
+    return max_vel_factor * m_control_data.max_vel;
+}
+
+
+void
+armarx::ObstacleAvoidingPlatformUnit::clean_up()
+{
+    // Clear buffers.
+    m_control_data.vel_history.clear();
+    m_viz.path.clear();
+
+    // Clear ArViz.
+    for (const std::string& layer_name : {"progress", "velocities", "agent"})
+    {
+        viz::Layer layer = arviz.layer(layer_name);
+        arviz.commit(layer);
+    }
+}
+
+
+void
+armarx::ObstacleAvoidingPlatformUnit::visualize(const Eigen::Vector2f& modulated_vel_2d)
+{
+    using namespace armarx::viz;
+
+    const Eigen::Vector3f modulated_vel{modulated_vel_2d.x(), modulated_vel_2d.y(), 0};
+
+    // Progress.  Only draw in position control mode.
+    if (m_control_loop.mode == control_mode::position)
+    {
+        const float min_keypoint_dist = 50;
+        const Eigen::Vector3f& last_keypoint_pos =
+            m_viz.path.size() >= 1 ? m_viz.path.back() : m_viz.start;
+        bool redraw_path = false;
+
+        // Keep a certain distance between path keypoints.
+        if ((last_keypoint_pos - m_agent.pos).norm() > min_keypoint_dist)
+        {
+            m_viz.path.push_back(m_agent.pos);
+            redraw_path = true;
+        }
+
+        // Draw path.
+        if (redraw_path)
+        {
+            Layer layer = arviz.layer("progress");
+
+            // Start.
+            layer.add(Sphere{"start"}
+                      .position(m_viz.start)
+                      .color(Color::cyan(255, 64))
+                      .radius(40));
+
+            // Path.
+            for (unsigned i = 0; i < m_viz.path.size(); ++i)
+            {
+                layer.add(Sphere{"path_" + std::to_string(i + 1)}
+                          .position(m_viz.path[i])
+                          .color(Color::magenta(255, 128))
+                          .radius(20));
+            }
+
+            // Goal.
+            const Eigen::Vector3f target{m_control_data.target_pos.x(),
+                                         m_control_data.target_pos.y(),
+                                         0};
+            layer.add(Arrow{"goal"}
+                      .fromTo(target + Eigen::Vector3f{0, 0, 220},
+                              target + Eigen::Vector3f{0, 0, 40})
+                      .color(Color::red(255, 128))
+                      .width(20));
+
+            arviz.commit(layer);
+        }
+    }
+
+    // Velocities.
+    {
+        Layer layer = arviz.layer("velocities");
+
+        const Eigen::Vector3f from1{m_agent.pos + Eigen::Vector3f{0, 0, 2000}};
+        const Eigen::Vector3f from2 = from1 + Eigen::Vector3f{0, 0, 200};
+
+        if (not m_agent.desired_vel.isZero())
+        {
+            layer.add(Arrow{"original"}
+                      .fromTo(from1, from1 + m_agent.desired_vel * 5)
+                      .color(Color::green(255, 128))
+                      .width(25));
+        }
+
+        if (not modulated_vel.isZero())
+        {
+            layer.add(Arrow{"modulated"}
+                      .fromTo(from2, from2 + modulated_vel * 5)
+                      .color(Color::cyan(255, 128))
+                      .width(25));
+        }
+
+        arviz.commit(layer);
+    }
+
+    // Agent.
+    {
+        Layer layer = arviz.layer("agent");
+
+        layer.add(Sphere{"agent"}
+                  .position(m_agent.pos)
+                  .color(Color::red(255, 128))
+                  .radius(50));
+
+        // Agent safety margin.
+        if (m_agent.safety_margin > 0)
+        {
+            layer.add(Cylinder{"agent_safety_margin"}
+                      .pose(simox::math::pos_rpy_to_mat4f(m_agent.pos, -M_PI_2, 0, 0))
+                      .color(Color::red(255, 64))
+                      .radius(m_agent.safety_margin)
+                      .height(2));
+        }
+
+        arviz.commit(layer);
+    }
+}
+
+
+armarx::PropertyDefinitionsPtr
+armarx::ObstacleAvoidingPlatformUnit::createPropertyDefinitions()
+{
+    PropertyDefinitionsPtr def{new PlatformUnitPropertyDefinitions{getConfigIdentifier()}};
+
+    def->component(m_platform, "Platform");
+    def->component(m_obstacle_avoidance, "PlatformObstacleAvoidance");
+
+    // Control parameters.
+    def->optional(m_control_data.kp, "control.pos.kp");
+    def->optional(m_rot_pid_controller.Kp, "control.rot.kp");
+    def->optional(m_rot_pid_controller.Ki, "control.rot.ki");
+    def->optional(m_rot_pid_controller.Kd, "control.rot.kd");
+    def->optional(m_control_loop.cycle_time, "control.pose.cycle_time");
+
+    // Obstacle avoidance parameter.
+    def->optional(m_agent.safety_margin, "doa.agent_safety_margin");
+
+    return def;
+}
diff --git a/source/RobotAPI/components/units/ObstacleAvoidingPlatformUnit/ObstacleAvoidingPlatformUnit.h b/source/RobotAPI/components/units/ObstacleAvoidingPlatformUnit/ObstacleAvoidingPlatformUnit.h
new file mode 100644
index 000000000..e8ed4cb3f
--- /dev/null
+++ b/source/RobotAPI/components/units/ObstacleAvoidingPlatformUnit/ObstacleAvoidingPlatformUnit.h
@@ -0,0 +1,246 @@
+/*
+ * This file is part of ArmarX.
+ *
+ * ArmarX is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * ArmarX is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package    RobotAPI::ArmarXObjects::ObstacleAvoidingPlatformUnit
+ * @author     Christian R. G. Dreher <c.dreher@kit.edu>
+ * @date       2020
+ * @copyright  http://www.gnu.org/licenses/gpl-2.0.txt
+ *             GNU General Public License
+ */
+
+
+#pragma once
+
+
+// STD/STL
+#include <deque>
+#include <string>
+#include <mutex>
+#include <vector>
+
+// Eigen
+#include <Eigen/Core>
+
+// Ice
+#include <IceUtil/Time.h>
+
+// Simox
+#include <VirtualRobot/Nodes/RobotNode.h>
+
+// ArmarX
+#include <ArmarXCore/util/tasks.h>
+#include <ArmarXCore/util/time.h>
+
+// RobotAPI
+#include <RobotAPI/components/units/PlatformUnit.h>
+#include <RobotAPI/interface/components/ObstacleAvoidanceInterface.h>
+#include <RobotAPI/libraries/core/PIDController.h>
+#include <RobotAPI/libraries/RobotAPIComponentPlugins/ArVizComponentPlugin.h>
+#include <RobotAPI/libraries/RobotAPIComponentPlugins/RobotStateComponentPlugin.h>
+
+
+namespace armarx
+{
+
+    class ObstacleAvoidingPlatformUnit :
+        virtual public PlatformUnit,
+        virtual public RobotStateComponentPluginUser,
+        virtual public ArVizComponentPluginUser
+    {
+
+    private:
+
+        enum class control_mode
+        {
+            position,
+            velocity,
+            none
+        };
+
+        struct control_loop
+        {
+            std::mutex mutex;
+            control_mode mode = control_mode::none;
+            RunningTask<ObstacleAvoidingPlatformUnit>::pointer_type task;
+            IceUtil::Time cycle_time = IceUtil::Time::milliSeconds(10);
+        };
+
+        struct control_data
+        {
+            std::mutex mutex;
+            Eigen::Vector2f target_pos;
+            float target_ori;
+            Eigen::Vector2f target_vel;
+            float target_rot_vel;
+            float target_dist;
+            float target_angular_dist;
+            Eigen::Vector2f agent_pos;
+            float agent_ori;
+            float max_vel = 200;
+            float max_rot_vel = 0.3;
+            float pos_reached_threshold = 8;
+            float ori_reached_threshold = 0.1;
+            float kp = 3.5;
+            std::deque<std::tuple<Eigen::Vector2f, Eigen::Vector2f>> vel_history;
+            unsigned amount_smoothing = 50;
+            unsigned amount_max_vel = 3;
+        };
+
+        struct visualization
+        {
+            Eigen::Vector3f start;
+            std::vector<Eigen::Vector3f> path;
+            bool enabled = true;
+        };
+
+    public:
+
+        ObstacleAvoidingPlatformUnit();
+
+        virtual
+        ~ObstacleAvoidingPlatformUnit()
+        override;
+
+        virtual
+        std::string
+        getDefaultName()
+        const override;
+
+        virtual
+        void
+        moveTo(
+            float target_pos_x,
+            float target_pos_y,
+            float target_ori,
+            float pos_reached_threshold,
+            float ori_reached_threshold,
+            const Ice::Current& = Ice::Current{})
+        override;
+
+        virtual
+        void
+        move(
+            float target_vel_x,
+            float target_vel_y,
+            float target_rot_vel,
+            const Ice::Current& = Ice::Current{})
+        override;
+
+        virtual
+        void
+        moveRelative(
+            float target_pos_delta_x,
+            float target_pos_delta_y,
+            float target_delta_ori,
+            float pos_reached_threshold,
+            float ori_reached_threshold,
+            const Ice::Current& = Ice::Current{})
+        override;
+
+        virtual
+        void
+        setMaxVelocities(
+            float max_pos_vel,
+            float max_rot_vel,
+            const Ice::Current& = Ice::Current{})
+        override;
+
+        virtual
+        void
+        stopPlatform(const Ice::Current& = Ice::Current{})
+        override;
+
+    protected:
+
+        virtual
+        void
+        onInitPlatformUnit()
+        override;
+
+        virtual
+        void
+        onStartPlatformUnit()
+        override;
+
+        virtual
+        void
+        onExitPlatformUnit()
+        override;
+
+        virtual
+        PropertyDefinitionsPtr
+        createPropertyDefinitions()
+        override;
+
+    private:
+
+        void
+        schedule_high_level_control_loop(control_mode mode);
+
+        void
+        high_level_control_loop();
+
+        void
+        run_control_loop();
+
+        bool
+        target_pos_reached();
+
+        bool
+        target_reached();
+
+        Eigen::Vector2f
+        post_process_vel(const Eigen::Vector2f& target_vel, const Eigen::Vector2f& modulated_vel);
+
+        Eigen::Vector2f
+        calculate_mean_modulated_vel()
+        const;
+
+        unsigned
+        calculate_adaptive_smoothing_buffer_size()
+        const;
+
+        float
+        calculate_adaptive_max_vel()
+        const;
+
+        void
+        visualize(const Eigen::Vector2f& modulated_vel);
+
+        void
+        clean_up();
+
+    public:
+
+        static const std::string default_name;
+
+    private:
+
+        PlatformUnitInterfacePrx m_platform;
+        ObstacleAvoidanceInterfacePrx m_obstacle_avoidance;
+        VirtualRobot::RobotPtr m_robot;
+
+        ObstacleAvoidingPlatformUnit::control_loop m_control_loop;
+        ObstacleAvoidingPlatformUnit::control_data m_control_data;
+
+        PIDController m_rot_pid_controller{1.0, 0.00009, 0.0};
+
+        obstacleavoidance::Agent m_agent;
+
+        visualization m_viz;
+
+    };
+
+}
diff --git a/source/RobotAPI/components/units/ObstacleAvoidingPlatformUnit/main.cpp b/source/RobotAPI/components/units/ObstacleAvoidingPlatformUnit/main.cpp
new file mode 100644
index 000000000..8dd9c74ae
--- /dev/null
+++ b/source/RobotAPI/components/units/ObstacleAvoidingPlatformUnit/main.cpp
@@ -0,0 +1,40 @@
+/*
+ * This file is part of ArmarX.
+ *
+ * ArmarX is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * ArmarX is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package    RobotAPI::ArmarXObjects::ObstacleAvoidingPlatformUnit
+ * @author     Christian R. G. Dreher <c.dreher@kit.edu>
+ * @date       2020
+ * @copyright  http://www.gnu.org/licenses/gpl-2.0.txt
+ *             GNU General Public License
+ */
+
+
+// STD/STL
+#include <string>
+
+// ArmarX
+#include <ArmarXCore/core/application/Application.h>
+#include <ArmarXCore/core/Component.h>
+
+// RobotAPI
+#include <RobotAPI/components/units/ObstacleAvoidingPlatformUnit/ObstacleAvoidingPlatformUnit.h>
+
+
+int main(int argc, char* argv[])
+{
+    using namespace armarx;
+    const std::string name = ObstacleAvoidingPlatformUnit::default_name;
+    return runSimpleComponentApp<ObstacleAvoidingPlatformUnit>(argc, argv, name);
+}
-- 
GitLab