From 9e332661cb2aa56007f55c26ee8d887587a20a04 Mon Sep 17 00:00:00 2001
From: bav6096 <benedikt.deike@informatik.uni-hamburg.de>
Date: Tue, 7 Dec 2021 06:15:38 +0100
Subject: [PATCH] preparation for developed expressions

---
 launch/visualiser.launch                 |  22 ++++
 launch/visualising.launch                |   3 +
 nodes/visualiser                         |  15 +--
 src/visualising/expression/expression.py |  33 ++++++
 src/visualising/monitoring/__init__.py   |   0
 src/visualising/monitoring/watchdog.py   | 142 +++++++++++++++++++++++
 src/visualising/visualiser.py            |  38 +++---
 7 files changed, 224 insertions(+), 29 deletions(-)
 create mode 100644 launch/visualiser.launch
 create mode 100644 launch/visualising.launch
 create mode 100644 src/visualising/monitoring/__init__.py
 create mode 100755 src/visualising/monitoring/watchdog.py

diff --git a/launch/visualiser.launch b/launch/visualiser.launch
new file mode 100644
index 0000000..eed1fb9
--- /dev/null
+++ b/launch/visualiser.launch
@@ -0,0 +1,22 @@
+<launch>
+    <node pkg="visualising" type="visualiser" name="visualiser">
+        <param name="/wdg_freq" value="2.0" type="double" />
+        <param name="/vis_freq" value="2.0" type="double" />
+
+        <param name="/arduino_port" value="/ttyUSB0" type="str" />
+        <param name="/arduino_baud" value="57600" type="int" />
+
+        <!-- set visualisation strategy -->
+        <param name="/visualisation_strategy" value="1" type="int" />
+
+        <!-- example of how to set an aggregation strategy for a metric -->
+        <rosparam param="aggregation_strategy_metrics">
+            example_1: 10
+            example_2: 10
+            test_domain_1: 0
+            test_domain_2: 0
+        </rosparam>
+        <!-- set the aggregation strategy for domains -->
+        <param name="aggregation_strategy_domains" value="0" type="int" />
+    </node>
+</launch>
diff --git a/launch/visualising.launch b/launch/visualising.launch
new file mode 100644
index 0000000..75f4422
--- /dev/null
+++ b/launch/visualising.launch
@@ -0,0 +1,3 @@
+<launch>
+    <include file="$(find visualising)/launch/visualiser.launch" />
+</launch>
\ No newline at end of file
diff --git a/nodes/visualiser b/nodes/visualiser
index d202ccc..2439041 100755
--- a/nodes/visualiser
+++ b/nodes/visualiser
@@ -1,20 +1,13 @@
 #!/usr/bin/env python
 
 import rospy
-from visualising.visualiser import Visualiser
-
-
-def visualiser():
-    rospy.init_node("visualiser")
-    node = Visualiser()
-
-    rate = rospy.Rate(10)
-    while not rospy.is_shutdown():
-        rate.sleep()
 
+from visualising.visualiser import Visualiser
 
 if __name__ == '__main__':
     try:
-        visualiser()
+        rospy.init_node("visualiser")
+        Visualiser()
+        rospy.spin()
     except rospy.ROSInterruptException:
         pass
