diff --git a/source/RobotAPI/components/GamepadControlUnit/CMakeLists.txt b/source/RobotAPI/components/GamepadControlUnit/CMakeLists.txt
index c3b764a153b15400568f67eb1dc9c2813874b921..8154ff69af2b057c18189c6ad07262e2a6f33142 100644
--- a/source/RobotAPI/components/GamepadControlUnit/CMakeLists.txt
+++ b/source/RobotAPI/components/GamepadControlUnit/CMakeLists.txt
@@ -1,6 +1,6 @@
 armarx_component_set_name("GamepadControlUnit")
 
-set(COMPONENT_LIBS ArmarXCoreInterfaces ArmarXCore ArmarXCoreObservers RobotAPIInterfaces )
+set(COMPONENT_LIBS ArmarXCoreInterfaces ArmarXCore ArmarXCoreObservers RobotAPIComponentPlugins RobotAPIInterfaces )
 
 set(SOURCES GamepadControlUnit.cpp)
 set(HEADERS GamepadControlUnit.h)
diff --git a/source/RobotAPI/components/GamepadControlUnit/GamepadControlUnit.cpp b/source/RobotAPI/components/GamepadControlUnit/GamepadControlUnit.cpp
index 5227bee38e728999d4a0f7fed6809913144573b6..5113961eeebb95c1af593714fbb1eca93ea84909 100644
--- a/source/RobotAPI/components/GamepadControlUnit/GamepadControlUnit.cpp
+++ b/source/RobotAPI/components/GamepadControlUnit/GamepadControlUnit.cpp
@@ -22,15 +22,15 @@
 
 #include "GamepadControlUnit.h"
 
+#include <ArmarXCore/core/time/DateTime.h>
+#include <ArmarXCore/core/time/Duration.h>
+#include <ArmarXCore/core/time/ice_conversions.h>
 #include <ArmarXCore/observers/variant/TimestampVariant.h>
 
-
-
-
-
 namespace armarx
 {
-    void GamepadControlUnit::onInitComponent()
+    void
+    GamepadControlUnit::onInitComponent()
     {
         ARMARX_INFO << "oninit GamepadControlUnit";
         usingProxy(getProperty<std::string>("PlatformUnitName").getValue());
@@ -41,67 +41,80 @@ namespace armarx
         scaleY = getProperty<float>("ScaleY").getValue();
         scaleRotation = getProperty<float>("ScaleAngle").getValue();
         ARMARX_INFO << "oninit GamepadControlUnit end";
-
-
-
     }
 
-
-    void GamepadControlUnit::onConnectComponent()
+    void
+    GamepadControlUnit::onConnectComponent()
     {
         ARMARX_INFO << "onConnect GamepadControlUnit";
-        platformUnitPrx = getProxy<PlatformUnitInterfacePrx>(getProperty<std::string>("PlatformUnitName").getValue());
+        platformUnitPrx = getProxy<PlatformUnitInterfacePrx>(
+            getProperty<std::string>("PlatformUnitName").getValue());
         emergencyStop = getProxy<EmergencyStopMasterInterfacePrx>("EmergencyStopMaster");
-    }
 
 
-    void GamepadControlUnit::onDisconnectComponent()
-    {
-
+        if (enableHeartBeat)
+        {
+            this->heartbeatPlugin->signUp(armarx::core::time::Duration::MilliSeconds(1000),
+                                    armarx::core::time::Duration::MilliSeconds(1500),
+                                    {"Gamepad"},
+                                    "The GamepadControlUnit");
+        }
     }
 
+    void
+    GamepadControlUnit::onDisconnectComponent()
+    {
+    }
 
-    void GamepadControlUnit::onExitComponent()
+    void
+    GamepadControlUnit::onExitComponent()
     {
         ARMARX_INFO << "exit GamepadControlUnit";
     }
 
-    armarx::PropertyDefinitionsPtr GamepadControlUnit::createPropertyDefinitions()
+    armarx::PropertyDefinitionsPtr
+    GamepadControlUnit::createPropertyDefinitions()
     {
-        auto defs = armarx::PropertyDefinitionsPtr(new GamepadControlUnitPropertyDefinitions(
-                getConfigIdentifier()));
-	defs->topic(robotHealthTopic);
-	defs->optional(enableHeartBeat, "EnableHeartBeat", "Flag to enable send a heart beat to the robot healh topic");
-	return defs;
+        auto defs = armarx::PropertyDefinitionsPtr(
+            new GamepadControlUnitPropertyDefinitions(getConfigIdentifier()));
+        defs->optional(enableHeartBeat,
+                       "EnableHeartBeat",
+                       "Flag to enable send a heart beat to the robot healh topic");
+        return defs;
     }
 
-    void GamepadControlUnit::reportGamepadState(const std::string& device, const std::string& name, const GamepadData& data, const TimestampBasePtr& timestamp, const Ice::Current& c)
+    void
+    GamepadControlUnit::reportGamepadState(const std::string& device,
+                                           const std::string& name,
+                                           const GamepadData& data,
+                                           const TimestampBasePtr& timestamp,
+                                           const Ice::Current& c)
     {
 
 
-       // struct GamepadData {
-       //     float leftStickX;
-       //     float leftStickY;
-       //     float rightStickX;
-       //     float rightStickY;
-       //     float dPadX;
-       //     float dPadY;
-       //     float leftTrigger;
-       //     float rightTrigger;
-       //
-       //     bool leftButton;
-       //     bool rightButton;
-       //     bool backButton;
-       //     bool startButton;
-       //     bool xButton;
-       //     bool yButton;
-       //     bool aButton;
-       //     bool bButton;
-       //     bool theMiddleButton;
-       //     bool leftStickButton;
-       //     bool rightStickButton;
-       //
-       // };
+        // struct GamepadData {
+        //     float leftStickX;
+        //     float leftStickY;
+        //     float rightStickX;
+        //     float rightStickY;
+        //     float dPadX;
+        //     float dPadY;
+        //     float leftTrigger;
+        //     float rightTrigger;
+        //
+        //     bool leftButton;
+        //     bool rightButton;
+        //     bool backButton;
+        //     bool startButton;
+        //     bool xButton;
+        //     bool yButton;
+        //     bool aButton;
+        //     bool bButton;
+        //     bool theMiddleButton;
+        //     bool leftStickButton;
+        //     bool rightStickButton;
+        //
+        // };
 
 
         if (data.leftTrigger > 0)
@@ -116,7 +129,9 @@ namespace armarx
         //scales are for the robot axis
         if (data.rightTrigger > 0)
         {
-            platformUnitPrx->move(data.leftStickY  * scaleX, data. leftStickX  * scaleY, data.rightStickX * scaleRotation);
+            platformUnitPrx->move(data.leftStickY * scaleX,
+                                  data.leftStickX * scaleY,
+                                  data.rightStickX * scaleRotation);
         }
         else
         {
@@ -124,64 +139,68 @@ namespace armarx
         }
 
 
-	if (data.leftButton)
-	{
-
-		if(leftHandTime <= 0.0)
-		{
-			leftHandTime = IceUtil::Time::now().toMicroSeconds();
-		}
-		else if ((IceUtil::Time::now().toMicroSeconds() - leftHandTime) > 1000* 1000)
-		{
-
-			HandUnitInterfacePrx handUnit = getProxy<HandUnitInterfacePrx>("LeftHandUnit");
-			if (handUnit)
-			{
-				std::string shapeName = (leftHandOpen) ? "Close" : "Open";
-				handUnit->setShape(shapeName);
-				leftHandOpen = !leftHandOpen;
-				leftHandTime = 0.0;
-			}
-		}
-	}
-	else
-	{
-		leftHandTime = 0.0;
-	}
-
-	if(data.rightButton)
-	{
-	
-		if(rightHandTime <= 0.0)
-		{
-			rightHandTime = IceUtil::Time::now().toMicroSeconds();
-		}
-		else if ((IceUtil::Time::now().toMicroSeconds() - rightHandTime) > 1000* 1000)
-		{
-			HandUnitInterfacePrx handUnit = getProxy<HandUnitInterfacePrx>("RightHandUnit");
-			if (handUnit)
-			{
-				std::string shapeName = (rightHandOpen) ? "Close" : "Open";
-				handUnit->setShape(shapeName);
-				rightHandOpen = !rightHandOpen;
-				rightHandTime = 0.0;
-			}
-		}
-	}
-	else
-	{
-		rightHandTime = 0.0;
-	}
-
-
-	if(enableHeartBeat)
-	{
-		RobotHealthHeartbeatArgs args;
-        	args.maximumCycleTimeErrorMS = 1000;
-		robotHealthTopic->heartbeat(getDefaultName(), args);
-
-	}
-                
+        if (data.leftButton)
+        {
+
+            if (leftHandTime <= 0.0)
+            {
+                leftHandTime = IceUtil::Time::now().toMicroSeconds();
+            }
+            else if ((IceUtil::Time::now().toMicroSeconds() - leftHandTime) > 1000 * 1000)
+            {
+
+                HandUnitInterfacePrx handUnit = getProxy<HandUnitInterfacePrx>("LeftHandUnit");
+                if (handUnit)
+                {
+                    std::string shapeName = (leftHandOpen) ? "Close" : "Open";
+                    handUnit->setShape(shapeName);
+                    leftHandOpen = !leftHandOpen;
+                    leftHandTime = 0.0;
+                }
+            }
+        }
+        else
+        {
+            leftHandTime = 0.0;
+        }
+
+        if (data.rightButton)
+        {
+
+            if (rightHandTime <= 0.0)
+            {
+                rightHandTime = IceUtil::Time::now().toMicroSeconds();
+            }
+            else if ((IceUtil::Time::now().toMicroSeconds() - rightHandTime) > 1000 * 1000)
+            {
+                HandUnitInterfacePrx handUnit = getProxy<HandUnitInterfacePrx>("RightHandUnit");
+                if (handUnit)
+                {
+                    std::string shapeName = (rightHandOpen) ? "Close" : "Open";
+                    handUnit->setShape(shapeName);
+                    rightHandOpen = !rightHandOpen;
+                    rightHandTime = 0.0;
+                }
+            }
+        }
+        else
+        {
+            rightHandTime = 0.0;
+        }
+
+
+        if (enableHeartBeat)
+        {
+            auto now = armarx::core::time::dto::DateTime();
+            armarx::core::time::toIce(now, armarx::core::time::DateTime::Now());
+            heartbeatPlugin->heartbeat();
+        }
+
         //ARMARX_INFO << "sending targets" << data.leftStickX* scaleX << " " << data.leftStickY* scaleY << " " << data.rightStickX* scaleRotation;
     }
-}
+
+    GamepadControlUnit::GamepadControlUnit()
+    {
+        addPlugin(heartbeatPlugin);
+    }
+} // namespace armarx
diff --git a/source/RobotAPI/components/GamepadControlUnit/GamepadControlUnit.h b/source/RobotAPI/components/GamepadControlUnit/GamepadControlUnit.h
index da5945c44c7e289081df35385ea7e75184d69c90..9438f305fe6a96648eb4d6011112ea637a245369 100644
--- a/source/RobotAPI/components/GamepadControlUnit/GamepadControlUnit.h
+++ b/source/RobotAPI/components/GamepadControlUnit/GamepadControlUnit.h
@@ -30,6 +30,7 @@
 
 #include <RobotAPI/interface/units/HandUnitInterface.h>
 
+#include <RobotAPI/libraries/RobotAPIComponentPlugins/HeartbeatComponentPlugin.h>
 #include <RobotAPI/interface/components/RobotHealthInterface.h>
 #include <RobotAPI/interface/units/PlatformUnitInterface.h>
 
