From b7d811bb24f51dee9a5ee47b30fb4a84160e0ad0 Mon Sep 17 00:00:00 2001
From: Fabian Peller <fabian.peller-konrad@kit.edu>
Date: Fri, 12 May 2023 23:25:49 +0200
Subject: [PATCH] remove polling possibility

---
 .../components/RobotHealth/RobotHealth.cpp    | 383 +++++++++++-------
 .../components/RobotHealth/RobotHealth.h      | 161 ++++----
 .../RobotHealth/RobotHealthDummy.cpp          |  68 ++--
 .../components/RobotHealth/RobotHealthDummy.h |  25 +-
 .../components/RobotHealthInterface.ice       |  36 +-
 5 files changed, 394 insertions(+), 279 deletions(-)

diff --git a/source/RobotAPI/components/RobotHealth/RobotHealth.cpp b/source/RobotAPI/components/RobotHealth/RobotHealth.cpp
index 0e4b7f72a..5364b5de5 100644
--- a/source/RobotAPI/components/RobotHealth/RobotHealth.cpp
+++ b/source/RobotAPI/components/RobotHealth/RobotHealth.cpp
@@ -20,245 +20,323 @@
  *             GNU General Public License
  */
 
-#include <ArmarXCore/util/CPPUtility/trace.h>
-
 #include "RobotHealth.h"
 
 #include <boost/regex.hpp>
 
+#include <ArmarXCore/core/time/ice_conversions.h>
+#include <ArmarXCore/util/CPPUtility/trace.h>
 
 namespace armarx
 {
-    void RobotHealth::onInitComponent()
+    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();
-
-        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);
 
+        monitorAggregatedHealthTask =
+            new PeriodicTask<RobotHealth>(this, &RobotHealth::monitorAggregatedHealthTaskClb, 10);
 
-        //usingProxy(getProperty<std::string>("RemoteGuiName").getValue());
+        defaultMaximumCycleTimeWarn =
+            armarx::core::time::Duration::MilliSeconds(p.maximumCycleTimeWarnMS);
+        defaultMaximumCycleTimeErr =
+            armarx::core::time::Duration::MilliSeconds(p.maximumCycleTimeErrMS);
 
         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)
-            {
-                healthState = RobotHealthState::HealthError;
-            }
+            UpdateEntry& e = updateEntries.at(i);
+
+            std::unique_lock elock(e.mutex);
 
             if (!e.enabled)
             {
                 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;
+            }
+        }
+
+        reportDebugObserver();
+    }
+
+    void
+    RobotHealth::monitorAggregatedHealthTaskClb()
+    {
+        RobotHealthState overallHealthState = RobotHealthState::HealthOK;
+
+        {
+            std::shared_lock lock(updateMutex);
+
+            // 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);
+
+                if (e.required)
+                {
+                    if (e.state == HealthWarning && overallHealthState != HealthError)
+                    {
+                        overallHealthState = RobotHealthState::HealthWarning;
+                    }
+                    else if (e.state == HealthError)
+                    {
+                        overallHealthState = RobotHealthState::HealthError;
+                    }
+                }
+                else
                 {
-                    healthState = RobotHealthState::HealthWarning;
+                    if (e.state != HealthOK && overallHealthState != HealthError)
+                    {
+                        overallHealthState = RobotHealthState::HealthWarning;
+                    }
                 }
             }
-
         }
-        if (hasNewErr)
+
+        if (overallHealthState == HealthError)
         {
             ARMARX_TRACE;
-            emergencyStopTopicPrx->reportEmergencyStopState(EmergencyStopState::eEmergencyStopActive);
+            p.emergencyStopTopicPrx->reportEmergencyStopState(
+                EmergencyStopState::eEmergencyStopActive);
         }
         ARMARX_TRACE;
-        aggregatedRobotHealthTopicPrx->aggregatedHeartbeat(healthState);
-        /*if(allRequiredRunning && robotUnitPrx)
-        {
-            try
-            {
-                robotUnitPrx->sendHeartbeat();
-            }
-            catch(...)
-            {}
-        }*/
-
-        reportDebugObserver();
+        p.aggregatedRobotHealthTopicPrx->aggregatedHeartbeat(overallHealthState);
     }
 
