diff --git a/source/RobotAPI/components/armem/client/MemoryToDebugObserver/Component.cpp b/source/RobotAPI/components/armem/client/MemoryToDebugObserver/Component.cpp
index 70e1acea7b4b2af6339f14ef01cae92ed733c420..6d42b4106f87d9999d13724f48c39524b28e8a99 100644
--- a/source/RobotAPI/components/armem/client/MemoryToDebugObserver/Component.cpp
+++ b/source/RobotAPI/components/armem/client/MemoryToDebugObserver/Component.cpp
@@ -25,11 +25,6 @@
 #include <ArmarXCore/core/time/Frequency.h>
 #include <ArmarXCore/core/time/Metronome.h>
 
-#include <RobotAPI/libraries/armem/core/error/mns.h>
-#include <RobotAPI/libraries/aron/core/data/variant/Variant.h>
-#include <RobotAPI/libraries/aron/core/data/variant/primitive/All.h>
-#include <RobotAPI/libraries/aron/core/data/visitor/variant/VariantVisitor.h>
-
 namespace armarx::memory_to_debug_observer
 {
 
@@ -39,7 +34,6 @@ namespace armarx::memory_to_debug_observer
         armarx::PropertyDefinitionsPtr defs =
             new ComponentPropertyDefinitions(getConfigIdentifier());
 
-
         return defs;
     }
 