diff --git a/src/visualising/expression/expression.py b/src/visualising/expression/expression.py
index e69de29..e67faef 100755
--- a/src/visualising/expression/expression.py
+++ b/src/visualising/expression/expression.py
@@ -0,0 +1,33 @@
+import numpy as np
+
+class Emotion:
+    def __init__(self, arduino):
+        self.arduino = arduino
+
+    @staticmethod
+    def state_to_color(state):
+        red = np.array([255, 0, 0])
+        white = np.array([255, 255, 255])
+
+        return (1 - state) * white + state * red
+
+    def react(self, state):
+        color = Emotion.state_to_color(state)
+        if state > 0.7:
+            self.happy(color)
+        else:
+            if state > 0.3:
+                self.ok(color)
+            else:
+                self.angry(color)
+
+    def happy(self, color):
+        
+
+
+    def ok(self, color):
+
+
+
+    def angry(self, color):
+
diff --git a/src/visualising/monitoring/__init__.py b/src/visualising/monitoring/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/visualising/monitoring/watchdog.py b/src/visualising/monitoring/watchdog.py
new file mode 100755
index 0000000..0b9428d
--- /dev/null
+++ b/src/visualising/monitoring/watchdog.py
@@ -0,0 +1,142 @@
+#!/usr/bin/env python
+
+import rospy
+
+from monitoring.msg import Monitoring
+
+
+class Watchdog:
+    def __init__(self):
+        self.sub = rospy.Subscriber("/monitoring", Monitoring, self.update_log)
+
+        # Stores the most current values of each metric.
+        self.log = {}
+
+        # Strategies that specify how the critical level of each metric should be aggregated in a domain.
+        # It is possible to specify a different aggregation strategy for every single domain.
+        self.aggregation_strategy_metrics = rospy.get_param("/visualiser/aggregation_strategy_metrics")
+        # Dictionary that stores the aggregated critical levels of the metrics of a domain.
+        self.aggregated_metrics = {}
+
+        # Strategy that specifies how the critical levels of all domains should be aggregated.
+        self.aggregation_strategy_domains = rospy.get_param("/visualiser/aggregation_strategy_domains")
+        # Variable that stores the aggregated critical level of all domains.
+        self.aggregated_domains = None
+
+        # Frequency at which the metrics in the log are aggregated.
+        wdg_freq = rospy.get_param("/visualiser/wdg_freq", 1.0)
+
+        if not wdg_freq > 0.0:
+            rospy.logwarn(
+                "Watchdog: The frequency at which the metrics of the robot are aggregated must be greater then 0! "
+                "Using 1 as frequency!")
+            wdg_freq = 1.0
+
+        self.timer = rospy.Timer(rospy.Duration(1.0 / wdg_freq), self.update_condition)
+
+    # TODO: Think about efficiency!
+    def update_log(self, monitoring):
+        origin = monitoring.origin
+        metric = monitoring.metric
+
+        domain = metric.domain
+        label = metric.label
+        value = metric.value
+        unit = metric.unit
+        error = metric.error
+
+        if domain in self.log:
+            if origin in self.log[domain]:
+                self.log[domain][origin].update({label: {"value": value, "unit": unit, "error": error}})
+            else:
+                self.log[domain][origin] = {}
+                self.log[domain][origin][label] = {"value": value, "unit": unit, "error": error}
+        else:
+            self.log[domain] = {}
+            self.log[domain][origin] = {}
+            self.log[domain][origin][label] = {"value": value, "unit": unit, "error": error}
+
+    def update_condition(self, event):
+        self.aggregate_metrics()
+        self.aggregate_domains()
+
+    # TODO: Add more modes!
+    def aggregate_metrics(self):
+        for domain in self.log:
+            if domain in self.aggregation_strategy_metrics and self.aggregation_strategy_metrics[domain] != 0:
+
+                # Strategy 1: Take the highest error level of any metric in a domain.
+                if self.aggregation_strategy_metrics[domain] == 1:
+                    highest_error = 0.0
+
+                    for origin in self.log[domain]:
+                        for label in self.log[domain][origin]:
+                            local_error = self.log[domain][origin][label]["error"]
+                            if local_error > highest_error:
+                                highest_error = local_error
+
+                    self.aggregated_metrics.update({domain: highest_error})
+
+                # Strategy 2: Take the lowest error level of any metric in a domain.
+                elif self.aggregation_strategy_metrics[domain] == 2:
+                    lowest_error = 1.0
+
+                    for origin in self.log[domain]:
+                        for label in self.log[domain][origin]:
+                            local_error = self.log[domain][origin][label]["error"]
+                            if local_error < lowest_error:
+                                lowest_error = local_error
+
+                    self.aggregated_metrics.update({domain: lowest_error})
+
+                else:
+                    rospy.logwarn("Watchdog: The specified method for aggregating metrics is not recognised!")
+
+            # Default strategy: Take the average error level of every metric in a domain.
+            else:
+                count = 0.001
+                aggregated_error = 0.0
+
+                for origin in self.log[domain]:
+                    for label in self.log[domain][origin]:
+                        count = count + 1
+                        aggregated_error = aggregated_error + self.log[domain][origin][label]["error"]
+
+                quotient = aggregated_error / count
+                self.aggregated_metrics.update({domain: quotient})
+
+    # TODO: Add more modes!
+    def aggregate_domains(self):
+        # Strategy 1: Take the highest error level of any domain.
+        if self.aggregation_strategy_domains == 1:
+            highest_error = 0.0
+
+            for domain in self.aggregated_metrics:
+                local_error = self.aggregated_metrics[domain]
+                if local_error > highest_error:
+                    highest_error = local_error
+
+            self.aggregated_domains = highest_error
+
+        # Strategy 2: Take the lowes error level of any domain.
+        elif self.aggregation_strategy_domains == 2:
+            lowest_error = 1.0
+
+            for domain in self.aggregated_metrics:
+                local_error = self.aggregated_metrics[domain]
+                if local_error < lowest_error:
+                    lowest_error = local_error
+
+            self.aggregated_domains = lowest_error
+
+        # Default strategy: Take the average error level of every domain.
+        else:
+            count = 0.001
+            aggregated_error = 0.0
+
+            for domain in self.aggregated_metrics:
+                count = count + 1
+                aggregated_error = aggregated_error + self.aggregated_metrics[domain]
+
+            quotient = aggregated_error / count
+            self.aggregated_domains = quotient
diff --git a/src/visualising/visualiser.py b/src/visualising/visualiser.py
index aa16b85..ee23e71 100644
--- a/src/visualising/visualiser.py
+++ b/src/visualising/visualiser.py
@@ -2,31 +2,33 @@
 
 import rospy
 