-    void RobotHealth::onDisconnectComponent()
+    void
+    RobotHealth::onDisconnectComponent()
     {
         //robotUnitPrx = nullptr;
-        monitorHealthTask->stop();
+        monitorUpdateHealthTask->stop();
     }
 
-
-    void RobotHealth::onExitComponent()
+    void
+    RobotHealth::onExitComponent()
     {
-
     }
 
-    armarx::PropertyDefinitionsPtr RobotHealth::createPropertyDefinitions()
+    armarx::PropertyDefinitionsPtr
+    RobotHealth::createPropertyDefinitions()
     {
-        return armarx::PropertyDefinitionsPtr(new RobotHealthPropertyDefinitions(
-                getConfigIdentifier()));
+        auto defs = armarx::PropertyDefinitionsPtr(
+            new armarx::ComponentPropertyDefinitions(getConfigIdentifier()));
+
+
+        defs->topic(p.emergencyStopTopicPrx,
+                    "The name of the topic over which changes of the emergencyStopState are sent.");
+        defs->topic<RobotHealthInterfacePrx>(p.robotHealthTopicName,
+                                             "Name of the RobotHealth topic");
+
+        defs->topic(p.aggregatedRobotHealthTopicPrx, "Name of the AggregatedRobotHealthTopic");
+
+        defs->optional(p.maximumCycleTimeWarnMS,
+                       "Default value of the maximum cycle time for warnings");
+        defs->optional(p.maximumCycleTimeErrMS,
+                       "Default value of the maximum cycle time for error");
+
+        return defs;
     }
 
-    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)
+            std::shared_lock lock(updateMutex);
+
+            for (size_t i = 0; i < updateEntries.size(); i++)
             {
-                return entries.at(i);
+                if (updateEntries.at(i).name == name)
+                {
+                    return {false, updateEntries.at(i)};
+                }
             }
+
+            // Todo upgrade lock?
         }
-        std::unique_lock lock(mutex);
-        ARMARX_INFO << "registering '" << name << "'";
-        entries.emplace_back(name, defaultMaximumCycleTimeWarn * 1000, defaultMaximumCycleTimeErr * 1000);
-        validEntries++;
-        return entries.back();
-    }
 
+        std::unique_lock lock(updateMutex);
+        ARMARX_INFO << "registering for updates: '" << 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 std::string& componentName,
+                        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)
+        if (args->mode == POLLING)
         {
-            ARMARX_INFO << "'" << componentName << "' is now alive and running";
-            e.lastDelta = 0;
+            // TODO
         }
         else
         {
-            e.lastDelta = now - e.lastUpdate;
-            e.history.at(e.historyIndex) = e.lastDelta;
-            e.historyIndex = (e.historyIndex + 1) % e.history.size();
+            auto argsCasted = RobotHealthUpdateHeartbeatArgsPtr::dynamicCast(args);
+
+            auto e = findOrCreateUpdateEntry(componentName);
+
+            std::unique_lock lock(e.second.mutex);
+
+            if (e.first)
+            {
+                ARMARX_INFO << "Newly registering component " << componentName << " for updates.";
+            }
+            else
+            {
+                ARMARX_INFO << "Updating the config for component " << componentName
+                            << " for updates.";
+            }
+
+            armarx::core::time::fromIce(argsCasted->maximumCycleTimeWarning,
+                                        e.second.maximumCycleTimeWarn);
+            armarx::core::time::fromIce(argsCasted->maximumCycleTimeError,
+                                        e.second.maximumCycleTimeErr);
+
+            e.second.required = args->required;
+            e.second.description = args->description;
+            e.second.enabled = true;
+            e.second.state = HealthOK;
+            e.second.history.clear();
         }