@@ -72,6 +73,8 @@ namespace armarx
         virtual public GamepadUnitListener
     {
     public:
+        GamepadControlUnit();
+        
         /**
          * @see armarx::ManagedIceObject::getDefaultName()
          */
@@ -109,10 +112,11 @@ namespace armarx
     private:
         PlatformUnitInterfacePrx platformUnitPrx;
 
+        plugins::HeartbeatComponentPlugin* heartbeatPlugin = nullptr;
 
 
-	bool enableHeartBeat = false;
-        RobotHealthInterfacePrx robotHealthTopic;
+
+        bool enableHeartBeat = false;
         float scaleX;
         float scaleY;
         float scaleRotation;
diff --git a/source/RobotAPI/components/RobotHealth/RobotHealth.cpp b/source/RobotAPI/components/RobotHealth/RobotHealth.cpp
index 0e4b7f72ac1cc1f5f9b69b678c2d2f790c479184..0daef1ab73ee13ed22d59090270ffe94a6736c44 100644
--- a/source/RobotAPI/components/RobotHealth/RobotHealth.cpp
+++ b/source/RobotAPI/components/RobotHealth/RobotHealth.cpp
@@ -20,296 +20,523 @@
  *             GNU General Public License
  */
 
-#include <ArmarXCore/util/CPPUtility/trace.h>
-
 #include "RobotHealth.h"
+#include "ArmarXCore/core/exceptions/local/ExpressionException.h"
+#include "ArmarXCore/core/logging/Logging.h"
+#include "ArmarXCore/core/time/Clock.h"
+
+#include <SimoxUtility/algorithm/get_map_keys_values.h>
+#include <SimoxUtility/algorithm/string/string_tools.h>
+#include <algorithm>
 
 #include <boost/regex.hpp>
 
+#include <ArmarXCore/core/time/ice_conversions.h>
+#include <ArmarXCore/util/CPPUtility/trace.h>
+#include <optional>
 
 namespace armarx
 {
-    void RobotHealth::onInitComponent()
-    {
-        ARMARX_TRACE;
-        monitorHealthTask = new PeriodicTask<RobotHealth>(this, &RobotHealth::monitorHealthTaskClb, 10);
-        defaultMaximumCycleTimeWarn = getProperty<int>("MaximumCycleTimeWarnMS").getValue();
-        defaultMaximumCycleTimeErr = getProperty<int>("MaximumCycleTimeErrMS").getValue();
-        usingTopic(getProperty<std::string>("RobotHealthTopicName").getValue());
-        reportErrorsWithSpeech = getProperty<bool>("ReportErrorsWithSpeech").getValue();
-        speechMinimumReportInterval = getProperty<int>("SpeechMinimumReportInterval").getValue();
+    inline const std::string PROPERTY_REQUESTER_ID = "required_default";
 
+    void
+    RobotHealth::onInitComponent()
+    {
         ARMARX_TRACE;
-        //robotUnitRequired = getProperty<bool>("RobotUnitRequired").getValue();
-        /*if(robotUnitRequired)
-        {
-            ARMARX_IMPORTANT << "RobotUnit is required";
-            usingProxy(getProperty<std::string>("RobotUnitName").getValue());
-        }*/
+        monitorUpdateHealthTask =
+            new PeriodicTask<RobotHealth>(this, &RobotHealth::monitorHealthUpdateTaskClb, 10);
 
+        defaultMaximumCycleTimeWarn =
+            armarx::core::time::Duration::MilliSeconds(p.maximumCycleTimeWarnMS);
+        defaultMaximumCycleTimeErr =
+            armarx::core::time::Duration::MilliSeconds(p.maximumCycleTimeErrMS);
 
-        //usingProxy(getProperty<std::string>("RemoteGuiName").getValue());
+        const std::vector<std::string> requiredTags = simox::alg::split(p.requiredTags, ",");
+        // a special requester: cannot be changed during runtime.
+        tagsPerRequester[PROPERTY_REQUESTER_ID] = std::set<std::string>(requiredTags.begin(), requiredTags.end());
 
         setDebugObserverBatchModeEnabled(true);
     }
 
-
-    void RobotHealth::onConnectComponent()
+    void
+    RobotHealth::onConnectComponent()
     {
         ARMARX_TRACE;
-        emergencyStopTopicPrx = getTopic<EmergencyStopListenerPrx>(getProperty<std::string>("EmergencyStopTopicName").getValue());
-        //remoteGuiPrx = getProxy<RemoteGuiInterfacePrx>(getProperty<std::string>("RemoteGuiName").getValue());
-        aggregatedRobotHealthTopicPrx = getTopic<AggregatedRobotHealthInterfacePrx>(getProperty<std::string>("AggregatedRobotHealthTopicName").getValue());
-        textToSpeechTopic = getTopic<TextListenerInterfacePrx>(getProperty<std::string>("TextToSpeechTopicName").getValue());
-        lastSpeechOutput = TimeUtil::GetTime();
-
-        /*if(robotUnitRequired)
-        {
-            robotUnitPrx = getTopic<RobotUnitInterfacePrx>(getProperty<std::string>("RobotUnitName").getValue());
-            robotUnitPrx->enableHeartbeat();
-        }*/
 
-        monitorHealthTask->start();
+        monitorUpdateHealthTask->start();
     }
 
-    void RobotHealth::monitorHealthTaskClb()
+    void
+    RobotHealth::monitorHealthUpdateTaskClb()
     {
-        ARMARX_TRACE;
-        long now = TimeUtil::GetTime().toMicroSeconds();
-        bool hasNewErr = false;
+        std::shared_lock lock(updateMutex);
 
-        RobotHealthState healthState = RobotHealthState::HealthOK;
+        ARMARX_TRACE;
+        auto now = armarx::core::time::DateTime::Now();
 
-        for (size_t i = 0; i < validEntries; i++)
+        for (size_t i = 0; i < updateEntries.size(); i++)
         {
             ARMARX_TRACE;
-            Entry& e = entries.at(i);
-            long delta = std::max(now - e.lastUpdate, e.lastDelta);
-            if (e.required && delta > e.maximumCycleTimeErr)
+            UpdateEntry& e = updateEntries.at(i);
+
+            std::unique_lock elock(e.mutex);
+
+            if (!e.enabled)
             {
-                healthState = RobotHealthState::HealthError;
+                continue;
             }
 
-            if (!e.enabled)
+            if(e.history.empty())
             {
                 continue;
             }
-            ARMARX_TRACE;
 
+            auto delta = now - e.history.back();
+
+            ARMARX_TRACE;
 
             if (delta > e.maximumCycleTimeErr)
             {
                 ARMARX_TRACE;
-                healthState = RobotHealthState::HealthError;
-                if (e.isRunning)
+
+                if (e.state != HealthError) // not already in error state
                 {
                     ARMARX_TRACE;
-                    ARMARX_ERROR << deactivateSpam(0.1, e.name) << "Component " << e.name << " has died.";
-                    if (reportErrorsWithSpeech && (TimeUtil::GetTime() - lastSpeechOutput).toSecondsDouble() > speechMinimumReportInterval)
-                    {
-                        ARMARX_TRACE;
-                        lastSpeechOutput = TimeUtil::GetTime();
-
-                        std::string name;
-
-                        boost::smatch m;
-                        boost::regex regex("^[a-zA-Z]+");
-                        if (boost::regex_search(e.name, m, regex))
-                        {
-                            if (m.empty())
-                            {
-                                name = "Whatever";
-                            }
-                            else
-                            {
-                                name = m[0].str();
-                            }
-                        }
-
-                        textToSpeechTopic->reportText("Oh no! Component " + name + " is no longer running.");
-                    }
-                    hasNewErr = true;
-                    e.isRunning = false;
+                    ARMARX_ERROR << deactivateSpam(0.1, e.name) << "Component " << e.name
+                                 << " has died.";
+                    e.state = HealthError;
                 }
             }
-            else if (e.isRunning && delta > e.maximumCycleTimeWarn)
+            else if (delta > e.maximumCycleTimeWarn)
             {
                 ARMARX_TRACE;
-                ARMARX_WARNING << deactivateSpam(0.1, e.name) << "Component " << e.name << " is running too slow.";
-                if (healthState == RobotHealthState::HealthOK)
+                ARMARX_WARNING << deactivateSpam(0.1, e.name) << "Component " << e.name
+                               << " is running too slow.";
+
+                e.state = HealthWarning;
+            }
+            else
+            {
+              // If everything is OK (again), update health
+              e.state = HealthOK;
+            }
+        }
+
+        // update aggregated health
+        RobotHealthState overallHealthState = RobotHealthState::HealthOK;
+
+
+        // get aggregated status
+        for (size_t i = 0; i < updateEntries.size(); i++)
+        {
+            ARMARX_TRACE;
+            UpdateEntry& e = updateEntries.at(i);
+
+            std::shared_lock elock(e.mutex);
+
+            // trim history
+            while (e.history.size() > 20)
+            {
+              e.history.pop_front();
+            }
+
+            if (e.required)
+            {
+                if (e.state == HealthWarning && overallHealthState != HealthError)
+                {
+                    overallHealthState = RobotHealthState::HealthWarning;
+                }
+                else if (e.state == HealthError)
                 {
-                    healthState = RobotHealthState::HealthWarning;
+                    overallHealthState = RobotHealthState::HealthError;
+                }
+            }
+            else
+            {
+                if (e.state != HealthOK && overallHealthState != HealthError)
+                {
+                    overallHealthState = RobotHealthState::HealthWarning;
                 }
             }
-
         }
-        if (hasNewErr)
+
+        if (overallHealthState == HealthError)
         {
             ARMARX_TRACE;
-            emergencyStopTopicPrx->reportEmergencyStopState(EmergencyStopState::eEmergencyStopActive);
+            ARMARX_INFO << "Requesting emergency stop";
+            ARMARX_CHECK_NOT_NULL(p.emergencyStopTopicPrx);
+            p.emergencyStopTopicPrx->reportEmergencyStopState(
+                EmergencyStopState::eEmergencyStopActive);
         }
         ARMARX_TRACE;
-        aggregatedRobotHealthTopicPrx->aggregatedHeartbeat(healthState);
-        /*if(allRequiredRunning && robotUnitPrx)
-        {
-            try
-            {
-                robotUnitPrx->sendHeartbeat();
-            }
-            catch(...)
-            {}
-        }*/
+        ARMARX_CHECK_NOT_NULL(p.aggregatedRobotHealthTopicPrx);
+        p.aggregatedRobotHealthTopicPrx->aggregatedHeartbeat(overallHealthState);
 
         reportDebugObserver();
     }
 
-    void RobotHealth::onDisconnectComponent()
+    void
+    RobotHealth::onDisconnectComponent()
     {
         //robotUnitPrx = nullptr;
-        monitorHealthTask->stop();
+        monitorUpdateHealthTask->stop();
     }
 
+    void
+    RobotHealth::onExitComponent()
+    {
+    }
 
-    void RobotHealth::onExitComponent()
+    armarx::PropertyDefinitionsPtr
+    RobotHealth::createPropertyDefinitions()
     {
+        auto defs = armarx::PropertyDefinitionsPtr(
+            new armarx::ComponentPropertyDefinitions(getConfigIdentifier()));
+
+
+        defs->topic(p.emergencyStopTopicPrx, "EmergencyStop",
+                    "The name of the topic over which changes of the emergencyStopState are sent.");
+        defs->topic<RobotHealthInterfacePrx>(p.robotHealthTopicName, "RobotHealthTopic",
+                                             "Name of the RobotHealth topic");
+
+        defs->topic(p.aggregatedRobotHealthTopicPrx, "AggregatedRobotHealthTopic", "Name of the AggregatedRobotHealthTopic");
+
+        defs->optional(p.maximumCycleTimeWarnMS, "MaximumCycleTimeWarnMS",
+                       "Default value of the maximum cycle time for warnings");
+        defs->optional(p.maximumCycleTimeErrMS, "MaximumCycleTimeErrMS",
+                       "Default value of the maximum cycle time for error");
 
+        defs->optional(p.requiredTags, "RequiredTags", "Tags that should be requested.");
+
+        return defs;
     }
 
-    armarx::PropertyDefinitionsPtr RobotHealth::createPropertyDefinitions()
+    RobotHealth::UpdateEntry*
+    RobotHealth::findUpdateEntry(const std::string& name)
     {
-        return armarx::PropertyDefinitionsPtr(new RobotHealthPropertyDefinitions(
-                getConfigIdentifier()));
+        ARMARX_TRACE;
+
+        for (size_t i = 0; i < updateEntries.size(); i++)
+        {
+            if (updateEntries.at(i).name == name)
+            {
+                return &updateEntries.at(i);
+            }
+        }
+
+        return nullptr;
     }
 
-    RobotHealth::Entry& RobotHealth::findOrCreateEntry(const std::string& name)
+    std::pair<bool, RobotHealth::UpdateEntry&>
+    RobotHealth::findOrCreateUpdateEntry(const std::string& name)
     {
         ARMARX_TRACE;
-        for (size_t i = 0; i < entries.size(); i++)
         {
-            if (entries.at(i).name == name)
+            for (size_t i = 0; i < updateEntries.size(); i++)
             {
-                return entries.at(i);
+                if (updateEntries.at(i).name == name)
+                {
+                    return {false, updateEntries.at(i)};
+                }
             }
         }
-        std::unique_lock lock(mutex);
-        ARMARX_INFO << "registering '" << name << "'";
-        entries.emplace_back(name, defaultMaximumCycleTimeWarn * 1000, defaultMaximumCycleTimeErr * 1000);
-        validEntries++;
-        return entries.back();
-    }
 
+        ARMARX_INFO << "registering for heartbeat: '" << name << "'";
 
+        updateEntries.emplace_back(name, defaultMaximumCycleTimeWarn, defaultMaximumCycleTimeErr);
 
-    void RobotHealth::heartbeat(const std::string& componentName, const RobotHealthHeartbeatArgs& args, const Ice::Current&)
+        return {true, updateEntries.back()};
+    }
+
+    void
+    RobotHealth::signUp(const RobotHealthHeartbeatArgs& args, const Ice::Current& current)
     {
         ARMARX_TRACE;
-        Entry& e = findOrCreateEntry(componentName);
-        long now = TimeUtil::GetTime().toMicroSeconds();
-        e.maximumCycleTimeWarn = args.maximumCycleTimeWarningMS * 1000;
-        e.maximumCycleTimeErr = args.maximumCycleTimeErrorMS * 1000;
-        if (!e.isRunning)
+        std::unique_lock lockU(updateMutex);
+
+        auto e = findOrCreateUpdateEntry(args.identifier);
+
         {
-            ARMARX_INFO << "'" << componentName << "' is now alive and running";
-            e.lastDelta = 0;
+            std::unique_lock lock(e.second.mutex);
+
+            if (e.first)
+            {
+                ARMARX_INFO << "Newly registering component " << args.identifier << " for updates.";
+            }
+            // else no messsage as components may spam sign up for legacy reasons (when there was no sign up)
+
+            armarx::core::time::fromIce(args.maximumCycleTimeWarning, e.second.maximumCycleTimeWarn);
+            armarx::core::time::fromIce(args.maximumCycleTimeError, e.second.maximumCycleTimeErr);
+
+            e.second.tags = args.tags;
+            // e.second.required = args.requiredByDefault; // FIXME
+            e.second.description = args.description;
+            e.second.enabled = true;
+            e.second.state = HealthOK;
+            e.second.history.clear();
         }
-        else
+
+        updateRequiredElements();
+    }
+
+    void
+    RobotHealth::heartbeat(const std::string& identifier,
+                            const core::time::dto::DateTime& /*referenceTime*/,
+                            const Ice::Current& current)
+    {
+        ARMARX_TRACE;
+        ARMARX_VERBOSE << "Finding update entry";
+
+        // We hold a reference to 'o' which is an element in a vector.
+        // If we don't lock until the end of this scope, the vector size might change and 'o' will be invalidated. 
+        std::shared_lock lockU(updateMutex);  
+        auto* const entry = findUpdateEntry(identifier);
+
+        if (entry == nullptr)
         {
-            e.lastDelta = now - e.lastUpdate;
-            e.history.at(e.historyIndex) = e.lastDelta;
-            e.historyIndex = (e.historyIndex + 1) % e.history.size();
+            ARMARX_WARNING << "Attention. Component `" << identifier 
+                           << "` was not signed up for heartbeat. Ignoring heartbeat for now...";
+            return;
         }
-        ARMARX_TRACE;
-        e.lastUpdate = now;
-        e.isRunning = true;
-        e.enabled = true;
+
+        ARMARX_CHECK_NOT_NULL(entry);
+
+        std::unique_lock lock(entry->mutex);
+
+        if (not entry->enabled)
         {
-            std::unique_lock lock(e.messageMutex);
-            e.message = args.message;
+            ARMARX_WARNING << deactivateSpam() << "Attention. Component `" << identifier
+                           << "` was not enabled. The heartbeat is ignored... ";
+            return;
         }
-    }
 
-    void armarx::RobotHealth::unregister(const std::string& componentName, const Ice::Current&)
-    {
-        ARMARX_TRACE;
-        Entry& e = findOrCreateEntry(componentName);
-        e.isRunning = false;
-        e.enabled = false;
-        ARMARX_INFO << "unregistering " << componentName;
+        ARMARX_VERBOSE << "Extending history";
+        // auto now = armarx::core::time::DateTime::Now();
+
+
+        // armarx::core::time::DateTime timestamp;
+        // fromIce(referenceTime, timestamp);
+        const DateTime timestamp = Clock::Now();
+        entry->history.push_back(timestamp);
     }
 
-    std::string RobotHealth::getSummary(const Ice::Current&)
+    void
+    armarx::RobotHealth::unregister(const std::string& componentName, const Ice::Current&)
     {
         ARMARX_TRACE;
-        long now = TimeUtil::GetTime().toMicroSeconds();
-        std::stringstream ss;
-        for (size_t i = 0; i < validEntries; i++)
+        std::shared_lock lock(updateMutex);
+
+        bool found = false;
         {
-            ARMARX_TRACE;
-            Entry& e = entries.at(i);
-            long delta = std::max(now - e.lastUpdate, e.lastDelta);
+            std::shared_lock lock(updateMutex);
 
-            long minDelta = delta;
-            long maxDelta = delta;
-            for (size_t i = 1; i < e.history.size(); i++)
+            for (size_t i = 0; i < updateEntries.size(); i++)
             {
-                long historicalDelta = e.history.at(i);
-                if (historicalDelta < 0)
+                auto& e = updateEntries.at(i);
+                if (e.name == componentName)
                 {
+                    std::unique_lock elock(e.mutex);
+                    e.required = false;
+                    e.enabled = false;
+                    found = true;
                     break;
                 }
-                minDelta = std::min(minDelta, historicalDelta);
-                maxDelta = std::max(maxDelta, historicalDelta);
             }
+            // Todo upgrade lock?
+        }
 
-            ss << e.name;
-            if (e.required)
+        if (found)
+        {
+            ARMARX_INFO << "Component " << componentName << " disabled from heartbeat";
+        }
+        else
+        {
+            ARMARX_INFO << "Could not unregister component " << componentName << ". Not found.";
+        }
+
+        updateRequiredElements();
+    }
+
+    void
+    armarx::RobotHealth::addRequiredTags(const std::string& requesterIdentifier,
+                     const std::vector<std::string>& tags,
+                     const Ice::Current& current)
+    {
+        std::shared_lock lock(updateMutex);
+
+        // add newly requested tags
+        for(const auto& tag : tags)
+        {
+            tagsPerRequester[requesterIdentifier].insert(tag);
+        }
+
+        updateRequiredElements();
+    }
+
+    std::set<std::string>
+    armarx::RobotHealth::requestedTags() const
+    {
+        // obtain a list of all tags that are relevant / requested at the moment
+        std::set<std::string> allRequestedTags;
+        for(const auto& tgs: simox::alg::get_values(tagsPerRequester))
+        {
+            for(const auto& tag : tgs)
             {
-                ss << ", required";
+                allRequestedTags.insert(tag);
             }
+        } 
+        return allRequestedTags;
+    }
 
-            if (!e.required && !e.enabled)
-            {
-                ss << ": disabled";
+    // checks whether any element in 'search' is in 'values'
+    bool containsAnyOf(const std::set<std::string>& values, const std::vector<std::string>& searchKeys)
+    {
+        return std::any_of(searchKeys.begin(), searchKeys.end(), [&values](const auto& key){
+            return values.count(key) > 0;
+        });
+    }
+
+    void armarx::RobotHealth::updateRequiredElements()
+    {
+        ARMARX_IMPORTANT << "updateRequiredElements";
+        const std::set<std::string> allRequestedTags = requestedTags();
+
+        for (auto &e : updateEntries) {
+            ARMARX_INFO << e.name;
+
+            e.required = false; // if required, will be set in the following
+
+            std::unique_lock lock(e.mutex);
+
+            if (containsAnyOf(allRequestedTags, e.tags)) {
+                ARMARX_INFO << e.name << " is required.";
+                e.required = true;
+                if (not e.enabled) {
+                    ARMARX_WARNING << "You are requiring the disabled component "
+                                << e.name << ". Enabling it...";
+                    e.enabled = true;
+                }
             }
-            else
+        }
+    }
+
+    void
+    armarx::RobotHealth::removeRequiredTags(const std::string& requesterIdentifier,
+                       const std::vector<std::string>& tags,
+                       const Ice::Current& current)
+    {
+        std::shared_lock lock(updateMutex);
+
+        // update the required tags list / map
+        for(const auto& tag : tags)
+        {
+            tagsPerRequester[requesterIdentifier].erase(tag);
+        }
+
+        updateRequiredElements();
+    }
+
+    void
+    armarx::RobotHealth::resetRequiredTags(const Ice::Current& current)
+    {
+        std::shared_lock lock(updateMutex);
+
+        // treat the special provider (see onInitComponent()) appropriately  
+        ARMARX_CHECK(tagsPerRequester.count(PROPERTY_REQUESTER_ID) > 0);
+        const auto propertyProviderTags = tagsPerRequester.at(PROPERTY_REQUESTER_ID);
+
+        // clear everything except the special provider
+        tagsPerRequester.clear();
+        tagsPerRequester.emplace(PROPERTY_REQUESTER_ID, propertyProviderTags);
+    }
+
+    RobotHealthInfo
+    RobotHealth::getSummary(const Ice::Current&)
+    {
+        std::shared_lock lock(updateMutex);
+
+        ARMARX_TRACE;
+        RobotHealthInfo ret;
+
+        auto now = armarx::core::time::DateTime::Now();
+
+        armarx::core::time::Duration minDelta;
+        armarx::core::time::Duration maxDelta;
+
+        for (const auto& e : updateEntries)
+        {
+            RobotHealthInfoEntry healthEntry;
+
+            for (size_t i = 1; i < e.history.size(); i++)
             {
-                if (delta > e.maximumCycleTimeErr)
-                {
-                    ss << ": ERROR";
-                }
-                else if (delta > e.maximumCycleTimeWarn)
+                auto later = e.history[i];
+                auto pre = e.history[i - 1];
+
+                auto delta = later - pre;
+
+                if (minDelta > delta)
                 {
-                    ss << ": warning";
+                    minDelta = delta;
                 }
-                else
+
+                if (maxDelta < delta)
                 {
-                    ss << ": ok";
+                    maxDelta = delta;
                 }
-                ss << " (" << (delta / 1000) << "ms, min: " << (minDelta / 1000) << "ms, max: " << (maxDelta / 1000) << "ms)";
             }
-            ARMARX_TRACE;
 
-            std::unique_lock lock(e.messageMutex);
-            if (e.message.size())
-            {
-                ss << " - " << e.message;
-            }
+            const Duration timeSinceLastUpdate = e.history.empty() ? Duration() : Clock::Now() - e.history.back();
 
-            ss << "\n";
+            healthEntry.identifier = e.name;
+            healthEntry.state = e.state;
+            healthEntry.enabled = e.enabled;
+            healthEntry.required = e.required;
+            toIce(healthEntry.minDelta, minDelta);
+            toIce(healthEntry.maxDelta,maxDelta);
+            toIce(healthEntry.timeSinceLastUpdate, timeSinceLastUpdate);
+            toIce(healthEntry.maximumCycleTimeWarning, e.maximumCycleTimeWarn);
+            toIce(healthEntry.maximumCycleTimeError, e.maximumCycleTimeErr);
+            healthEntry.tags = e.tags;
 
+            ret.entries[e.name] = healthEntry;
         }
-        return ss.str();
+
+        const auto tags = requestedTags();
+        ret.activeTags = std::vector<std::string>(tags.begin(), tags.end());
+
+        return ret;
     }
 
-    void RobotHealth::reportDebugObserver()
+    std::string
+    RobotHealth::getTopicName(const Ice::Current& current)
     {
-        for(const auto& entry: entries)
+      return p.robotHealthTopicName;
+    }
+
+    void
+    RobotHealth::reportDebugObserver()
+    {
+        for (const auto& entry : updateEntries)
         {
-            setDebugObserverDatafield("RobotHealth_" + entry.name + "_lastDelta", entry.lastDelta);
-            setDebugObserverDatafield("RobotHealth_" + entry.name + "_maximumCycleTimeWarn", entry.maximumCycleTimeWarn);
-            setDebugObserverDatafield("RobotHealth_" + entry.name + "_maximumCycleTimeErr", entry.maximumCycleTimeErr);
+            if (entry.history.size() > 1)
+            {
+                auto later = entry.history[entry.history.size() - 1];
+                auto pre = entry.history[entry.history.size() - 2];
+                const Duration delta = later - pre;
+
+                const Duration deltaToNow = Clock::Now() - entry.history.back();
+
+                setDebugObserverDatafield("RobotHealth_" + entry.name + "_lastDelta",
+                                          delta.toMilliSecondsDouble());
+                setDebugObserverDatafield("RobotHealth_" + entry.name + "_deltaToNow",
+                                          deltaToNow.toMilliSecondsDouble());
+                setDebugObserverDatafield("RobotHealth_" + entry.name + "_maximumCycleTimeWarn",
+                                          entry.maximumCycleTimeWarn.toMilliSecondsDouble());
+                setDebugObserverDatafield("RobotHealth_" + entry.name + "_maximumCycleTimeErr",
+                                          entry.maximumCycleTimeErr.toMilliSecondsDouble());
+            }
         }
 
         sendDebugObserverBatch();
     }
-}
+} // namespace armarx
diff --git a/source/RobotAPI/components/RobotHealth/RobotHealth.h b/source/RobotAPI/components/RobotHealth/RobotHealth.h
index 3da12c1797ffa0afbc9af7310ec40953ecc51309..899c237647f8c381bab41dd4db35d96dbe6499a4 100644
--- a/source/RobotAPI/components/RobotHealth/RobotHealth.h
+++ b/source/RobotAPI/components/RobotHealth/RobotHealth.h
@@ -22,54 +22,28 @@
 
 #pragma once
 
-#include <RobotAPI/interface/components/RobotHealthInterface.h>
-#include <RobotAPI/interface/units/RobotUnit/RobotUnitInterface.h>
-#include <RobotAPI/interface/speech/SpeechInterface.h>
-
-#include <ArmarXGui/interface/RemoteGuiInterface.h>
+#include <array>
+#include <atomic>
+#include <deque>
+#include <mutex>
 
-#include <ArmarXCore/core/time/TimeUtil.h>
 #include <ArmarXCore/core/Component.h>
 #include <ArmarXCore/core/services/tasks/PeriodicTask.h>
+#include <ArmarXCore/core/time/DateTime.h>
+#include <ArmarXCore/core/time/Duration.h>
+#include <ArmarXCore/core/time/Frequency.h>
+#include <ArmarXCore/core/time/TimeUtil.h>
 #include <ArmarXCore/interface/components/EmergencyStopInterface.h>
 #include <ArmarXCore/libraries/ArmarXCoreComponentPlugins/DebugObserverComponentPlugin.h>
 
-#include <atomic>
-#include <mutex>
+#include <ArmarXGui/interface/RemoteGuiInterface.h>
+
+#include <RobotAPI/interface/components/RobotHealthInterface.h>
+#include <RobotAPI/interface/speech/SpeechInterface.h>
+#include <RobotAPI/interface/units/RobotUnit/RobotUnitInterface.h>
 
 namespace armarx
 {
-    /**
-     * @class RobotHealthPropertyDefinitions
-     * @brief
-     */
-    class RobotHealthPropertyDefinitions:
-        public armarx::ComponentPropertyDefinitions
-    {
-    public:
-        RobotHealthPropertyDefinitions(std::string prefix):
-            armarx::ComponentPropertyDefinitions(prefix)
-        {
-            defineOptionalProperty<std::string>("EmergencyStopTopicName", "EmergencyStop", "The name of the topic over which changes of the emergencyStopState are sent.");
-            defineOptionalProperty<std::string>("RobotHealthTopicName", "RobotHealthTopic", "Name of the RobotHealth topic");
-            defineOptionalProperty<bool>("ReportErrorsWithSpeech", true, "");
-            defineOptionalProperty<std::string>("TextToSpeechTopicName", "TextToSpeech", "Name of the TextToSpeech topic");
-            defineOptionalProperty<int>("MaximumCycleTimeWarnMS", 50, "Default value of the maximum cycle time for warnings");
-            defineOptionalProperty<int>("MaximumCycleTimeErrMS", 100, "Default value of the maximum cycle time for error");
-            defineOptionalProperty<std::string>("AggregatedRobotHealthTopicName", "AggregatedRobotHealthTopic", "Name of the AggregatedRobotHealthTopic");
-            defineOptionalProperty<std::string>("RequiredComponents", "", "Comma separated list of required components");
-
-            defineOptionalProperty<int>("SpeechMinimumReportInterval", 60, "Time that has to pass between reported messages in seconds.");
-
-            //defineOptionalProperty<std::string>("RemoteGuiName", "RemoteGuiProvider", "Name of the remote GUI provider");
-            //defineOptionalProperty<std::string>("RobotUnitName", "Armar6Unit", "Name of the RobotUnit");
-            //defineOptionalProperty<bool>("RobotUnitRequired", true, "Wait for RobotUnit");
-
-            //defineRequiredProperty<std::string>("PropertyName", "Description");
-            //defineOptionalProperty<std::string>("PropertyName", "DefaultValue", "Description");
-        }
-    };
-
     /**
      * @defgroup Component-RobotHealth RobotHealth
      * @ingroup RobotAPI-Components
@@ -90,34 +64,12 @@ namespace armarx
         /**
          * @see armarx::ManagedIceObject::getDefaultName()
          */
-        std::string getDefaultName() const override
+        std::string
+        getDefaultName() const override
         {
             return "RobotHealth";
         }
 
-        struct Entry
-        {
-            Entry(std::string name, long maximumCycleTimeWarn, long maximumCycleTimeErr)
-                : name(name), maximumCycleTimeWarn(maximumCycleTimeWarn), maximumCycleTimeErr(maximumCycleTimeErr), history(100, -1)
-            {}
-            Entry(Entry&&) = default;
-            Entry& operator=(Entry&&) = default;
-
-            std::string name;
-            long maximumCycleTimeWarn;
-            long maximumCycleTimeErr;
-            long lastUpdate = 0;
-            long lastDelta = 0;
-            RobotHealthState state = HealthOK;
-            std::string message = "";
-            bool isRunning = false;
-            bool required = false;
-            bool enabled = true;
-            std::mutex messageMutex;
-            std::vector<long> history;
-            size_t historyIndex = 0;
-        };
-
     protected:
         /**
          * @see armarx::ManagedIceObject::onInitComponent()
@@ -144,32 +96,90 @@ namespace armarx
          */
         armarx::PropertyDefinitionsPtr createPropertyDefinitions() override;
 
-        void monitorHealthTaskClb();
-        Entry& findOrCreateEntry(const std::string& name);
+    private:
+        struct UpdateEntry
+        {
+            UpdateEntry(const std::string& name,
+                        const armarx::core::time::Duration& maximumCycleTimeWarn,
+                        const armarx::core::time::Duration& maximumCycleTimeErr) :
+                name(name),
+                maximumCycleTimeWarn(maximumCycleTimeWarn),
+                maximumCycleTimeErr(maximumCycleTimeErr)
+            {
+            }
 
-        void reportDebugObserver();
+            std::string name;
+            std::vector<std::string> tags;
+            RobotHealthState state = HealthOK;
+            std::string description = "";
+
+            bool required = false;
+            bool enabled = false;
+
+            mutable std::shared_mutex mutex;
+            std::deque<armarx::core::time::DateTime> history;
+
+            armarx::core::time::Duration maximumCycleTimeWarn;
+            armarx::core::time::Duration maximumCycleTimeErr;
+        };
+
+        void monitorHealthUpdateTaskClb();
+
+        UpdateEntry* findUpdateEntry(const std::string& name);
+        std::pair<bool, UpdateEntry&> findOrCreateUpdateEntry(const std::string& name);
 
-        std::mutex mutex;
-        std::deque<Entry> entries;
-        std::atomic_size_t validEntries {0};
-        PeriodicTask<RobotHealth>::pointer_type monitorHealthTask;
-        int defaultMaximumCycleTimeWarn;
-        int defaultMaximumCycleTimeErr;
-        EmergencyStopListenerPrx emergencyStopTopicPrx;
-        //RobotUnitInterfacePrx robotUnitPrx;
-        //bool robotUnitRequired;
-        RemoteGuiInterfacePrx remoteGuiPrx;
-        AggregatedRobotHealthInterfacePrx aggregatedRobotHealthTopicPrx;
-        TextListenerInterfacePrx textToSpeechTopic;
-        bool reportErrorsWithSpeech;
-        int speechMinimumReportInterval;
-        IceUtil::Time lastSpeechOutput;
+        void reportDebugObserver();
 
         // RobotHealthInterface interface
     public:
-        void heartbeat(const std::string& componentName, const RobotHealthHeartbeatArgs& args, const Ice::Current&) override;
-        void unregister(const std::string& componentName, const Ice::Current&) override;
-        std::string getSummary(const Ice::Current&) override;
+        void signUp(const RobotHealthHeartbeatArgs& args, const Ice::Current& current) override;
+        void unregister(const std::string& identifier, const Ice::Current&) override;
+        
+
+        void addRequiredTags(const std::string& requesterIdentifier,
+                     const std::vector<std::string>& tags,
+                     const Ice::Current& current) override;
+        void removeRequiredTags(const std::string& requesterIdentifier,
+                       const std::vector<std::string>& tags,
+                       const Ice::Current& current) override;
+        void resetRequiredTags(const Ice::Current& current) override;
+
+
+        void heartbeat(const std::string& identifier,
+                       const core::time::dto::DateTime& referenceTime,
+                       const Ice::Current& current) override;
+
+        std::string getTopicName(const Ice::Current& current) override;
+
+        // RobotHealthComponentInterface interface
+    public:
+        RobotHealthInfo getSummary(const Ice::Current& current) override;
 
+    private:
+        void updateRequiredElements();
+        std::set<std::string> requestedTags() const;
+
+        mutable std::shared_mutex updateMutex;
+
+        std::deque<UpdateEntry> updateEntries;
+
+        std::map<std::string, std::set<std::string>> tagsPerRequester;
+
+        PeriodicTask<RobotHealth>::pointer_type monitorUpdateHealthTask;
+
+        armarx::core::time::Duration defaultMaximumCycleTimeWarn;
+        armarx::core::time::Duration defaultMaximumCycleTimeErr;
+
+        struct Properties
+        {
+            EmergencyStopListenerPrx emergencyStopTopicPrx;
+            std::string robotHealthTopicName = "RobotHealthTopic";
+            AggregatedRobotHealthInterfacePrx aggregatedRobotHealthTopicPrx;
+            RemoteGuiInterfacePrx remoteGuiPrx;
+            long maximumCycleTimeWarnMS = 50;
+            long maximumCycleTimeErrMS = 100;
+
+            std::string requiredTags;
+        } p;
     };
-}
+} // namespace armarx
diff --git a/source/RobotAPI/components/RobotHealth/RobotHealthDummy.cpp b/source/RobotAPI/components/RobotHealth/RobotHealthDummy.cpp
index b239fdd0719716c1e579a803a6a89265168240c3..e028bc55648f02446b5980cc00124950d4564219 100644
--- a/source/RobotAPI/components/RobotHealth/RobotHealthDummy.cpp
+++ b/source/RobotAPI/components/RobotHealth/RobotHealthDummy.cpp
@@ -21,26 +21,35 @@
  */
 
 #include "RobotHealthDummy.h"