@@ -74,7 +68,7 @@ namespace armarx::memory_to_debug_observer
             };
             for (const std::string& attribute : attributes)
             {
-                properties.plottedValues.push_back(MemoryValueID{
+                properties.memoryToDebugObserver.plottedValues.push_back({
                     .entityID = robotEntityId,
                     .aronPath = {{"joints", attribute, jointName}},
                 });
@@ -96,6 +90,7 @@ namespace armarx::memory_to_debug_observer
     Component::onDisconnectComponent()
     {
         task->stop();
+        task = nullptr;
     }
 
     void
@@ -106,166 +101,22 @@ namespace armarx::memory_to_debug_observer
     void
     Component::runLoop()
     {
+        armem::client::util::MemoryToDebugObserver::Services services{
+            .memoryNameSystem = memoryNameSystem(),
+            .debugObserver = getDebugObserverComponentPlugin(),
+        };
+        armem::client::util::MemoryToDebugObserver memoryToDebugObserver{
+            properties.memoryToDebugObserver, services};
+
         Frequency frequency = Frequency::Hertz(30);
         Metronome metronome(frequency);
-        while (not task->isStopped())
+        while (task and not task->isStopped())
         {
-            loopOnce();
+            memoryToDebugObserver.pollOnce();
             metronome.waitForNextTick();
         }
     }
 
-    class Visitor : public aron::data::ConstVariantVisitor
-    {
-    public:
-        Visitor(armarx::plugins::DebugObserverComponentPlugin& debugObserver) :
-            debugObserver{debugObserver}
-        {
-        }
-
-        // void visitAronVariant(const data::DictPtr&) override;
-        // void visitAronVariant(const data::ListPtr&) override;
-        // void visitAronVariant(const data::NDArrayPtr&) override;
-        void
-        visitAronVariant(const aron::data::IntPtr& v) override
-        {
-            setDatafield(v->getValue());
-        }
-
-        void
-        visitAronVariant(const aron::data::LongPtr& v) override
-        {
-            setDatafield(v->getValue());
-        }
-
-        void
-        visitAronVariant(const aron::data::FloatPtr& v) override
-        {
-            setDatafield(v->getValue());
-        }
-
-        void
-        visitAronVariant(const aron::data::DoublePtr& v) override
-        {
-            setDatafield(v->getValue());
-        }
-
-        void
-        visitAronVariant(const aron::data::BoolPtr& v) override
-        {
-            setDatafield(v->getValue());
-        }
-
-        void
-        visitAronVariant(const aron::data::StringPtr& v) override
-        {
-            setDatafield(v->getValue());
-        }
-
-        template <class ValueT>
-        void
-        setDatafield(const ValueT& value)
-        {
-            debugObserver.setDebugObserverDatafield(channelName, datafieldName, value);
-            ++count;
-        }
-
-        armarx::plugins::DebugObserverComponentPlugin& debugObserver;
-        std::string channelName;
-        std::string datafieldName;
-
-        int count = 0;
-    };
-
-    void
-    Component::loopOnce()
-    {
-        Visitor visitor(getDebugObserverComponentPlugin());
-
-        std::stringstream log;
-        for (const MemoryValueID& valueId : properties.plottedValues)
-        {
-            armem::client::Reader* reader = getReader(valueId.entityID, log);
-            if (reader == nullptr)
-            {
-                // Log is written in getReader.
-                continue;
-            }
-
-            std::optional<armem::wm::EntitySnapshot> snapshot =
-                reader->getLatestSnapshotIn(valueId.entityID);
-
-            if (not snapshot.has_value())
-            {
-                log << "\nDid not find snapshot in entity " << valueId.entityID << ".";
-                continue;
-            }
-
-            aron::data::DictPtr data = snapshot->getInstance(0).data();
-            aron::data::VariantPtr valueVariant = data->navigateAbsolute(valueId.aronPath);
-            if (not valueVariant)
-            {
-                log << "\nDid not find " << valueId.aronPath.toString() << " in entity snapshot "
-                    << snapshot->id() << ".";
-                continue;
-            }
-
-
-            auto makeDatafieldName = [](const aron::Path& path) -> std::string
-            {
-                // The first element is always "ARON->", which we can discard for readability.
-                std::string str = path.toString();
-                str =
-                    simox::alg::remove_prefix(str, path.getRootIdentifier() + path.getDelimeter());
-                str = simox::alg::replace_all(str, path.getDelimeter(), ">");
-                return str;
-            };
-
-            visitor.channelName = simox::alg::replace_all(valueId.entityID.str(), "/", ">");
-            visitor.datafieldName = makeDatafieldName(valueId.aronPath);
-
-            aron::data::visit(visitor, valueVariant);
-        }
-
-        ARMARX_INFO << "Sending debug observer batch with " << visitor.count << " values ...";
-        sendDebugObserverBatch();
-
-        if (not log.str().empty())
-        {
-            ARMARX_INFO << "Encountered issues while sending memory values to the debug observer "
-                           "for plotting: "
-                        << log.str();
-        }
-    }
-
-    armem::client::Reader*
-    Component::getReader(const armem::MemoryID& memoryID, std::stringstream& log)
-    {
-        armem::MemoryID key = memoryID.getProviderSegmentID();
-
-        auto it = memoryReaders.find(key);
-        if (it != memoryReaders.end())
-        {
-            return &it->second;
-        }
-        else
-        {
-            armem::client::Reader reader;
-            try
-            {
-                reader = memoryNameSystem().getReader(key);
-            }
-            catch (armem::error::CouldNotResolveMemoryServer& e)
-            {
-                log << "\n" << e.what();
-                return nullptr;
-            }
-
-            auto [it, _] = memoryReaders.emplace(key, reader);
-            return &it->second;
-        }
-    }
-
     void
     Component::createRemoteGuiTab()
     {
diff --git a/source/RobotAPI/components/armem/client/MemoryToDebugObserver/Component.h b/source/RobotAPI/components/armem/client/MemoryToDebugObserver/Component.h
index ad76e6c9b4285b58142756b9a5df4f18e1726a11..02b4bb24909d77599065554df46b9e8deac32d59 100644
--- a/source/RobotAPI/components/armem/client/MemoryToDebugObserver/Component.h
+++ b/source/RobotAPI/components/armem/client/MemoryToDebugObserver/Component.h
@@ -23,31 +23,17 @@
 #pragma once
 
 #include <ArmarXCore/core/Component.h>
-#include <ArmarXCore/interface/observers/ObserverInterface.h>
 #include <ArmarXCore/libraries/ArmarXCoreComponentPlugins/DebugObserverComponentPlugin.h>
 #include <ArmarXCore/util/tasks.h>
 
 #include <ArmarXGui/libraries/ArmarXGuiComponentPlugins/LightweightRemoteGuiComponentPlugin.h>
 
-#include <RobotAPI/interface/armem/mns/MemoryNameSystemInterface.h>
-#include <RobotAPI/libraries/armem/client/Reader.h>
 #include <RobotAPI/libraries/armem/client/plugins/PluginUser.h>
-#include <RobotAPI/libraries/armem/core/wm/memory_definitions.h>
+#include <RobotAPI/libraries/armem/client/util/MemoryToDebugObserver.h>
 
 namespace armarx::memory_to_debug_observer
 {
 
-
-    struct MemoryValueID
-    {
-        armem::MemoryID entityID;
-        aron::Path aronPath;
-    };
-
-    class MemoryToDebugObserver
-    {
-    };
-
     /**
      * @defgroup Component-MemoryToDebugObserver MemoryToDebugObserver
      * @ingroup RobotAPI-Components
@@ -58,6 +44,9 @@ namespace armarx::memory_to_debug_observer
      * @class Component
      * @ingroup Component-MemoryToDebugObserver
      * @brief Implementation of \ref Component-MemoryToDebugObserver.
+     *
+     * @see armarx::armem::client::util::MemoryToDebugObserver for the business logic
+     * implementation.
      */
     class Component :
         virtual public armarx::Component,
@@ -86,21 +75,16 @@ namespace armarx::memory_to_debug_observer
         void onExitComponent() override;
 
         void runLoop();
-        void loopOnce();
-
-        armem::client::Reader* getReader(const armem::MemoryID& memoryID, std::stringstream& log);
 
 
     private:
         struct Properties
         {
-            std::vector<MemoryValueID> plottedValues;
+            armem::client::util::MemoryToDebugObserver::Properties memoryToDebugObserver;
         };
 
         Properties properties;
 
-        std::map<armem::MemoryID, armem::client::Reader> memoryReaders;
-
         armarx::RunningTask<Component>::pointer_type task;
 
         struct RemoteGuiTab : RemoteGui::Client::Tab
diff --git a/source/RobotAPI/libraries/armem/CMakeLists.txt b/source/RobotAPI/libraries/armem/CMakeLists.txt
index 9030bd17597399abf79e3413ec71a789f3a113ca..670fef073807ec08cf5938f77a5876f099d89f07 100644
--- a/source/RobotAPI/libraries/armem/CMakeLists.txt
+++ b/source/RobotAPI/libraries/armem/CMakeLists.txt
@@ -7,6 +7,7 @@ armarx_set_target("Library: ${LIB_NAME}")
 set(LIBS
     ArmarXCoreInterfaces
     ArmarXCore
+    DebugObserverHelper
     RemoteGui
     aron
     aroncommon
@@ -64,8 +65,9 @@ set(LIB_FILES
     client/plugins/PluginUser.cpp
     client/plugins/Plugin.cpp
 
-    client/util/SubscriptionHandle.cpp
     client/util/MemoryListener.cpp
+    client/util/MemoryToDebugObserver.cpp
+    client/util/SubscriptionHandle.cpp
     client/util/SimpleReaderBase.cpp
     client/util/SimpleWriterBase.cpp
 
@@ -157,10 +159,11 @@ set(LIB_HEADERS
     client/query/detail/NameSelectorOps.h
     client/query/detail/SelectorOps.h
 
-    client/util/SubscriptionHandle.h
+    client/util/MemoryToDebugObserver.h
     client/util/MemoryListener.h
     client/util/SimpleReaderBase.h
     client/util/SimpleWriterBase.h
+    client/util/SubscriptionHandle.h
 
     server.h
 
diff --git a/source/RobotAPI/libraries/armem/client/util/MemoryToDebugObserver.cpp b/source/RobotAPI/libraries/armem/client/util/MemoryToDebugObserver.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5e8f44930e104219c8c2a33c34ecc0a9e18d9b5f
--- /dev/null
+++ b/source/RobotAPI/libraries/armem/client/util/MemoryToDebugObserver.cpp
@@ -0,0 +1,190 @@
+/*
+ * 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::MemoryToDebugObserver
+ * @author     Rainer Kartmann ( rainer dot kartmann at kit dot edu )
+ * @date       2023
+ * @copyright  http://www.gnu.org/licenses/gpl-2.0.txt
+ *             GNU General Public License
+ */
+
+#include "MemoryToDebugObserver.h"
+
+#include <RobotAPI/libraries/armem/core/error/mns.h>
+#include <RobotAPI/libraries/aron/core/data/variant/Variant.h>
+#include <RobotAPI/libraries/aron/core/data/variant/primitive/All.h>
+#include <RobotAPI/libraries/aron/core/data/visitor/variant/VariantVisitor.h>
+
+namespace armarx::armem::client::util
+{
+
+    MemoryToDebugObserver::MemoryToDebugObserver(const Properties& properties,
+                                                 const Services& services) :
+        properties(properties), services(services)
+    {
+        services.debugObserver.setDebugObserverBatchModeEnabled(true);
+    }
+
+    class Visitor : public aron::data::ConstVariantVisitor
+    {
+    public:
+        Visitor(armarx::DebugObserverHelper& debugObserver) : debugObserver{debugObserver}
+        {
+        }
+
+        // void visitAronVariant(const data::DictPtr&) override;
+        // void visitAronVariant(const data::ListPtr&) override;
+        // void visitAronVariant(const data::NDArrayPtr&) override;
+        void
+        visitAronVariant(const aron::data::IntPtr& v) override
+        {
+            setDatafield(v->getValue());
+        }
+
+        void
+        visitAronVariant(const aron::data::LongPtr& v) override
+        {
+            setDatafield(v->getValue());
+        }
+
+        void
+        visitAronVariant(const aron::data::FloatPtr& v) override
+        {
+            setDatafield(v->getValue());
+        }
+
+        void
+        visitAronVariant(const aron::data::DoublePtr& v) override
+        {
+            setDatafield(v->getValue());
+        }
+
+        void
+        visitAronVariant(const aron::data::BoolPtr& v) override
+        {
+            setDatafield(v->getValue());
+        }
+
+        void
+        visitAronVariant(const aron::data::StringPtr& v) override
+        {
+            setDatafield(v->getValue());
+        }
+
+        template <class ValueT>
+        void
+        setDatafield(const ValueT& value)
+        {
+            debugObserver.setDebugObserverDatafield(channelName, datafieldName, value);
+            ++count;
+        }
+
+        armarx::DebugObserverHelper& debugObserver;
+        std::string channelName;
+        std::string datafieldName;
+
+        int count = 0;
+    };
+
+    void
+    MemoryToDebugObserver::pollOnce()
+    {
+        Visitor visitor(services.debugObserver);
+
+        std::stringstream log;
+        for (const MemoryValueID& valueId : properties.plottedValues)
+        {
+            armem::client::Reader* reader = nullptr;
+            try
+            {
+                reader = getReader(valueId.entityID);
+            }
+            catch (armem::error::CouldNotResolveMemoryServer& e)
+            {
+                log << "\n" << e.what();
+                continue;
+            }
+            ARMARX_CHECK_NOT_NULL(reader);
+
+            std::optional<armem::wm::EntitySnapshot> snapshot =
+                reader->getLatestSnapshotIn(valueId.entityID);
+
+            if (not snapshot.has_value())
+            {
+                log << "\nDid not find snapshot in entity " << valueId.entityID << ".";
+                continue;
+            }
+
+            aron::data::DictPtr data = snapshot->getInstance(0).data();
+            aron::data::VariantPtr valueVariant = data->navigateAbsolute(valueId.aronPath);
+            if (not valueVariant)
+            {
+                log << "\nDid not find " << valueId.aronPath.toString() << " in entity snapshot "
+                    << snapshot->id() << ".";
+                continue;
+            }
+
+            visitor.channelName = makeChannelName(valueId.entityID);
+            visitor.datafieldName = makeDatafieldName(valueId.aronPath);
+
+            aron::data::visit(visitor, valueVariant);
+        }
+
+        services.debugObserver.sendDebugObserverBatch();
+
+        if (not log.str().empty())
+        {
+            ARMARX_INFO << "Encountered issues while sending memory values to the debug observer "
+                           "for plotting: "
+                        << log.str();
+        }
+    }
+
+    std::string
+    MemoryToDebugObserver::makeChannelName(const MemoryID& memoryID)
+    {
+        return simox::alg::replace_all(memoryID.str(), "/", ">");
+    }
+
+    std::string
+    MemoryToDebugObserver::makeDatafieldName(const aron::Path& path)
+    {
+        // The first element is always "ARON->", which we can discard for readability.
+        std::string str = path.toString();
+        str = simox::alg::remove_prefix(str, path.getRootIdentifier() + path.getDelimeter());
+        str = simox::alg::replace_all(str, path.getDelimeter(), ">");
+        return str;
+    }
+
+    Reader*
+    MemoryToDebugObserver::getReader(const MemoryID& memoryID)
+    {
+        armem::MemoryID key = memoryID.getProviderSegmentID();
+
+        auto it = memoryReaders.find(key);
+        if (it != memoryReaders.end())
+        {
+            return &it->second;
+        }
+        else
+        {
+            armem::client::Reader reader = services.memoryNameSystem.getReader(key);
+
+            auto [it, _] = memoryReaders.emplace(key, reader);
+            return &it->second;
+        }
+    }
+
+} // namespace armarx::armem::client::util
diff --git a/source/RobotAPI/libraries/armem/client/util/MemoryToDebugObserver.h b/source/RobotAPI/libraries/armem/client/util/MemoryToDebugObserver.h
new file mode 100644
index 0000000000000000000000000000000000000000..c254db3a33235b2400d5d3138a642b97a7316209
--- /dev/null
+++ b/source/RobotAPI/libraries/armem/client/util/MemoryToDebugObserver.h
@@ -0,0 +1,81 @@
+/*
+ * 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::MemoryToDebugObserver
+ * @author     Rainer Kartmann ( rainer dot kartmann at kit dot edu )
+ * @date       2023
+ * @copyright  http://www.gnu.org/licenses/gpl-2.0.txt
+ *             GNU General Public License
+ */
+
+#pragma once
+
+#include <ArmarXCore/libraries/DebugObserverHelper/DebugObserverHelper.h>
+
+#include <RobotAPI/interface/armem/mns/MemoryNameSystemInterface.h>
+#include <RobotAPI/libraries/armem/client/Reader.h>
+#include <RobotAPI/libraries/armem/client/plugins/PluginUser.h>
+#include <RobotAPI/libraries/armem/core/wm/memory_definitions.h>
+
+namespace armarx::armem::client::util
+{
+    /**
+     * @brief ID of an ARON value in the memory.
+     */
+    struct MemoryValueID
+    {
+        armem::MemoryID entityID;
+        aron::Path aronPath;
+    };
+
+    /**
+     * @brief Transfers data from memory servers to the DebugObserver.
+     *
+     * This allows to visualize the values in the LivePlotter.
+     */
+    class MemoryToDebugObserver
+    {
+    public:
+        struct Properties
+        {
+            std::vector<MemoryValueID> plottedValues;
+        };
+
+        struct Services
+        {
+            MemoryNameSystem& memoryNameSystem;
+            armarx::DebugObserverHelper& debugObserver;
+        };
+
+        MemoryToDebugObserver(const Properties& properties, const Services& services);
+
+        /**
+         * @brief Query values from the memory and send them to the debug observer.
+         */
+        void pollOnce();
+
+    private:
+        static std::string makeChannelName(const armem::MemoryID& memoryID);
+        static std::string makeDatafieldName(const aron::Path& path);
+
+        armem::client::Reader* getReader(const armem::MemoryID& memoryID);
+
+        Properties properties;
+        Services services;
+
+        std::map<armem::MemoryID, armem::client::Reader> memoryReaders;
+    };
+
+} // namespace armarx::armem::client::util