+    }
+
+    void
+    RobotHealth::heartbeat(const std::string& componentName,
+                           const core::time::dto::DateTime& referenceTime,
+                           const Ice::Current&)
+    {
         ARMARX_TRACE;
-        e.lastUpdate = now;
-        e.isRunning = true;
-        e.enabled = true;
+        auto e = findOrCreateUpdateEntry(componentName);
+
+        std::unique_lock lock(e.second.mutex);
+
+        if (e.first) // newly created
+        {
+            ARMARX_WARNING << "Attention. Component " << componentName
+                           << " was not signed up for heartbeat. Creating a heartbeat with default "
+                              "config... Thus, this component is by default NOT REQUIRED.";
+        }
+
+        if (!e.second.enabled)
         {
-            std::unique_lock lock(e.messageMutex);
-            e.message = args.message;
+            ARMARX_WARNING << "Attention. Component " << componentName
+                           << " was not enabled. The heartbeat updated again enabled this "
+                              "component for updates using the latest config found. ";
+            e.second.enabled = true;
         }
+
+        auto now = armarx::core::time::DateTime::Now();
     }
 
-    void armarx::RobotHealth::unregister(const std::string& componentName, const Ice::Current&)
+    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;
+        bool found = false;
+        {
+            std::shared_lock lock(pollingMutex);
+
+            for (size_t i = 0; i < updateEntries.size(); i++)
+            {
+                auto& e = updateEntries.at(i);
+                if (e.name == componentName)
+                {
+                    std::unique_lock elock(e.mutex);
+                    e.enabled = false;
+                    break;
+                }
+            }
+            // Todo upgrade lock?
+        }
+
+        if (found)
+        {
+            ARMARX_INFO << "Component " << componentName << " disabled from heartbeat";
+        }
+        else
+        {
+            ARMARX_INFO << "Could not unregister component " << componentName << ". Not found.";
+        }
     }
 
-    std::string RobotHealth::getSummary(const Ice::Current&)
+    RobotHealthInfo
+    RobotHealth::getSummary(const Ice::Current&)
     {
         ARMARX_TRACE;
-        long now = TimeUtil::GetTime().toMicroSeconds();
+        auto now = armarx::core::time::DateTime::Now();
+
         std::stringstream ss;
-        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);
+            UpdateEntry& e = updateEntries.at(i);
+            auto delta = now - e.history.back();
 
-            long minDelta = delta;
-            long maxDelta = delta;
+            auto minDelta = delta;
+            auto maxDelta = delta;
             for (size_t i = 1; i < e.history.size(); i++)
             {
-                long historicalDelta = e.history.at(i);
-                if (historicalDelta < 0)
+                auto historicalDelta = e.history.at(i);
+
+                if (minDelta > historicalDelta)
+                {
+                }
+
+                if (maxDelta < historicalDelta)
                 {
-                    break;
                 }
-                minDelta = std::min(minDelta, historicalDelta);
-                maxDelta = std::max(maxDelta, historicalDelta);
             }
 
             ss << e.name;
@@ -285,31 +363,34 @@ namespace armarx
                 {
                     ss << ": ok";
                 }
-                ss << " (" << (delta / 1000) << "ms, min: " << (minDelta / 1000) << "ms, max: " << (maxDelta / 1000) << "ms)";
+                ss << " (" << (delta / 1000) << "ms, min: " << (minDelta / 1000)
+                   << "ms, max: " << (maxDelta / 1000) << "ms)";
             }
             ARMARX_TRACE;
 
-            std::unique_lock lock(e.messageMutex);
+            std::unique_lock lock(e.mutex);
             if (e.message.size())
             {
                 ss << " - " << e.message;
             }
 
             ss << "\n";
-
         }
         return ss.str();
     }
 
-    void RobotHealth::reportDebugObserver()
+    void
+    RobotHealth::reportDebugObserver()
     {
-        for(const auto& entry: entries)
+        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);
+            setDebugObserverDatafield("RobotHealth_" + entry.name + "_maximumCycleTimeWarn",
+                                      entry.maximumCycleTimeWarn);
+            setDebugObserverDatafield("RobotHealth_" + entry.name + "_maximumCycleTimeErr",
+                                      entry.maximumCycleTimeErr);
         }
 
         sendDebugObserverBatch();
     }