+
 #include <time.h>
+
 #include <thread>
 
+#include <ArmarXCore/core/time/DateTime.h>
+#include <ArmarXCore/core/time/ice_conversions.h>
+
 namespace armarx
 {
-    void RobotHealthDummy::onInitComponent()
+    void
+    RobotHealthDummy::onInitComponent()
     {
         dummyTask = new RunningTask<RobotHealthDummy>(this, &RobotHealthDummy::runTask);
         getProperty(sleepmode, "SleepMode");
     }
 
-
-    void RobotHealthDummy::onConnectComponent()
+    void
+    RobotHealthDummy::onConnectComponent()
     {
-        robotHealthTopicPrx = getTopic<RobotHealthInterfacePrx>(getProperty<std::string>("RobotHealthTopicName").getValue());
+        robotHealthTopicPrx = getTopic<RobotHealthInterfacePrx>(
+            getProperty<std::string>("RobotHealthTopicName").getValue());
         dummyTask->start();
     }
 
-#define NANOSECS_PER_SEC  1000000000
-    int RobotHealthDummy::NanoSleep(long nanosec)
+#define NANOSECS_PER_SEC 1000000000
+
+    int
+    RobotHealthDummy::NanoSleep(long nanosec)
     {
         struct timespec ts;
         ts.tv_sec = nanosec / NANOSECS_PER_SEC;
@@ -48,48 +57,55 @@ namespace armarx
         return nanosleep(&ts, nullptr); // jitter up to +100ms!
     }
 
-    void RobotHealthDummy::sleepwait(long microseconds)
+    void
+    RobotHealthDummy::sleepwait(long microseconds)
     {
         long start = TimeUtil::GetTime().toMicroSeconds();
         auto end = start + microseconds;
         do
         {
             NanoSleep(1000);
-        }
-        while (TimeUtil::GetTime().toMicroSeconds() < end);
+        } while (TimeUtil::GetTime().toMicroSeconds() < end);
     }