-from monitoring.watchdog import Watchdog
-from visualising.arduino import Arduino
-from visualising.connection import Connection, ArduinoException
-from visualising.animation import Animation
+from visualising.monitoring.watchdog import Watchdog
+from visualising.communication.arduino import Arduino
+from visualising.communication.channel.connection import Connection
 
 
 class Visualiser:
     def __init__(self):
-        port = rospy.get_param("/arduino_port", "/dev/ttyUSB0")
-        baud = rospy.get_param("/arduino_baud", 57600)
-        self.visualisation_strategy = rospy.get_param("/visualisation_strategy", 1)
+        port = rospy.get_param("/visualiser/arduino_port")
+        baud = rospy.get_param("/visualiser/arduino_baud")
+        freq = rospy.get_param("/visualiser/vis_freq")
 
-        self.arduino = Arduino(Connection(port, baud))
+        # self.arduino = Arduino(Connection(port, baud))
         self.watchdog = Watchdog()
 
-    def visualise(self):
-        if self.visualisation_strategy == 1:
-            self.mood_mode()
-        elif self.visualisation_strategy == 2:
-            self.info_mode()
+        if rospy.get_param("/visualiser/visualisation_strategy") == 1:
+            self.expression = None
         else:
-            rospy.logwarn("Visualiser: The specified strategy is not recognised by the visualiser!")
+            self.expression = None
 
-    def info_mode(self):
-        print("Hallo!")
+        if not freq > 0.0:
+            rospy.logwarn(
+                "Visualiser: The frequency at which the visualisation of the robot state is updated must be greater "
+                "than 0! Using 1 as frequency!")
+            freq = 1.0
 
-    def stream_animation(self, animation):
-        self.arduino.stream_animation(animation)
+        self.timer = rospy.Timer(rospy.Duration(1.0 / freq), self.visualise)
+
+    def visualise(self, event):
+        # self.expression.react()
+        rospy.logwarn(self.watchdog.aggregated_domains)
-- 
GitLab