-}
+} // namespace armarx
diff --git a/source/RobotAPI/components/RobotHealth/RobotHealth.h b/source/RobotAPI/components/RobotHealth/RobotHealth.h
index 3da12c179..a47b7a7d0 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,73 @@ 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;
+            RobotHealthState state = HealthOK;
+            std::string description = "";
+
+            bool required = false;
+            bool enabled = true;
 
-        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;
+            mutable std::shared_mutex mutex;
+            std::deque<armarx::core::time::DateTime> history;
+
+            armarx::core::time::Duration maximumCycleTimeWarn;
+            armarx::core::time::Duration maximumCycleTimeErr;
+        };
+
+        void monitorHealthUpdateTaskClb();
+        void monitorAggregatedHealthTaskClb();
+
+        std::pair<bool, UpdateEntry&> findOrCreateUpdateEntry(const std::string& name);
+
+        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 std::string& componentName,
+                    const RobotHealthHeartbeatArgs& args,
+                    const Ice::Current& current) override;
+        void heartbeat(const std::string& componentName,
+                       const std::string& message,
+                       const core::time::dto::DateTime& referenceTime,
+                       const Ice::Current& current) override;
+
+        // RobotHealthComponentInterface interface
+    public:
+        RobotHealthInfo getSummary(const Ice::Current& current) override;
+
+    private:
+        mutable std::shared_mutex updateMutex;
+
+        std::deque<UpdateEntry> updateEntries;
 
+        PeriodicTask<RobotHealth>::pointer_type monitorUpdateHealthTask;
+        PeriodicTask<RobotHealth>::pointer_type monitorAggregatedHealthTask;
+
+        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;
+        } p;
     };
-}
+} // namespace armarx
diff --git a/source/RobotAPI/components/RobotHealth/RobotHealthDummy.cpp b/source/RobotAPI/components/RobotHealth/RobotHealthDummy.cpp
index b239fdd07..416a43da0 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,54 @@ 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()
     {
 
-
+        robotHealthTopicPrx->signUp(getName(), RobotHealthHeartbeatArgs());
 
         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 +141,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 cb9d46377..cfd8d369d 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/interface/components/RobotHealthInterface.ice b/source/RobotAPI/interface/components/RobotHealthInterface.ice
index bbad059d5..9a0ce888f 100644
--- a/source/RobotAPI/interface/components/RobotHealthInterface.ice
+++ b/source/RobotAPI/interface/components/RobotHealthInterface.ice
@@ -22,33 +22,55 @@
 
 #pragma once
 
+#include <ArmarXCore/interface/core/BasicTypes.ice>
+#include <ArmarXCore/interface/core/time.ice>
+
 module armarx
 {
     enum RobotHealthState
     {
         HealthOK, HealthWarning, HealthError
-    };
+    };´
 
     struct RobotHealthHeartbeatArgs
     {
-        int maximumCycleTimeWarningMS = 100;
-        int maximumCycleTimeErrorMS = 200;
-        string message;
+        string componentName;
+        bool required;
+        string description;
+        Ice::StringList 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);
+        idempotent void require(Ice::StringSeq namesOrTags);
+        idempotent void unrequire(Ice::StringSeq namesOrTags);
+
+        idempotent void signUp(RobotHealthHeartbeatArgs args);
+
+        idempotent void heartbeat(string componentName, string message, armarx::core::time::dto::DateTime referenceTime);
+        idempotent void unregister(string componentName);
     };
 
+    // 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 componentName;
+        RobotHealthState state;
+        string message;
+    };
+
+    dictionary<string, RobotHealthInfoEntry> RobotHealthInfo;
+
     interface RobotHealthComponentInterface extends RobotHealthInterface
     {
-        string getSummary();
+        RobotHealthInfo getSummary();
     };
 };
-- 
GitLab