-    void RobotHealthDummy::yieldwait(long microseconds)
+
+    void
+    RobotHealthDummy::yieldwait(long microseconds)
     {
         long start = TimeUtil::GetTime().toMicroSeconds();
         auto end = start + microseconds;
         do
         {
             std::this_thread::yield();
-        }
-        while (TimeUtil::GetTime().toMicroSeconds() < end); // jitter up to +25ms...
+        } while (TimeUtil::GetTime().toMicroSeconds() < end); // jitter up to +25ms...
     }
-    void RobotHealthDummy::busydwait(long microseconds)
+
+    void
+    RobotHealthDummy::busydwait(long microseconds)
     {
         long start = TimeUtil::GetTime().toMicroSeconds();
         auto end = start + microseconds;
         do
         {
             ;
-        }
-        while (TimeUtil::GetTime().toMicroSeconds() < end);
+        } while (TimeUtil::GetTime().toMicroSeconds() < end);
     }
 
-    void RobotHealthDummy::runTask()
+    void
+    RobotHealthDummy::runTask()
     {
-
-
+        auto args = RobotHealthHeartbeatArgs();
+        args.identifier = getName();
+        robotHealthTopicPrx->signUp(args);
 
         ARMARX_INFO << "starting rinning task";
         while (!dummyTask->isStopped())
         {
             long beforeTopicCall = TimeUtil::GetTime().toMicroSeconds();
             //ARMARX_INFO << "send heartbeat";
-            robotHealthTopicPrx->heartbeat(getName(), RobotHealthHeartbeatArgs());
+
+            armarx::core::time::dto::DateTime now;
+            armarx::core::time::toIce(now, armarx::core::time::DateTime::Now());
+            robotHealthTopicPrx->heartbeat(getName(), now);
             long afterTopicCall = TimeUtil::GetTime().toMicroSeconds();
             if (sleepmode == "nanosleep")
             {
@@ -126,23 +142,24 @@ namespace armarx
         }
     }
 
-
-    void RobotHealthDummy::onDisconnectComponent()
+    void
+    RobotHealthDummy::onDisconnectComponent()
     {
         //ARMARX_IMPORTANT << "onDisconnectComponent";
         dummyTask->stop();
     }
 
-
-    void RobotHealthDummy::onExitComponent()
+    void
+    RobotHealthDummy::onExitComponent()
     {
         //ARMARX_IMPORTANT << "onExitComponent";
         dummyTask->stop();
     }
 
-    armarx::PropertyDefinitionsPtr RobotHealthDummy::createPropertyDefinitions()
+    armarx::PropertyDefinitionsPtr
+    RobotHealthDummy::createPropertyDefinitions()
     {
-        return armarx::PropertyDefinitionsPtr(new RobotHealthDummyPropertyDefinitions(
-                getConfigIdentifier()));
+        return armarx::PropertyDefinitionsPtr(
+            new RobotHealthDummyPropertyDefinitions(getConfigIdentifier()));
     }
-}
+} // namespace armarx
diff --git a/source/RobotAPI/components/RobotHealth/RobotHealthDummy.h b/source/RobotAPI/components/RobotHealth/RobotHealthDummy.h
index cb9d46377437764fac06cb4a9535d1adabb5534c..cfd8d369d0335768c2e85dd735f1d31d1e30e60d 100644
--- a/source/RobotAPI/components/RobotHealth/RobotHealthDummy.h
+++ b/source/RobotAPI/components/RobotHealth/RobotHealthDummy.h
@@ -24,10 +24,10 @@
 
 
 #include <ArmarXCore/core/Component.h>
-#include <RobotAPI/interface/components/RobotHealthInterface.h>
 #include <ArmarXCore/core/services/tasks/RunningTask.h>
 #include <ArmarXCore/core/time/TimeUtil.h>
 
+#include <RobotAPI/interface/components/RobotHealthInterface.h>
 
 namespace armarx
 {
@@ -35,15 +35,18 @@ namespace armarx
      * @class RobotHealthDummyPropertyDefinitions
      * @brief
      */
-    class RobotHealthDummyPropertyDefinitions:
-        public armarx::ComponentPropertyDefinitions
+    class RobotHealthDummyPropertyDefinitions : public armarx::ComponentPropertyDefinitions
     {
     public:
-        RobotHealthDummyPropertyDefinitions(std::string prefix):
+        RobotHealthDummyPropertyDefinitions(std::string prefix) :
             armarx::ComponentPropertyDefinitions(prefix)
         {
-            defineOptionalProperty<std::string>("RobotHealthTopicName", "RobotHealthTopic", "Name of the RobotHealth topic");
-            defineOptionalProperty<std::string>("SleepMode", "nanosleep", "Which sleep function to use: nanosleep, sleepwait, yieldwait, busywait");
+            defineOptionalProperty<std::string>(
+                "RobotHealthTopicName", "RobotHealthTopic", "Name of the RobotHealth topic");
+            defineOptionalProperty<std::string>(
+                "SleepMode",
+                "nanosleep",
+                "Which sleep function to use: nanosleep, sleepwait, yieldwait, busywait");
 
             //defineRequiredProperty<std::string>("PropertyName", "Description");
             //defineOptionalProperty<std::string>("PropertyName", "DefaultValue", "Description");
@@ -61,14 +64,14 @@ namespace armarx
      *
      * Detailed description of class RobotHealthDummy.
      */
-    class RobotHealthDummy :
-        virtual public armarx::Component
+    class RobotHealthDummy : virtual public armarx::Component
     {
     public:
         /**
          * @see armarx::ManagedIceObject::getDefaultName()
          */
-        std::string getDefaultName() const override
+        std::string
+        getDefaultName() const override
         {
             return "RobotHealthDummy";
         }
@@ -77,6 +80,7 @@ namespace armarx
         void yieldwait(long microseconds);
         void busydwait(long microseconds);
         void sleepwait(long microseconds);
+
     protected:
         /**
          * @see armarx::ManagedIceObject::onInitComponent()
@@ -109,6 +113,5 @@ namespace armarx
         RunningTask<RobotHealthDummy>::pointer_type dummyTask;
 
         std::string sleepmode;
-
     };
-}
+} // namespace armarx
diff --git a/source/RobotAPI/drivers/HokuyoLaserUnit/HokuyoLaserUnit.cpp b/source/RobotAPI/drivers/HokuyoLaserUnit/HokuyoLaserUnit.cpp
index 52af2c8dd81679509f7aaf965caa91e3b49349eb..2a1f02718bb8b1fa9465445086238a4e04ba92dd 100644
--- a/source/RobotAPI/drivers/HokuyoLaserUnit/HokuyoLaserUnit.cpp
+++ b/source/RobotAPI/drivers/HokuyoLaserUnit/HokuyoLaserUnit.cpp
@@ -22,17 +22,25 @@
 
 #include "HokuyoLaserUnit.h"
 
-#include <ArmarXCore/observers/variant/TimestampVariant.h>
-
 #include <SimoxUtility/algorithm/string/string_tools.h>
 
-#include <HokuyoLaserScannerDriver/urg_utils.h>
+#include <ArmarXCore/core/time/DateTime.h>
+#include <ArmarXCore/core/time/Duration.h>
+#include <ArmarXCore/core/time/ice_conversions.h>
+#include <ArmarXCore/observers/variant/TimestampVariant.h>
 
+#include <HokuyoLaserScannerDriver/urg_utils.h>
 
 using namespace armarx;
 
+HokuyoLaserUnit::HokuyoLaserUnit()
+{
+    addPlugin(heartbeat);
+}
+
 
-void HokuyoLaserUnit::onInitComponent()
+void
+HokuyoLaserUnit::onInitComponent()
 {
     offeringTopicFromProperty("RobotHealthTopicName");
     offeringTopicFromProperty("DebugObserverName");
@@ -72,19 +80,21 @@ void HokuyoLaserUnit::onInitComponent()
         }
         catch (std::exception const& ex)
         {
-            ARMARX_WARNING << "Could not convert port to integer for laser scanner device " << deviceString
-                           << " (port is " << deviceInfo[1] << ") : " << ex.what();
+            ARMARX_WARNING << "Could not convert port to integer for laser scanner device "
+                           << deviceString << " (port is " << deviceInfo[1] << ") : " << ex.what();
             continue;
         }
     }
 }
 
-
-void HokuyoLaserUnit::onConnectComponent()
+void
+HokuyoLaserUnit::onConnectComponent()
 {
-    robotHealthTopic = getTopic<RobotHealthInterfacePrx>(getProperty<std::string>("RobotHealthTopicName").getValue());
+    ARMARX_TRACE;
+
     topic = getTopic<LaserScannerUnitListenerPrx>(topicName);
-    debugObserver = getTopic<DebugObserverInterfacePrx>(getProperty<std::string>("DebugObserverName").getValue());
+    debugObserver = getTopic<DebugObserverInterfacePrx>(
+        getProperty<std::string>("DebugObserverName").getValue());
 
     connectedDevices.clear();
     for (HokuyoLaserScanDevice& device : devices)
@@ -95,12 +105,21 @@ void HokuyoLaserUnit::onConnectComponent()
             device.task = nullptr;
         }
 
+        // connecting devices for first time
         if (!device.reconnect())
         {
-            ARMARX_WARNING << "Not starting task for laser scanner with IP: " << device.ip << ", Port: " << device.port;
+            ARMARX_WARNING << "Not starting task for laser scanner with IP: " << device.ip
+                           << ", Port: " << device.port;
             continue;
         }
 
+        ARMARX_CHECK_NOT_NULL(heartbeat);
+        heartbeat->signUp(device.ip,
+                          armarx::core::time::Duration::MilliSeconds(500),
+                          armarx::core::time::Duration::MilliSeconds(800),
+                          {"LaserScanner", "Localization"},
+                          "HokuyoLaserScanDevice");
+
         LaserScannerInfo info;
         info.device = device.ip;
         info.frame = device.frame;
@@ -115,23 +134,22 @@ void HokuyoLaserUnit::onConnectComponent()
         device.lengthData.resize(lengthDataSize);
 
         device.scanTopic = topic;
-        device.robotHealthTopic = robotHealthTopic;
+        device.robotHealthPlugin = heartbeat;
         device.debugObserver = debugObserver;
 
         connectedDevices.push_back(info);
 
-        ARMARX_INFO << "Connected to " << device.ip << ", " << info.frame << ", "
-                    << info.minAngle << ", " << info.maxAngle << ", " << info.stepSize;
+        ARMARX_INFO << "Connected to " << device.ip << ", " << info.frame << ", " << info.minAngle
+                    << ", " << info.maxAngle << ", " << info.stepSize;
 
-        device.task = new RunningTask<HokuyoLaserScanDevice>(&device, &HokuyoLaserScanDevice::run,
-                "HokuyoLaserScanUpdate_" + device.ip);
+        device.task = new RunningTask<HokuyoLaserScanDevice>(
+            &device, &HokuyoLaserScanDevice::run, "HokuyoLaserScanUpdate_" + device.ip);
         device.task->start();
     }
-
 }
 
-
-void HokuyoLaserUnit::onDisconnectComponent()
+void
+HokuyoLaserUnit::onDisconnectComponent()
 {
     for (HokuyoLaserScanDevice& device : devices)
     {
@@ -148,29 +166,32 @@ void HokuyoLaserUnit::onDisconnectComponent()
     }
 }
 
-
-void HokuyoLaserUnit::onExitComponent()
+void
+HokuyoLaserUnit::onExitComponent()
 {
-
 }
 
-armarx::PropertyDefinitionsPtr HokuyoLaserUnit::createPropertyDefinitions()
+armarx::PropertyDefinitionsPtr
+HokuyoLaserUnit::createPropertyDefinitions()
 {
-    return armarx::PropertyDefinitionsPtr(new HokuyoLaserUnitPropertyDefinitions(
-            getConfigIdentifier()));
+    return armarx::PropertyDefinitionsPtr(
+        new HokuyoLaserUnitPropertyDefinitions(getConfigIdentifier()));
 }
 
-std::string HokuyoLaserUnit::getReportTopicName(const Ice::Current&) const
+std::string
+HokuyoLaserUnit::getReportTopicName(const Ice::Current&) const
 {
     return topicName;
 }
 
-LaserScannerInfoSeq HokuyoLaserUnit::getConnectedDevices(const Ice::Current&) const
+LaserScannerInfoSeq
+HokuyoLaserUnit::getConnectedDevices(const Ice::Current&) const
 {
     return connectedDevices;
 }
 
-bool HokuyoLaserScanDevice::reconnect()
+bool
+HokuyoLaserScanDevice::reconnect()
 {
     if (connected)
     {
@@ -183,8 +204,8 @@ bool HokuyoLaserScanDevice::reconnect()
     connected = (ret == 0);
     if (!connected)
     {
-        ARMARX_WARNING << "Could not connect to laser scanner device using URG driver (IP: "
-                       << ip << ", Port: " << port << ", Error: " << ret << ")";
+        ARMARX_WARNING << "Could not connect to laser scanner device using URG driver (IP: " << ip
+                       << ", Port: " << port << ", Error: " << ret << ")";
         return false;
     }
     ret = urg_start_measurement(&urg, URG_DISTANCE, URG_SCAN_INFINITY, 0);
@@ -196,21 +217,24 @@ bool HokuyoLaserScanDevice::reconnect()
     }
     else
     {
-        ARMARX_WARNING << "Could not start measurement for laser scanner device using URG driver (IP: "
-                       << ip << ", Port: " << port << ", Error: " << ret << ")";
+        ARMARX_WARNING
+            << "Could not start measurement for laser scanner device using URG driver (IP: " << ip
+            << ", Port: " << port << ", Error: " << ret << ")";
         return false;
     }
 }
 
-void HokuyoLaserScanDevice::run()
+void
+HokuyoLaserScanDevice::run()
 {
+    ARMARX_TRACE;
     while (!task->isStopped())
     {
         IceUtil::Time time_start = TimeUtil::GetTime();
 
         if (errorCounter > 10)
         {
-            ARMARX_ERROR << "Device " << ip  << " has too many consecutive errors!";
+            ARMARX_ERROR << "Device " << ip << " has too many consecutive errors!";
             // assume dead
             reconnect();
         }
@@ -219,8 +243,9 @@ void HokuyoLaserScanDevice::run()
             int lengthDataSize = urg_get_distance(&urg, lengthData.data(), nullptr);
             if (lengthDataSize < 0)
             {
-                ARMARX_WARNING << deactivateSpam(1) << "Could not get measurement for laser scanner (IP: "
-                               << ip << ", Port: " << port << ", Error: " << lengthDataSize << ")";
+                ARMARX_WARNING << deactivateSpam(1)
+                               << "Could not get measurement for laser scanner (IP: " << ip
+                               << ", Port: " << port << ", Error: " << lengthDataSize << ")";
                 errorCounter++;
                 continue;
             }
@@ -236,7 +261,8 @@ void HokuyoLaserScanDevice::run()
                 // TODO: Extract the min and max valid value for distance into parameters?
                 if (distance >= 21 && distance <= 30000)
                 {
-                    step.angle = angleOffset + (float)urg_index2rad(&urg, stepIndex); // Convert steps to rad
+                    step.angle =
+                        angleOffset + (float)urg_index2rad(&urg, stepIndex); // Convert steps to rad
                     step.distance = (float)distance; // Data is already in mm
                     scan.push_back(step);
                 }
@@ -256,16 +282,14 @@ void HokuyoLaserScanDevice::run()
             }
             IceUtil::Time time_topicSensor = TimeUtil::GetTime();
 
-            RobotHealthHeartbeatArgs args;
-            args.maximumCycleTimeWarningMS = 500;
-            args.maximumCycleTimeErrorMS = 800;
-            if (robotHealthTopic)
+            if (robotHealthPlugin != nullptr)
             {
-                robotHealthTopic->heartbeat(componentName + "_" + ip, args);
+                robotHealthPlugin->heartbeatOnChannel(ip);
             }
             else
             {
-                ARMARX_WARNING << "No robot health topic available: IP: " << ip << ", Port: " << port;
+                ARMARX_WARNING << "No robot health topic available: IP: " << ip
+                               << ", Port: " << port;
             }
 
             IceUtil::Time time_topicHeartbeat = TimeUtil::GetTime();
@@ -274,20 +298,30 @@ void HokuyoLaserScanDevice::run()
 
             StringVariantBaseMap durations;
             durations["total_ms"] = new Variant(duration.toMilliSecondsDouble());
-            durations["measure_ms"] = new Variant((time_measure - time_start).toMilliSecondsDouble());
-            durations["update_ms"] = new Variant((time_update - time_measure).toMilliSecondsDouble());
-            durations["topic_sensor_ms"]      = new Variant((time_topicSensor - time_update).toMilliSecondsDouble());
-            durations["topic_health_ms"]      = new Variant((time_topicHeartbeat - time_topicSensor).toMilliSecondsDouble());
-            debugObserver->setDebugChannel("LaserScannerDuration_" + simox::alg::replace_all(ip, ".", "_"), durations);
+            durations["measure_ms"] =
+                new Variant((time_measure - time_start).toMilliSecondsDouble());
+            durations["update_ms"] =
+                new Variant((time_update - time_measure).toMilliSecondsDouble());
+            durations["topic_sensor_ms"] =
+                new Variant((time_topicSensor - time_update).toMilliSecondsDouble());
+            durations["topic_health_ms"] =
+                new Variant((time_topicHeartbeat - time_topicSensor).toMilliSecondsDouble());
+            debugObserver->setDebugChannel(
+                "LaserScannerDuration_" + simox::alg::replace_all(ip, ".", "_"), durations);
 
             if (duration.toSecondsDouble() > 0.1)
             {
                 ARMARX_WARNING << "Laserscanner reports are slow"
                                << "Total time:     " << duration.toMilliSecondsDouble() << "ms\n"
-                               << "Measure:   " << (time_measure - time_start).toMilliSecondsDouble() << "ms \n"
-                               << "Update:    " << (time_update - time_measure).toMilliSecondsDouble() << "ms\n"
-                               << "TopicSensor: " << (time_topicSensor - time_update).toMilliSecondsDouble() << "ms\n"
-                               << "TopicHeart:  " << (time_topicHeartbeat - time_topicSensor).toMilliSecondsDouble() << "ms\n";
+                               << "Measure:   "
+                               << (time_measure - time_start).toMilliSecondsDouble() << "ms \n"
+                               << "Update:    "
+                               << (time_update - time_measure).toMilliSecondsDouble() << "ms\n"
+                               << "TopicSensor: "
+                               << (time_topicSensor - time_update).toMilliSecondsDouble() << "ms\n"
+                               << "TopicHeart:  "
+                               << (time_topicHeartbeat - time_topicSensor).toMilliSecondsDouble()
+                               << "ms\n";
             }
         }
     }
diff --git a/source/RobotAPI/drivers/HokuyoLaserUnit/HokuyoLaserUnit.h b/source/RobotAPI/drivers/HokuyoLaserUnit/HokuyoLaserUnit.h
index e9afa8e5b927857a03b171dc615722275d30986f..0b8ad3985e9e3b1825054fe1339cb41a5c59f4cd 100644
--- a/source/RobotAPI/drivers/HokuyoLaserUnit/HokuyoLaserUnit.h
+++ b/source/RobotAPI/drivers/HokuyoLaserUnit/HokuyoLaserUnit.h
@@ -22,14 +22,17 @@
 
 #pragma once
 
+#include <vector>
+
 #include <ArmarXCore/core/Component.h>
 #include <ArmarXCore/core/services/tasks/RunningTask.h>
+
 #include <RobotAPI/components/units/SensorActorUnit.h>
-#include <RobotAPI/interface/units/LaserScannerUnit.h>
 #include <RobotAPI/interface/components/RobotHealthInterface.h>
+#include <RobotAPI/interface/units/LaserScannerUnit.h>
+#include <RobotAPI/libraries/RobotAPIComponentPlugins/HeartbeatComponentPlugin.h>
 
 #include <HokuyoLaserScannerDriver/urg_sensor.h>
-#include <vector>
 
 namespace armarx
 {
@@ -37,19 +40,30 @@ namespace armarx
      * @class HokuyoLaserUnitPropertyDefinitions
      * @brief
      */
-    class HokuyoLaserUnitPropertyDefinitions:
-        public armarx::ComponentPropertyDefinitions
+    class HokuyoLaserUnitPropertyDefinitions : public armarx::ComponentPropertyDefinitions
     {
     public:
-        HokuyoLaserUnitPropertyDefinitions(std::string prefix):
+        HokuyoLaserUnitPropertyDefinitions(std::string prefix) :
             armarx::ComponentPropertyDefinitions(prefix)
         {
-            defineOptionalProperty<std::string>("LaserScannerTopicName", "LaserScans", "Name of the laser scan topic.");
+            defineOptionalProperty<std::string>(
+                "LaserScannerTopicName", "LaserScans", "Name of the laser scan topic.");
             defineOptionalProperty<int>("UpdatePeriod", 25, "Update period for laser scans");
-            defineOptionalProperty<float>("AngleOffset", -2.3561944902, "Offset is applied the raw angles before reporting them");
-            defineOptionalProperty<std::string>("Devices", "", "List of devices in form of 'IP1,port1,frame1;IP2,port2,frame2;...'");
-            defineOptionalProperty<std::string>("RobotHealthTopicName", "RobotHealthTopic", "Name of the RobotHealth topic");
-            defineOptionalProperty<std::string>("DebugObserverName", "DebugObserver", "Name of the topic the DebugObserver listens on");
+            defineOptionalProperty<float>("AngleOffset",
+                                          -2.3561944902,
+                                          "Offset is applied the raw angles before reporting them");
+            defineOptionalProperty<std::string>(
+                "Devices",
+                "",
+                "List of devices in form of 'IP1,port1,frame1;IP2,port2,frame2;...'");
+
+            defineOptionalProperty<std::string>(
+                "RobotHealthComponentName", "RobotHealth", "Name of the RobotHealth component");
+            defineOptionalProperty<std::string>(
+                "RobotHealthTopicName", "RobotHealthTopic", "Name of the RobotHealth topic");
+            defineOptionalProperty<std::string>("DebugObserverName",
+                                                "DebugObserver",
+                                                "Name of the topic the DebugObserver listens on");
         }
     };
 
@@ -66,6 +80,7 @@ namespace armarx
 
         bool reconnect();
 
+
         void run();
 
         RunningTask<HokuyoLaserScanDevice>::pointer_type task;
@@ -73,7 +88,7 @@ namespace armarx
 
         std::string componentName;
         LaserScannerUnitListenerPrx scanTopic;
-        RobotHealthInterfacePrx robotHealthTopic;
+        armarx::plugins::HeartbeatComponentPlugin* robotHealthPlugin;
         DebugObserverInterfacePrx debugObserver;
     };
 
@@ -93,10 +108,13 @@ namespace armarx
         virtual public armarx::SensorActorUnit
     {
     public:
+        HokuyoLaserUnit();
+
         /**
          * @see armarx::ManagedIceObject::getDefaultName()
          */
-        std::string getDefaultName() const override
+        std::string
+        getDefaultName() const override
         {
             return "HokuyoLaserUnit";
         }
@@ -138,8 +156,8 @@ namespace armarx
         float angleOffset = 0.0f;
         std::vector<HokuyoLaserScanDevice> devices;
         LaserScannerInfoSeq connectedDevices;
-        RobotHealthInterfacePrx robotHealthTopic;
         DebugObserverInterfacePrx debugObserver;
-    };
-}
 
+        plugins::HeartbeatComponentPlugin* heartbeat = nullptr;
+    };
+} // namespace armarx
diff --git a/source/RobotAPI/gui-plugins/GuiHealthClient/GuiHealthClientWidget.ui b/source/RobotAPI/gui-plugins/GuiHealthClient/GuiHealthClientWidget.ui
index 3c7001134bb60d82eb5017ee2b3d6f86a7bc57ee..2140458dc4fb92ddc0f065178ed4487733d053fa 100644
--- a/source/RobotAPI/gui-plugins/GuiHealthClient/GuiHealthClientWidget.ui
+++ b/source/RobotAPI/gui-plugins/GuiHealthClient/GuiHealthClientWidget.ui
@@ -1,55 +1,42 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <ui version="4.0">
- <class>GuiHealthClientWidget</class>
- <widget class="QWidget" name="GuiHealthClientWidget">
-  <property name="geometry">
-   <rect>
-    <x>0</x>
-    <y>0</y>
-    <width>500</width>
-    <height>71</height>
-   </rect>
-  </property>
-  <property name="windowTitle">
-   <string>GuiHealthClientWidget</string>
-  </property>
-  <layout class="QGridLayout" name="gridLayout">
-   <item row="0" column="1">
-    <widget class="QPushButton" name="pushButtonToggleOwnHeartbeats">
-     <property name="text">
-      <string>Toggle</string>
-     </property>
-    </widget>
-   </item>
-   <item row="0" column="0">
-    <widget class="QLabel" name="labelState">
-     <property name="text">
-      <string>TextLabel</string>
-     </property>
-    </widget>
-   </item>
-   <item row="0" column="2">
-    <spacer name="horizontalSpacer">
-     <property name="orientation">
-      <enum>Qt::Horizontal</enum>
-     </property>
-     <property name="sizeHint" stdset="0">
-      <size>
-       <width>40</width>
-       <height>20</height>
-      </size>
-     </property>
-    </spacer>
-   </item>
-   <item row="1" column="0" colspan="3">
-    <widget class="QLabel" name="labelHealthState">
-     <property name="text">
-      <string>TextLabel</string>
-     </property>
-    </widget>
-   </item>
-  </layout>
- </widget>
- <resources/>
- <connections/>
+  <class>GuiHealthClientWidget</class>
+  <widget class="QWidget" name="GuiHealthClientWidget">
+    <property name="geometry">
+      <rect>
+        <x>0</x>
+        <y>0</y>
+        <width>500</width>
+        <height>71</height>
+      </rect>
+    </property>
+    <property name="windowTitle">
+      <string>GuiHealthClientWidget</string>
+    </property>
+    <layout class="QGridLayout" name="gridLayout">
+
+      <item row="0" column="0" colspan="2">
+        <widget class="QLabel" name="labelTags">
+          <property name="text">
+            <string>...</string>
+          </property>
+        </widget>
+      </item>
+
+      <item row="0" column="2">
+        <widget class="QPushButton" name="pushButtonUnrequireAll">
+          <property name="text">
+            <string>Reset required tags</string>
+          </property>
+        </widget>
+      </item>
+
+      <item row="1" column="0" colspan="3">
+        <widget class="QTableWidget" name="tableHealthState">
+        </widget>
+      </item>
+    </layout>
+  </widget>
+  <resources />
+  <connections />
 </ui>
diff --git a/source/RobotAPI/gui-plugins/GuiHealthClient/GuiHealthClientWidgetController.cpp b/source/RobotAPI/gui-plugins/GuiHealthClient/GuiHealthClientWidgetController.cpp
index ca95dd07836e035a04019bd2fd1d176c4ca01256..aa795eb22ff8c96dfcb82b77f2f4f46f1512471a 100644
--- a/source/RobotAPI/gui-plugins/GuiHealthClient/GuiHealthClientWidgetController.cpp
+++ b/source/RobotAPI/gui-plugins/GuiHealthClient/GuiHealthClientWidgetController.cpp
@@ -22,67 +22,95 @@
 
 #include "GuiHealthClientWidgetController.h"
 
-#include <QLabel>
+#include <SimoxUtility/algorithm/get_map_keys_values.h>
+#include <qcolor.h>
+#include <qnamespace.h>
+#include <qtablewidget.h>
 #include <string>
+
+#include <QLabel>
 #include <QPushButton>
 
+#include <ArmarXCore/core/time/DateTime.h>
+#include <ArmarXCore/core/time/Duration.h>
+#include <ArmarXCore/core/time/ice_conversions.h>
+
 namespace armarx
 {
+    std::string serializeList(const std::vector<std::string>& l)
+    {
+        std::string s;
+        for(std::size_t i = 0; i < l.size(); i++)
+        {
+            const auto& tag = l.at(i);
+            s += tag;
+
+            if(i != l.size() - 1)
+            {
+                s += ", ";
+            }
+        }
+
+        return s;
+    }
+
     GuiHealthClientWidgetController::GuiHealthClientWidgetController()
     {
         widget.setupUi(getWidget());
-        //ARMARX_IMPORTANT << "ctor start";
 
-        healthTimer = new QTimer(this);
-        healthTimer->setInterval(10);
-        connect(healthTimer, SIGNAL(timeout()), this, SLOT(healthTimerClb()));
-        connect(widget.pushButtonToggleOwnHeartbeats, SIGNAL(clicked()), this, SLOT(toggleSendOwnHeartbeats()));
-        connect(this, SIGNAL(invokeConnectComponentQt()), this, SLOT(onConnectComponentQt()), Qt::QueuedConnection);
-        connect(this, SIGNAL(invokeDisconnectComponentQt()), this, SLOT(onDisconnectComponentQt()), Qt::QueuedConnection);
+        connect(this,
+                SIGNAL(invokeConnectComponentQt()),
+                this,
+                SLOT(onConnectComponentQt()),
+                Qt::QueuedConnection);
+        connect(this,
+                SIGNAL(invokeDisconnectComponentQt()),
+                this,
+                SLOT(onDisconnectComponentQt()),
+                Qt::QueuedConnection);
+
+        connect(widget.pushButtonUnrequireAll, 
+                SIGNAL(clicked()),
+                this,
+                SLOT(unrequireAll()));
 
         updateSummaryTimer = new QTimer(this);
         updateSummaryTimer->setInterval(100);
         connect(updateSummaryTimer, SIGNAL(timeout()), this, SLOT(updateSummaryTimerClb()));
-
-        widget.labelState->setText("idle.");
-        widget.pushButtonToggleOwnHeartbeats->setText("send own heartbeats");
-
-        //ARMARX_IMPORTANT << "ctor end";
     }
 
-
     GuiHealthClientWidgetController::~GuiHealthClientWidgetController()
     {
-
     }
 
-
-    void GuiHealthClientWidgetController::loadSettings(QSettings* settings)
+    void
+    GuiHealthClientWidgetController::loadSettings(QSettings* settings)
     {
-
     }
 
-    void GuiHealthClientWidgetController::saveSettings(QSettings* settings)
+    void
+    GuiHealthClientWidgetController::saveSettings(QSettings* settings)
     {
-
     }
 
-
-    void GuiHealthClientWidgetController::onInitComponent()
+    void
+    GuiHealthClientWidgetController::onInitComponent()
     {
         //ARMARX_IMPORTANT << "onInitComponent";
         usingProxy("RobotHealth");
         offeringTopic("RobotHealthTopic");
     }
 
-    void GuiHealthClientWidgetController::healthTimerClb()
+    void
+    GuiHealthClientWidgetController::healthTimerClb()
     {
-        RobotHealthHeartbeatArgs rhha;
-        rhha.maximumCycleTimeWarningMS = 250;
-        rhha.maximumCycleTimeErrorMS = 500;
-        robotHealthTopicPrx->heartbeat(getName(), RobotHealthHeartbeatArgs());
+        armarx::core::time::dto::DateTime now;
+        armarx::core::time::toIce(now, armarx::core::time::DateTime::Now());
+        robotHealthTopicPrx->heartbeat(getName(), now);
     }
-    void GuiHealthClientWidgetController::updateSummaryTimerClb()
+
+    void
+    GuiHealthClientWidgetController::updateSummaryTimerClb()
     {
         //ARMARX_IMPORTANT << "updateSummaryTimerClb";
         if (robotHealthComponentPrx)
@@ -90,59 +118,128 @@ namespace armarx
             //ARMARX_IMPORTANT << "has robotHealthComponentPrx";
             try
             {
-                //ARMARX_IMPORTANT << "before set text";
-                widget.labelHealthState->setText(QString::fromUtf8(robotHealthComponentPrx->getSummary().c_str()));
-                //ARMARX_IMPORTANT << "after set text";
+                auto summary = robotHealthComponentPrx->getSummary();
+
+                const std::size_t nCols = 7;
+
+                auto& tableWidget = widget.tableHealthState;
+                tableWidget->setRowCount(summary.entries.size());
+                tableWidget->setColumnCount(nCols);
+
+                const auto summaryVals = simox::alg::get_values(summary.entries);
+
+                tableWidget->setHorizontalHeaderLabels({
+                    "identifier", "required", "keeps frequency", "tags", "time since\nlast update [ms]", "warning\nthreshold [ms]", "error\nthreshold [ms]"
+                });
+
+                tableWidget->setColumnWidth(0, 250);
+                tableWidget->setColumnWidth(1, 80);
+                tableWidget->setColumnWidth(2, 120);
+                tableWidget->setColumnWidth(3, 180);
+                tableWidget->setColumnWidth(4, 120);
+                tableWidget->setColumnWidth(5, 120);
+                tableWidget->setColumnWidth(6, 120);
+                
+                for(std::size_t i = 0; i < summary.entries.size(); i++)
+                {
+                    const auto& entry = summaryVals.at(i);
+
+                    std::string stateRepr;
+                    QColor color;
+                    switch(entry.state)
+                    {
+                        case HealthOK:
+                            stateRepr = "yes";
+                            color.setRgb(0, 255, 0); // green
+                            break;
+                        case HealthWarning:
+                            stateRepr = "yes";
+                            color.setRgb(255, 165, 0); // orange
+                            break;
+                        case HealthError:
+                            stateRepr = "no";
+                            color.setRgb(255, 0, 0); // red
+                            break;
+                    }
+
+                    const std::string timeSinceLastRepr = std::to_string(entry.timeSinceLastUpdate.microSeconds / 1000);
+                    const std::string tagsRepr = serializeList(entry.tags);
+
+                    tableWidget->setItem(i, 0, new QTableWidgetItem(QString::fromStdString(entry.identifier)));
+                    
+                    auto* requiredItem = new QTableWidgetItem(QString::fromStdString(entry.required ? "yes" : "no"));
+                    requiredItem->setTextAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
+                    tableWidget->setItem(i, 1, requiredItem);
+                    
+                    auto* stateItem = new QTableWidgetItem(QString::fromStdString(stateRepr));
+                    stateItem->setTextAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
+                    tableWidget->setItem(i, 2, stateItem);
+                    tableWidget->item(i, 2)->setBackgroundColor(color);
+                    
+                    tableWidget->setItem(i, 3, new QTableWidgetItem(QString::fromStdString(tagsRepr)));
+                    
+                    tableWidget->setItem(i, 4, new QTableWidgetItem(QString::fromStdString(timeSinceLastRepr)));
+                    tableWidget->setItem(i, 5, new QTableWidgetItem(QString::fromStdString(std::to_string(entry.maximumCycleTimeWarning.microSeconds / 1000))));
+                    tableWidget->setItem(i, 6, new QTableWidgetItem(QString::fromStdString(std::to_string(entry.maximumCycleTimeError.microSeconds / 1000))));
+                }
+
+                std::string tagsText = "Active tags: [";
+                {
+                    std::sort(summary.activeTags.begin(), summary.activeTags.end());
+
+                    tagsText += serializeList(summary.activeTags);
+
+                    tagsText += "]";
+                }
+
+                widget.labelTags->setText(QString::fromStdString(tagsText));
             }
             catch (...)
-            {}
+            {
+            }
         }
     }
 
-    void GuiHealthClientWidgetController::toggleSendOwnHeartbeats()
+    void
+    GuiHealthClientWidgetController::unrequireAll()
     {
-        if (ownHeartbeatsActive)
-        {
-            ownHeartbeatsActive = false;
-            healthTimer->stop();
-            robotHealthTopicPrx->unregister(getName());
-            widget.labelState->setText("idle.");
-            //widget.pushButtonToggleOwnHeartbeats->setDisabled(true);
-            widget.pushButtonToggleOwnHeartbeats->setText("send own heartbeats");
-        }
-        else
+        ARMARX_INFO << "UnrequireAll.";
+        if (robotHealthComponentPrx)
         {
-            ownHeartbeatsActive = true;
-            healthTimer->start();
-            widget.labelState->setText("sending heartbeats...");
-            widget.pushButtonToggleOwnHeartbeats->setText("unregister self");
+            try
+            {
+                robotHealthComponentPrx->resetRequiredTags();
+            } 
+            catch(...){}
         }
-
     }
 
-
-    void GuiHealthClientWidgetController::onConnectComponent()
+    void
+    GuiHealthClientWidgetController::onConnectComponent()
     {
         //ARMARX_IMPORTANT << "onConnectComponent";
         robotHealthTopicPrx = getTopic<RobotHealthInterfacePrx>("RobotHealthTopic");
         robotHealthComponentPrx = getProxy<RobotHealthComponentInterfacePrx>("RobotHealth");
         invokeConnectComponentQt();
     }
-    void GuiHealthClientWidgetController::onConnectComponentQt()
+
+    void
+    GuiHealthClientWidgetController::onConnectComponentQt()
     {
         //healthTimer->start();
-        //ARMARX_IMPORTANT << "updateSummaryTimer->start";
+        //ARMARX_IMPORTANT <<ame "updateSummaryTimer->start";
         updateSummaryTimer->start();
     }
 
-
-    void GuiHealthClientWidgetController::onDisconnectComponent()
+    void
+    GuiHealthClientWidgetController::onDisconnectComponent()
     {
         invokeDisconnectComponentQt();
     }
-    void GuiHealthClientWidgetController::onDisconnectComponentQt()
+
+    void
+    GuiHealthClientWidgetController::onDisconnectComponentQt()
     {
-        healthTimer->stop();
         updateSummaryTimer->stop();
     }
-}
+} // namespace armarx
diff --git a/source/RobotAPI/gui-plugins/GuiHealthClient/GuiHealthClientWidgetController.h b/source/RobotAPI/gui-plugins/GuiHealthClient/GuiHealthClientWidgetController.h
index 0fed7c1ae22ad51714624a49388e8fec87a2967d..f5bc6ff28a593ba1d5e58bf1a02aabf42cd29bae 100644
--- a/source/RobotAPI/gui-plugins/GuiHealthClient/GuiHealthClientWidgetController.h
+++ b/source/RobotAPI/gui-plugins/GuiHealthClient/GuiHealthClientWidgetController.h
@@ -110,7 +110,7 @@ namespace armarx
         void onDisconnectComponentQt();
         void healthTimerClb();
         void updateSummaryTimerClb();
-        void toggleSendOwnHeartbeats();
+        void unrequireAll();
 
     signals:
         /* QT signal declarations */
@@ -122,13 +122,9 @@ namespace armarx
          * Widget Form
          */
         Ui::GuiHealthClientWidget widget;
-        bool ownHeartbeatsActive = false;
 
         RobotHealthInterfacePrx robotHealthTopicPrx;
         RobotHealthComponentInterfacePrx robotHealthComponentPrx;
-        QTimer* healthTimer;
         QTimer* updateSummaryTimer;
     };
 }
-
-
diff --git a/source/RobotAPI/interface/components/RobotHealthInterface.ice b/source/RobotAPI/interface/components/RobotHealthInterface.ice
index bbad059d5a7468428a5a351b07b95e64bc27532b..a42072ae33903969d0de84e5bd7de708d50532a0 100644
--- a/source/RobotAPI/interface/components/RobotHealthInterface.ice
+++ b/source/RobotAPI/interface/components/RobotHealthInterface.ice
@@ -22,8 +22,12 @@
 
 #pragma once
 
+#include <ArmarXCore/interface/core/BasicTypes.ice>
+#include <ArmarXCore/interface/core/time.ice>
+
 module armarx
 {
+
     enum RobotHealthState
     {
         HealthOK, HealthWarning, HealthError
@@ -31,24 +35,65 @@ module armarx
 
     struct RobotHealthHeartbeatArgs
     {
-        int maximumCycleTimeWarningMS = 100;
-        int maximumCycleTimeErrorMS = 200;
-        string message;
+        string identifier;
+        // bool requiredByDefault;
+        string description;
+        Ice::StringSeq tags;
+
+        armarx::core::time::dto::Duration maximumCycleTimeWarning;
+        armarx::core::time::dto::Duration maximumCycleTimeError;
     };
 
     interface RobotHealthInterface
     {
-        void heartbeat(string componentName, RobotHealthHeartbeatArgs args);
-        void unregister(string componentName);
+        void signUp(RobotHealthHeartbeatArgs args);
+        void unregister(string identifier);
+
+        void addRequiredTags(string requesterIdentifier, Ice::StringSeq tags);
+        void removeRequiredTags(string requesterIdentifier, Ice::StringSeq tags);
+        void resetRequiredTags();
+
+        void heartbeat(string identifier, armarx::core::time::dto::DateTime referenceTime);
+        
+        string getTopicName();
     };
 
+    // Used by the RobotHealth to send an overall status update ehich e.g. the emergency stop listens to.
     interface AggregatedRobotHealthInterface
     {
         void aggregatedHeartbeat(RobotHealthState overallHealthState);
     };
 
+    struct RobotHealthInfoEntry
+    {
+        string identifier;
+        RobotHealthState state;
+
+        // string message;
+        armarx::core::time::dto::Duration minDelta;
+        armarx::core::time::dto::Duration maxDelta;
+        armarx::core::time::dto::Duration timeSinceLastUpdate;
+
+        bool required; // 
+        bool enabled;
+
+        armarx::core::time::dto::Duration maximumCycleTimeWarning;
+        armarx::core::time::dto::Duration maximumCycleTimeError;
+
+        Ice::StringSeq tags;
+    };
+
+    dictionary<string, RobotHealthInfoEntry> RobotHealthEntries;
+
+    struct RobotHealthInfo
+    {
+        Ice::StringSeq activeTags;
+        RobotHealthEntries entries;
+    };
+
+
     interface RobotHealthComponentInterface extends RobotHealthInterface
     {
-        string getSummary();
+        RobotHealthInfo getSummary();
     };
 };
diff --git a/source/RobotAPI/libraries/RobotAPIComponentPlugins/HeartbeatComponentPlugin.cpp b/source/RobotAPI/libraries/RobotAPIComponentPlugins/HeartbeatComponentPlugin.cpp
index 41b48bdff28d2cdbd2e601ab7e9d6dadcdbc0e2b..b0ad0e37fe12f626c82e0c7d1c6ec0cf320bcead 100644
--- a/source/RobotAPI/libraries/RobotAPIComponentPlugins/HeartbeatComponentPlugin.cpp
+++ b/source/RobotAPI/libraries/RobotAPIComponentPlugins/HeartbeatComponentPlugin.cpp
@@ -2,56 +2,120 @@
 
 #include <ArmarXCore/core/Component.h>
 #include <ArmarXCore/core/exceptions/local/ExpressionException.h>
+#include <ArmarXCore/core/time/DateTime.h>
+#include <ArmarXCore/core/time/ice_conversions.h>
 
 #include <RobotAPI/interface/components/RobotHealthInterface.h>
 
 namespace armarx::plugins
 {
-    void HeartbeatComponentPlugin::configureHeartbeatChannel(const std::string& channel,
-                                                             const RobotHealthHeartbeatArgs& args)
+
+    void
+    HeartbeatComponentPlugin::signUp(const std::string& identifier,
+                                     const std::vector<std::string>& tags,
+                                     const std::string& description)
+    {
+        RobotHealthHeartbeatArgs argsCopy = defaultHeartbeatArgs;
+        argsCopy.description = description;
+        argsCopy.identifier = identifier;
+        argsCopy.tags = tags;
+        signUp(argsCopy);
+    }
+
+    void
+    HeartbeatComponentPlugin::signUp(const std::string& identifier,
+                                     const armarx::core::time::Duration& warning,
+                                     const armarx::core::time::Duration& error,
+                                     const std::vector<std::string>& tags,
+                                     const std::string& description)
     {
-        channelHeartbeatConfig.emplace(channel, args);
+        RobotHealthHeartbeatArgs argsCopy = defaultHeartbeatArgs;
+        argsCopy.description = description;
+        argsCopy.tags = tags;
+        argsCopy.identifier = identifier;
+        toIce(argsCopy.maximumCycleTimeWarning, warning);
+        toIce(argsCopy.maximumCycleTimeError, error);
+
+        ARMARX_TRACE;
+        signUp(argsCopy);
     }
 
-    void HeartbeatComponentPlugin::configureHeartbeatChannel(const std::string& channel, const std::string& message)
+    void
+    HeartbeatComponentPlugin::signUp(const armarx::core::time::Duration& warning,
+                                     const armarx::core::time::Duration& error,
+                                     const std::vector<std::string>& tags,
+                                     const std::string& description)
     {
-        auto args = heartbeatArgs;
-        args.message = message;
-        configureHeartbeatChannel(channel, args);
+        RobotHealthHeartbeatArgs argsCopy = defaultHeartbeatArgs;
+        argsCopy.description = description;
+        argsCopy.tags = tags;
+        toIce(argsCopy.maximumCycleTimeWarning, warning);
+        toIce(argsCopy.maximumCycleTimeError, error);
+        // argsCopy.requiredByDefault = required;
+        signUp(argsCopy);
     }
 
-    void HeartbeatComponentPlugin::heartbeat()
+    void
+    HeartbeatComponentPlugin::signUp(const RobotHealthHeartbeatArgs& args)
     {
+        ARMARX_TRACE;
+        ARMARX_CHECK_NOT_NULL(rhprx);
+
+        if (args.identifier.empty())
+        {
+            RobotHealthHeartbeatArgs argsCopy = args;
+            argsCopy.identifier = parent().getName();
+            rhprx->signUp(argsCopy);
+        }
+        else
+        {
+            // add component name prefix to identifier
+            RobotHealthHeartbeatArgs argsCopy = args;
+            argsCopy.identifier = parent().getName() + "_" + argsCopy.identifier;
+            rhprx->signUp(argsCopy);
+        }
+    }
 
+    void
+    HeartbeatComponentPlugin::heartbeat()
+    {
         if (robotHealthTopic)
         {
-            robotHealthTopic->heartbeat(componentName, heartbeatArgs);
-        } else
+            armarx::core::time::dto::DateTime now;
+            armarx::core::time::toIce(now, armarx::core::time::DateTime::Now());
+            robotHealthTopic->heartbeat(parent().getName(), now);
+        }
+        else
         {
             ARMARX_WARNING << "No robot health topic available!";
         }
     }
 
-    void HeartbeatComponentPlugin::heartbeat(const std::string& channel)
+    void
+    HeartbeatComponentPlugin::heartbeatOnChannel(const std::string& channelName)
     {
-        const auto argsIt = channelHeartbeatConfig.find(channel);
-        ARMARX_CHECK(argsIt != channelHeartbeatConfig.end()) << "heartbeat() called for unknown channel '" << channel
-                                                             << "'."
-                                                             << "You must register the config using configureHeartbeatChannel(channel) first!";
-
-        const auto& args = argsIt->second;
-
         if (robotHealthTopic)
         {
-            robotHealthTopic->heartbeat(componentName + "_" + channel, args);
-        } else
+            armarx::core::time::dto::DateTime now;
+            armarx::core::time::toIce(now, armarx::core::time::DateTime::Now());
+            robotHealthTopic->heartbeat(parent().getName() + "_" + channelName, now);
+        }
+        else
         {
             ARMARX_WARNING << "No robot health topic available!";
         }
     }
 
-    void HeartbeatComponentPlugin::preOnInitComponent()
+    void
+    HeartbeatComponentPlugin::preOnInitComponent()
     {
+        // set default args
+        auto warn = armarx::core::time::Duration::MilliSeconds(p.maximumCycleTimeWarningMS);
+        auto err = armarx::core::time::Duration::MilliSeconds(p.maximumCycleTimeErrorMS);
+        armarx::core::time::toIce(defaultHeartbeatArgs.maximumCycleTimeWarning, warn);
+        armarx::core::time::toIce(defaultHeartbeatArgs.maximumCycleTimeError, err);
+        // defaultHeartbeatArgs.requiredByDefault = true;
+
         //        if (topicName.empty())
         //        {
         //            parent<Component>().getProperty(topicName, makePropertyName(topicPropertyName));
@@ -59,33 +123,39 @@ namespace armarx::plugins
         //        parent<Component>().offeringTopic(topicName);
     }
 
-    void HeartbeatComponentPlugin::postOnInitComponent()
+    void
+    HeartbeatComponentPlugin::postOnInitComponent()
     {
     }
 
-    void HeartbeatComponentPlugin::preOnConnectComponent()
+    void
+    HeartbeatComponentPlugin::postOnConnectComponent()
     {
-        //        robotHealthTopic = parent<Component>().getTopic<RobotHealthInterfacePrx>(topicName);
-        componentName = parent<Component>().getName();
+        ARMARX_CHECK_NOT_NULL(rhprx);
+        topicName = rhprx->getTopicName();
+        robotHealthTopic = parent<Component>().getTopic<RobotHealthInterfacePrx>(topicName);
     }
 
-    void HeartbeatComponentPlugin::postCreatePropertyDefinitions(PropertyDefinitionsPtr& properties)
+    void
+    HeartbeatComponentPlugin::postCreatePropertyDefinitions(PropertyDefinitionsPtr& properties)
     {
-        if (!properties->hasDefinition(makePropertyName(topicPropertyName)))
+        if (!properties->hasDefinition(makePropertyName(healthPropertyName)))
         {
-            properties->topic(robotHealthTopic, topicName, topicPropertyName,
-                              "Name of the topic the DebugObserver listens on");
+            properties->component(
+                rhprx, "RobotHealth", healthPropertyName, "Name of the robot health component.");
         }
 
         if (not properties->hasDefinition(makePropertyName(maximumCycleTimeWarningMSPropertyName)))
         {
-            properties->required(heartbeatArgs.maximumCycleTimeWarningMS, maximumCycleTimeWarningMSPropertyName,
+            properties->required(p.maximumCycleTimeWarningMS,
+                                 maximumCycleTimeWarningMSPropertyName,
                                  "maximum cycle time before warning is emitted");
         }
 
         if (not properties->hasDefinition(makePropertyName(maximumCycleTimeErrorMSPropertyName)))
         {
-            properties->required(heartbeatArgs.maximumCycleTimeErrorMS, maximumCycleTimeErrorMSPropertyName,
+            properties->required(p.maximumCycleTimeErrorMS,
+                                 maximumCycleTimeErrorMSPropertyName,
                                  "maximum cycle time before error is emitted");
         }
     }
diff --git a/source/RobotAPI/libraries/RobotAPIComponentPlugins/HeartbeatComponentPlugin.h b/source/RobotAPI/libraries/RobotAPIComponentPlugins/HeartbeatComponentPlugin.h
index bbefa079c1d54e71444d9f0e90120f4db0250297..7b3654e84562fdbc5386eedb70e86424701592af 100644
--- a/source/RobotAPI/libraries/RobotAPIComponentPlugins/HeartbeatComponentPlugin.h
+++ b/source/RobotAPI/libraries/RobotAPIComponentPlugins/HeartbeatComponentPlugin.h
@@ -22,6 +22,8 @@
 #pragma once
 
 #include <ArmarXCore/core/ComponentPlugin.h>
+#include <ArmarXCore/core/ManagedIceObject.h>
+#include <ArmarXCore/core/time/Duration.h>
 
 #include <RobotAPI/interface/components/RobotHealthInterface.h>
 
@@ -34,21 +36,33 @@ namespace armarx::plugins
         using ComponentPlugin::ComponentPlugin;
 
         /**
-         * @brief Configures a heartbeat subchannel.
-         *
-         * @param channel Identifier of the heartbeat channel
-         * @param args Configuration of this channel's heartbeat properties
+         * @brief register component to heartbeat
          */
-        void configureHeartbeatChannel(const std::string& channel, const std::string& message);
+        void signUp(const std::string& channelName = "",
+                    const std::vector<std::string>& aliases = {},
+                    const std::string& description = "");
 
         /**
-         * @brief Configures a heartbeat subchannel.
-         *
-         * @param channel Identifier of the heartbeat channel
-         * @param args Configuration of this channel's heartbeat properties
+         * @brief register component to heartbeat
+         */
+        void signUp(const armarx::core::time::Duration& warning,
+                    const armarx::core::time::Duration& error,
+                    const std::vector<std::string>& aliases = {},
+                    const std::string& description = "");
+
+        /**
+         * @brief register component to heartbeat
          */
-        void configureHeartbeatChannel(const std::string& channel,
-                                       const RobotHealthHeartbeatArgs& args);
+        void signUp(const std::string& channelName,
+                    const armarx::core::time::Duration& warning,
+                    const armarx::core::time::Duration& error,
+                    const std::vector<std::string>& aliases = {},
+                    const std::string& description = "");
+
+        /**
+         * @brief register component to heartbeat, possibly with different component name
+         */
+        void signUp(const RobotHealthHeartbeatArgs& args);
 
         /**
          * @brief Sends out a heartbeat using the default config
@@ -63,35 +77,37 @@ namespace armarx::plugins
          *
          * @param channel Identifier of the heartbeat channel
          */
-        void heartbeat(const std::string& channel);
+        void heartbeatOnChannel(const std::string& channelName);
 
     protected:
         void preOnInitComponent() override;
         void postOnInitComponent() override;
-        void preOnConnectComponent() override;
+        // void preOnConnectComponent() override;
+        void postOnConnectComponent() override;
 
         void postCreatePropertyDefinitions(PropertyDefinitionsPtr& properties) override;
 
     private:
         //! heartbeat topic name (outgoing)
+        RobotHealthInterfacePrx robotHealthTopic;
         std::string topicName{"RobotHealthTopic"};
 
-        //! name of this component used as identifier for heartbeats
-        std::string componentName;
+        RobotHealthComponentInterfacePrx rhprx;
 
         //
-        static constexpr auto topicPropertyName = "heartbeat.TopicName";
+        static constexpr auto healthPropertyName = "heartbeat.ComponentName";
         static constexpr auto maximumCycleTimeWarningMSPropertyName =
             "heartbeat.maximumCycleTimeWarningMS";
         static constexpr auto maximumCycleTimeErrorMSPropertyName =
             "heartbeat.maximumCycleTimeErrorMS";
 
-        RobotHealthInterfacePrx robotHealthTopic;
+        struct Properties
+        {
+            long maximumCycleTimeWarningMS = 50;
+            long maximumCycleTimeErrorMS = 100;
+        } p;
 
         //! default config used in heartbeat(), set via properties
-        RobotHealthHeartbeatArgs heartbeatArgs;
-
-        //! configs used in heartbeat(channel), set by user via configureHeartbeatChannel(...)
-        std::unordered_map<std::string, RobotHealthHeartbeatArgs> channelHeartbeatConfig;
+        RobotHealthHeartbeatArgs defaultHeartbeatArgs;
     };
 } // namespace armarx::plugins
diff --git a/source/RobotAPI/libraries/aron/core/data/variant/container/Dict.cpp b/source/RobotAPI/libraries/aron/core/data/variant/container/Dict.cpp
index 93400fe1761849e97c4e4c228eeae3b5e60e0047..21235afefb794e8c78443e840a240f56c6720b14 100644
--- a/source/RobotAPI/libraries/aron/core/data/variant/container/Dict.cpp
+++ b/source/RobotAPI/libraries/aron/core/data/variant/container/Dict.cpp
@@ -112,7 +112,14 @@ namespace armarx::aron::data
         DictPtr ret(new Dict(p));
         for (const auto& [key, val] : getElements())
         {
-            ret->addElement(key, val->cloneAsVariant());
+            if (val)
+            {
+                ret->addElement(key, val->cloneAsVariant());
+            }
+            else
+            {
+                ret->addElement(key, nullptr);
+            }
         }
         return ret;
     }
@@ -161,6 +168,8 @@ namespace armarx::aron::data
     void
     Dict::addElement(const std::string& key, const VariantPtr& data)
     {
+        // Please note that the data may be null (indicating that a non existing maybetype has been added)
+
         if (hasElement(key))
         {
             ARMARX_TRACE;
@@ -173,6 +182,8 @@ namespace armarx::aron::data
     void
     Dict::addElementCopy(const std::string& key, const VariantPtr& data)
     {
+        // Please note that the data may be null (indicating that a non existing maybetype has been added)
+
         if (hasElement(key))
         {
             ARMARX_TRACE;
@@ -213,11 +224,16 @@ namespace armarx::aron::data
     void
     Dict::setElement(const std::string& key, const VariantPtr& data)
     {
-        const auto& p = data->getPath();
-        if (not p.hasDirectPrefix(this->getPath()))
+        // Please note that the data may be null (indicating that a non existing maybetype has been added)
+
+        if (data)
         {
-            ARMARX_WARNING << "An element added to a dict does not have a correct path set. This "
-                              "may cause errors. Please use setElemetCopy() instead.";
+            const auto& p = data->getPath();
+            if (not p.hasDirectPrefix(this->getPath()))
+            {
+                ARMARX_WARNING << "An element added to a dict does not have a correct path set. This "
+                                  "may cause errors. Please use setElemetCopy() instead.";
+            }
         }
 
         this->childrenNavigators[key] = data;
@@ -234,8 +250,14 @@ namespace armarx::aron::data
     void
     Dict::setElementCopy(const std::string& key, const VariantPtr& data)
     {
-        Path newPath = getPath().withElement(key);
-        auto copy = data->cloneAsVariant(newPath);
+        // Please note that the data may be null (indicating that a non existing maybetype has been added)
+
+        VariantPtr copy = nullptr;
+        if (data)
+        {
+            Path newPath = getPath().withElement(key);
+            copy = data->cloneAsVariant(newPath);
+        }
         setElement(key, copy);
     }