From e2fbe2a6e12cf77662fa4c4fbd07e503a48bc5af Mon Sep 17 00:00:00 2001
From: Matthias Bodenbenner <m.bodenbenner@wzl-mq.rwth-aachen.de>
Date: Thu, 21 Mar 2024 07:59:16 +0100
Subject: [PATCH] updated to newest soil version and updated evaluations
 scripts

---
 README.md                                     |   8 ++
 assets/interfaces/cartCMM.soil                |  29 ++++
 assets/interfaces/ipp.soil                    |  23 ++++
 assets/interfaces/part.soil                   |  29 ++++
 assets/interfaces/tool.soil                   |  59 ++++++++
 assets/interfaces/toolChanger.soil            |  23 ++++
 init.bat                                      |   1 +
 requirements.txt                              |   8 +-
 requirements_dev.txt                          |   2 +-
 scripts/generate.py                           |   2 +-
 src/assessment.py                             |   2 +-
 src/evaluation.py                             |  23 ++--
 src/ipp/hwc/__init__.py                       |   0
 src/ipp/hwc/com_tool.py                       |  17 +++
 src/ipp/hwc/device.py                         |  55 ++++++++
 src/ipp/hwc/ippServerConfig.toml              |   5 +
 src/ipp/hwc/ipp_client.py                     | 126 ++++++++++++++++++
 src/ipp/hwc/transaction.py                    |  92 +++++++++++++
 src/ipp/main.py                               |  12 ++
 src/lasertracker/hwc/com_base.py              |   6 +-
 src/lasertracker/hwc/com_target.py            |   4 +-
 src/monitoring/hwc/com_environmentalsensor.py |  24 ++--
 src/robot/hwc/com_mobilerobot.py              |  10 +-
 src/robot/hwc/com_robot.py                    |   6 +-
 24 files changed, 527 insertions(+), 39 deletions(-)
 create mode 100644 assets/interfaces/cartCMM.soil
 create mode 100644 assets/interfaces/ipp.soil
 create mode 100644 assets/interfaces/part.soil
 create mode 100644 assets/interfaces/tool.soil
 create mode 100644 assets/interfaces/toolChanger.soil
 create mode 100644 src/ipp/hwc/__init__.py
 create mode 100644 src/ipp/hwc/com_tool.py
 create mode 100644 src/ipp/hwc/device.py
 create mode 100644 src/ipp/hwc/ippServerConfig.toml
 create mode 100644 src/ipp/hwc/ipp_client.py
 create mode 100644 src/ipp/hwc/transaction.py
 create mode 100644 src/ipp/main.py

diff --git a/README.md b/README.md
index 346d8e5..b2358f3 100644
--- a/README.md
+++ b/README.md
@@ -49,3 +49,11 @@ Funded by the Deutsche Forschungsgemeinschaft (DFG, German Research Foundation)
 
 Funded by the Deutsche Forschungsgemeinschaft (DFG, German Research Foundation) under Project-ID 432233186 -- AIMS.
 
+## License of used third-party libraries
+
+| Library  | License                     |
+|----------|-----------------------------|
+| numpy    | BSD License                 |
+| requests | BSD License                 |
+| toml     | MIT License                 |
+| wzl-udi  | MIT License                 |
\ No newline at end of file
diff --git a/assets/interfaces/cartCMM.soil b/assets/interfaces/cartCMM.soil
new file mode 100644
index 0000000..2a4ce6c
--- /dev/null
+++ b/assets/interfaces/cartCMM.soil
@@ -0,0 +1,29 @@
+import tool;
+import toolChanger;
+import part;
+
+enum CoordinateSystems {
+    MachineCsy
+    MovableMachineCsy
+    MultipleArmCsy
+    RotaryTableVarCsy
+    PartCsy
+}
+
+variable ActiveCsy {
+    name: "ActiveCsy"
+    description: "The currently active coordinate system."
+    datatype: enum
+    dimension: []
+    range: CoordinateSystems
+}
+
+component CartCMM {
+    name: "CartCMM"
+    description: "Cartesian Coordinate Measuring Machine"
+    components:
+        toolChanger.ToolChanger toolChanger
+        part.Part part
+    parameters:
+        tool.Id activeTool
+}
\ No newline at end of file
diff --git a/assets/interfaces/ipp.soil b/assets/interfaces/ipp.soil
new file mode 100644
index 0000000..87ce326
--- /dev/null
+++ b/assets/interfaces/ipp.soil
@@ -0,0 +1,23 @@
+import cartCMM;
+
+function AbortE {
+    name: "AbortE"
+    description: "Aborts all pending transactions and stops the machine from moving."
+}
+
+function ClearAllErrors {
+    name: "ClearAllErrors"
+    description: "Is called to recover from an error."
+}
+
+component Ipp {
+    name: "I++"
+    description: "The top-level component of the Server."
+    components:
+        cartCMM.CartCMM cmm
+    functions:
+        AbortE abortE
+        ClearAllErrors clearAllErrors
+}
+
+interface Ipp IppServer {}
\ No newline at end of file
diff --git a/assets/interfaces/part.soil b/assets/interfaces/part.soil
new file mode 100644
index 0000000..05ac503
--- /dev/null
+++ b/assets/interfaces/part.soil
@@ -0,0 +1,29 @@
+@prefix quantitykind: <http://qudt.org/vocab/quantitykind/> ;
+@prefix unit: <http://qudt.org/vocab/unit/> ;
+
+variable Temperature defines <quantitykind:Temperature> {
+    name: "Temperature"
+    description: "Current temperature of the part."
+    datatype: float
+    dimension: []
+    range: (-50, 200)
+    unit: <unit:DEG_C>
+}
+
+variable Approach defines <quantitykind:Distance>{
+    name: "Approach"
+    description: "Currently specified approach distance of the part."
+    datatype: float
+    dimension: []
+    range: (0, 500)
+    unit: <unit:MilliM>
+}
+
+component Part {
+    name: "Part"
+    description: "Soil representation of a Part."
+    measurements:
+        Temperature temperature
+    parameters:
+        Approach approach
+}
\ No newline at end of file
diff --git a/assets/interfaces/tool.soil b/assets/interfaces/tool.soil
new file mode 100644
index 0000000..0c24d70
--- /dev/null
+++ b/assets/interfaces/tool.soil
@@ -0,0 +1,59 @@
+@prefix quantitykind: <http://qudt.org/vocab/quantitykind/> ;
+@prefix unit: <http://qudt.org/vocab/unit/> ;
+
+variable Name {
+    name: "Name"
+    description: "Name of a Tool"
+    datatype: string
+    dimension: []
+}
+
+variable Id {
+    name: "Id"
+    description: "Id of a Tool"
+    datatype: string
+    dimension: []
+}
+
+# variable PositionList {
+#     name: "PositionList"
+#     description: "List of Points used for ScanOnCurve"
+#     datatype: float
+#     dimension: [0, 3]
+# }
+
+variable Position defines <quantitykind:CartesianCoordinates> {
+    name: "Position"
+    description: "Current position of the tool, consisting of the I++ X, Y and Z values."
+    datatype: float
+    dimension: [3]
+    unit: <unit:MilliM>
+}
+
+function ScanOnCurve {
+    name: "ScanOnCurve"
+    description: "Function that issues a ScanOnCurve command on the I++ Server"
+    arguments:
+        PositionList posL
+}
+
+function PtMeas {
+    name: "PtMeas"
+    description: "Function that issues a PtMeas command on the I++ Server"
+    arguments:
+        Position pos
+}
+
+component Tool {
+    name: "Tool"
+    description: "The currently active Tool."
+    measurements:
+        Position pos
+    parameters:
+        constant Name name
+        constant Id id
+        PositionList posL
+    functions:
+        PtMeas ptMeas
+        ScanOnCurve scanOnCurve
+}
\ No newline at end of file
diff --git a/assets/interfaces/toolChanger.soil b/assets/interfaces/toolChanger.soil
new file mode 100644
index 0000000..f2859ed
--- /dev/null
+++ b/assets/interfaces/toolChanger.soil
@@ -0,0 +1,23 @@
+import tool;
+
+function EnumTools {
+    name: "EnumTools"
+    description: "Function that issues an EnumTools command on the I++ server."
+}
+
+function ChangeTool {
+    name: "EnumTools"
+    description: "Function that issues an ChangeTool command on the I++ server."
+    arguments:
+        tool.Id tool_id
+}
+
+component ToolChanger {
+    name: "ToolChanger"
+    description: "ToolChanger component"
+    components:
+        dynamic tool.Tool tools
+    functions:
+        EnumTools enumTools
+        ChangeTool changeTool
+}
\ No newline at end of file
diff --git a/init.bat b/init.bat
index b412275..2821a3d 100644
--- a/init.bat
+++ b/init.bat
@@ -1,4 +1,5 @@
 cd scripts
+:: python generate.py ipp.soil -g shell -s -hwc
 python generate.py robot.soil -g remote -s -hwc
 python generate.py lasertracker.soil -g remote -s -hwc
 python generate.py monitoring.soil -g remote -s -hwc
diff --git a/requirements.txt b/requirements.txt
index afee9be..4b8519b 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,4 @@
-numpy
-requests
-toml
-wzl-udi==9.3.8
+numpy==1.24.2
+requests==0.10.2
+toml==
+wzl-udi==10.0.0
diff --git a/requirements_dev.txt b/requirements_dev.txt
index f80a934..2092c7c 100644
--- a/requirements_dev.txt
+++ b/requirements_dev.txt
@@ -2,7 +2,7 @@ numpy
 requests
 toml
 tqdm
-wzl-udi==9.3.3
+wzl-udi==10.0.0
 kaleido
 plotly
 pandas
\ No newline at end of file
diff --git a/scripts/generate.py b/scripts/generate.py
index 70aeb9f..7aaf085 100644
--- a/scripts/generate.py
+++ b/scripts/generate.py
@@ -71,6 +71,6 @@ if __name__ == '__main__':
                                    stderr=subprocess.PIPE)
         stdout, stderr = process.communicate()
         print(stdout.decode())
-        print(stderr.decode())
+        print(stderr.decode('latin-1'))
     else:
         print(f'Generator type "{args.generator}" is unknown, must be one of "shell", "local", "remote".')
diff --git a/src/assessment.py b/src/assessment.py
index db1542f..ff7ba1c 100644
--- a/src/assessment.py
+++ b/src/assessment.py
@@ -9,7 +9,7 @@ from assessment.framework import Framework, AssessmentError, FUJI, FAIRChecker
 SRC = os.path.join('.')
 OUT = os.path.join('..', 'out')
 
-PROFILES = True
+PROFILES = False
 METADATA = True
 
 
diff --git a/src/evaluation.py b/src/evaluation.py
index 177a418..241b83f 100644
--- a/src/evaluation.py
+++ b/src/evaluation.py
@@ -8,14 +8,14 @@ SRC = os.path.join('..', 'src')
 ASSESSMENTS = os.path.join('..', 'out')
 OUT = os.path.join('..', 'out')
 
-PROFILES = True
+PROFILES = False
 METADATA = True
 
 if __name__ == '__main__':
 
     dummies = ['lasertracker', 'monitoring', 'robot']
     profile_scores = pandas.DataFrame()
-    metadata_scores = {'f-uji': [], 'fair-checker': []}
+    metadata_scores = pandas.DataFrame(columns=["framework", "dummy", "principle", "value"])
 
     for dummy in dummies:
 
@@ -26,15 +26,18 @@ if __name__ == '__main__':
         for framework in frameworks:
 
             if PROFILES:
-                profile_scores[framework.name] += [
-                    framework.evaluate(os.path.join(ASSESSMENTS, framework.name, dummy, 'profiles'))]
+                framework.evaluate(os.path.join(ASSESSMENTS, framework.name, dummy, 'profiles'), dummy)
 
             if METADATA:
-                metadata_scores[framework.name] += [
-                    framework.evaluate(os.path.join(ASSESSMENTS, framework.name, dummy, 'profiles'))]
+                score = framework.evaluate(os.path.join(ASSESSMENTS, framework.name, dummy, 'metadata'), dummy)
+                metadata_scores = pandas.concat([metadata_scores, score], ignore_index=True)
 
-    average_profile_scores = [profile_scores[key].mean() for key in profile_scores]
-    average_metadata_scores = [metadata_scores[key].mean() for key in metadata_scores]
+    average_scores = metadata_scores.groupby(['framework', 'principle'], as_index=False).agg({'value': 'mean'})
 
-    Framework.visualize(average_profile_scores, list(profile_scores.keys()), 'profiles.png')
-    Framework.visualize(average_metadata_scores, list(profile_scores.keys()), 'metadata.png')
+    print(average_scores)
+
+    # average_profile_scores = [profile_scores[key].mean() for key in profile_scores]
+    # average_metadata_scores = [metadata_scores[key].mean() for key in metadata_scores]
+
+    # Framework.visualize(average_profile_scores, list(profile_scores.keys()), 'profiles.png')
+    Framework.visualize(metadata_scores, 'metadata.png')
diff --git a/src/ipp/hwc/__init__.py b/src/ipp/hwc/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/ipp/hwc/com_tool.py b/src/ipp/hwc/com_tool.py
new file mode 100644
index 0000000..2004333
--- /dev/null
+++ b/src/ipp/hwc/com_tool.py
@@ -0,0 +1,17 @@
+from scripts.gen.ipp.tool.com_tool import COMToolTOP
+from scripts.gen.ipp.hwc.device import Device
+
+
+class COMTool(COMToolTOP):
+    def __init__(self, device: Device, name: str = "", id: str = "", posl: list[float] = None ):
+        COMToolTOP.__init__(self, device, name, id)
+        self._device = device
+
+    def fun_ptmeas(self, arg_pos):
+        self._device.pt_meas(arg_pos[0], arg_pos[1], arg_pos[2])
+
+    def scanoncurve(self, posl):
+        self._device.scanoncurve(posl)
+
+    def get_mea_pos(self):
+        return self._device.position
diff --git a/src/ipp/hwc/device.py b/src/ipp/hwc/device.py
new file mode 100644
index 0000000..604cef5
--- /dev/null
+++ b/src/ipp/hwc/device.py
@@ -0,0 +1,55 @@
+import asyncio
+import time
+from scripts.gen.ipp.device import DeviceTOP
+from threading import Thread
+from . import ipp_client
+import re
+
+
+class Tool(object):
+    def __init__(self, position):
+        self._position = position
+
+    def update(self, position):
+        self._position = position
+
+    @property
+    def position(self):
+        return self._position
+
+
+class Device(DeviceTOP, Thread):
+    def __init__(self):
+        DeviceTOP.__init__(self)
+        Thread.__init__(self)
+        self._x = 0
+        self._y = 0
+        self._z = 0
+        self._tool = Tool(self.position)
+        self._error = False
+        self._ipp_client = None
+
+    async def goto(self, x, y, z):
+        await self._ipp_client.transaction("GoTo(X(" + x + "), Y(" + y + "), Z(" + z + "))", False)
+
+    def pt_meas(self, x, y, z):
+        self._ipp_client.transaction("PtMeas(X(" + str(x) + "), Y(" + str(y) + "), Z(" + str(z) + "))", False)
+
+    @property
+    def position(self):
+        return [self._x, self._y, self._z]
+
+    def run(self):
+        self._ipp_client = ipp_client.IPPClient(self)
+        self._ipp_client.start()
+        time.sleep(1)
+        self._ipp_client.transaction("StartSession()", False)
+        self._ipp_client.transaction("PtMeas(X(1), Y(2), Z(3))", False)
+        self._ipp_client.transaction("GoTo(X(1), Y(2), Z(3))", False)
+        for i in range(1000):
+            self._ipp_client.transaction("ABC(1,2,3)", False)
+        self._ipp_client.transaction("OnMoveReport(X(), Y(), Z())", True)
+
+    def stop(self):
+        loop = asyncio.get_event_loop()
+        loop.run_until_complete(self._ipp_client.transaction("EndSession()", False))
diff --git a/src/ipp/hwc/ippServerConfig.toml b/src/ipp/hwc/ippServerConfig.toml
new file mode 100644
index 0000000..1f90202
--- /dev/null
+++ b/src/ipp/hwc/ippServerConfig.toml
@@ -0,0 +1,5 @@
+dataformat = 'json'
+
+[tcp]
+    address='localhost'
+    port=1294
\ No newline at end of file
diff --git a/src/ipp/hwc/ipp_client.py b/src/ipp/hwc/ipp_client.py
new file mode 100644
index 0000000..ccbf668
--- /dev/null
+++ b/src/ipp/hwc/ipp_client.py
@@ -0,0 +1,126 @@
+from __future__ import annotations
+import socket
+from threading import Thread
+import typing
+from . import transaction
+
+if typing.TYPE_CHECKING:
+    from .device import Device
+
+
+class UnexpectedTagError(Exception):
+    def __init__(self):
+        self.message = "The received message contains an unexpected tag."
+
+
+class MissingACKError(Exception):
+    def __init__(self):
+        self.message = "Was expecting an ACK response but received something else."
+
+
+class MissingTransactionCompleteResponseError(Exception):
+    def __init__(self):
+        self.message = "Transaction complete response is missing."
+
+
+class DaemonNonDataMessage(Exception):
+    def __init__(self):
+        self.message = "Received a daemon message that is not a data message!"
+
+
+class IPPClient(Thread):
+    def __init__(self, device_i: Device):
+        Thread.__init__(self)
+        self._socket = None
+        self._active = False
+        self._command_tag = 1
+        self._transaction_log = {}
+        self._data_log = {}
+        self._daemon_log = {}
+        self._active_streams = []
+        self._device = device_i
+
+    def _handle_event(self, tag, message_type, message_body):
+        non_event_tag = "0" + tag[1:5]
+        if non_event_tag in self._active_streams:
+            daemon = self._transaction_log.get(non_event_tag)
+            if message_type != "#":
+                raise DaemonNonDataMessage()
+            daemon['cb'].update_device(message_body.split(","))
+
+
+    def _parse_message(self, message):
+        print("<-- " + message)
+        message = message.split(" ")
+        tag = message[0]
+        message_type = message[1]
+        if tag[0] == "E":
+            self._handle_event(tag, message_type, "".join(message[2:]))
+            return
+
+        transaction = self._transaction_log.get(tag)
+        if not transaction['ACK']:
+            if message_type == '&':
+                transaction['ACK'] = True
+                return
+            else:
+                raise MissingACKError()
+
+        if message_type == '#':
+            self._data_log[tag + '#' + transaction['uuid']].append(message[::2])
+            return
+
+        if message_type == '%':
+            if not transaction['daemon']:
+                self._transaction_log.pop(tag)
+            return
+
+        return
+
+    def _send(self, message):
+        print("--> " + message.decode())
+        self._socket.sendall(message)
+
+    def transaction(self, command, streams):
+        tag = str(self._command_tag).zfill(5)
+        command_name = command.split('(')[0]
+
+        if streams:
+            self._active_streams.append(tag)
+
+        # Add transaction to pending transaction log
+        self._transaction_log[tag] = {
+            'ACK': False,
+            'uuid': command,
+            'daemon': streams,
+            'cb': transaction.create(command_name, tag, self._device)
+        }
+
+        self._send(str.encode(tag + " " + command + "\n"))
+        self._command_tag = self._command_tag + 1
+
+    def _listen(self):
+        buffer = []
+        stitch = False
+        while True:
+            if (len(buffer) > 0 and not stitch) or (len(buffer) > 1):
+                self._parse_message(buffer[0])
+                buffer.pop(0)
+                continue
+            data, _ = self._socket.recvfrom(1024)
+            data = data.decode()
+            buffer = buffer + (data.splitlines())
+            if stitch:
+                buffer[0:2] = ["".join(buffer[0:2])]
+                stitch = False
+            if len(data) > 0:
+                if data[-1] != '\n':
+                    stitch = True
+
+    def run(self):
+        self._active = True
+        self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        self._socket.connect(('localhost', 1294))
+
+        listener = Thread(target=self._listen)
+        listener.start()
diff --git a/src/ipp/hwc/transaction.py b/src/ipp/hwc/transaction.py
new file mode 100644
index 0000000..82b67f6
--- /dev/null
+++ b/src/ipp/hwc/transaction.py
@@ -0,0 +1,92 @@
+from __future__ import annotations
+from abc import ABC, abstractmethod
+import re
+import typing
+from sys import modules
+
+if typing.TYPE_CHECKING:
+    from .device import Device
+
+
+class TransactionNotImplementedException(Exception):
+    def __init__(self):
+        self.message = "Tried to use an unimplemented Transaction."
+
+
+class Transaction(ABC):
+
+    def __init__(self, tag: str, device: Device):
+        self._tag = tag
+        self._device = device
+
+    @property
+    def tag(self) -> str:
+        return self._tag
+
+    @abstractmethod
+    def update_device(self, data):
+        ...
+
+
+class OnMoveReportTransaction(Transaction):
+    def __init__(self, tag: str, device: Device):
+        Transaction.__init__(self, tag, device)
+
+    def update_device(self, data):
+        print("OnMoveReport update: " + '[%s]' % ', '.join(map(str, data)))
+        for data_point in data:
+            axis = data_point.split("(")[0]
+            value = re.findall(r"-?\d+\.\d+", data_point)
+            value = value[0]
+            match axis:
+                case "X":
+                    self._device._x = float(value)
+                case "Y":
+                    self._device._y = float(value)
+                case "Z":
+                    self._device._z = float(value)
+        # self._tool.update([self._x, self._y, self._z])
+
+
+class StartSessionTransaction(Transaction):
+    def __init__(self, tag: str, device: Device):
+        Transaction.__init__(self, tag, device)
+
+    def update_device(self, data):
+        pass
+
+
+class PtMeasTransaction(Transaction):
+    def __init__(self, tag: str, device: Device):
+        Transaction.__init__(self, tag, device)
+
+    def update_device(self, data):
+        pass
+
+
+class ScanOnCurveTransaction(Transaction):
+    def __init__(self, tag: str, device: Device):
+        Transaction.__init__(self, tag, device)
+
+    def update_device(self, data):
+        pass
+
+
+class GoToTransaction(Transaction):
+    def __init__(self, tag: str, device: Device):
+        Transaction.__init__(self, tag, device)
+
+    def update_device(self, data):
+        pass
+
+
+def create(command: str, tag: str, device: Device) -> Transaction | None:
+    module = modules[__name__]
+    try:
+        transaction = getattr(module, '{}Transaction'.format(command))(tag, device)
+    except Exception:
+        print("Unknown command: " + '"' + command + '"')
+        print("No callback registered.")
+        return None
+    return transaction
+
diff --git a/src/ipp/main.py b/src/ipp/main.py
new file mode 100644
index 0000000..25e0a06
--- /dev/null
+++ b/src/ipp/main.py
@@ -0,0 +1,12 @@
+import toml
+
+import start
+from hwc.device import Device
+from com_ipp import COMIpp
+
+if __name__ == '__main__':
+    device = Device()
+    soilClient = COMIpp(device)
+    device.start()
+
+    start.start(soilClient, toml.load('config.toml'), 'model.json')
\ No newline at end of file
diff --git a/src/lasertracker/hwc/com_base.py b/src/lasertracker/hwc/com_base.py
index bd8dbfc..478847c 100644
--- a/src/lasertracker/hwc/com_base.py
+++ b/src/lasertracker/hwc/com_base.py
@@ -24,13 +24,13 @@ class COMBase(COMBaseTOP):
         self._device.point_to(*par_position)
 
     def get_mea_azimuth(self):
-        return self._device.azimuth
+        return self._device.azimuth, random.random() * 10e-10
 
     def get_mea_elevation(self):
-        return self._device.elevation
+        return self._device.elevation, random.random() * 10e-10
 
     def get_mea_distance(self):
-        return self._device.distance
+        return self._device.distance, random.random() * 10e-9
 
     def get_par_state(self):
         return str(self._device.state)
diff --git a/src/lasertracker/hwc/com_target.py b/src/lasertracker/hwc/com_target.py
index 0c0d817..cb0710f 100644
--- a/src/lasertracker/hwc/com_target.py
+++ b/src/lasertracker/hwc/com_target.py
@@ -1,3 +1,5 @@
+import random
+
 import time
 from typing import List
 
@@ -21,7 +23,7 @@ class COMTarget(COMTargetTOP):
             yield self.get_mea_position(), arg_label
 
     def get_mea_position(self):
-        return self._device.target.position
+        return self._device.target.position, [[random.random()*10e-9 for j in range(3)] for i in range(3)]
 
     def get_par_state(self):
         return str(self._device.target.state)
diff --git a/src/monitoring/hwc/com_environmentalsensor.py b/src/monitoring/hwc/com_environmentalsensor.py
index dc1bd5a..ab7f19a 100644
--- a/src/monitoring/hwc/com_environmentalsensor.py
+++ b/src/monitoring/hwc/com_environmentalsensor.py
@@ -1,3 +1,7 @@
+from typing import Tuple, Any
+
+import random
+
 from com_environmentalsensor import COMEnvironmentalSensorTOP
 from hwc.device import Device
 
@@ -8,20 +12,20 @@ class COMEnvironmentalSensor(COMEnvironmentalSensorTOP):
         COMEnvironmentalSensorTOP.__init__(self, device, location)
         self.uuid = id
         
-    def get_mea_temperature(self) -> float:
-        return self._device.sensors[self.uuid].temperature
+    def get_mea_temperature(self) -> Tuple[float, Any]:
+        return self._device.sensors[self.uuid].temperature, random.random() * 10e-2
 
-    def get_mea_pressure(self) -> float:
-        return self._device.sensors[self.uuid].pressure
+    def get_mea_pressure(self) -> Tuple[float, Any]:
+        return self._device.sensors[self.uuid].pressure, random.random() * 10e-1
 
-    def get_mea_humidity(self) -> float:
-        return self._device.sensors[self.uuid].humidity
+    def get_mea_humidity(self) -> Tuple[float, Any]:
+        return self._device.sensors[self.uuid].humidity, random.random() * 10e-3
 
-    def get_mea_signalstrength(self) -> int:
-        return self._device.sensors[self.uuid].signal_strength
+    def get_mea_signalstrength(self) -> Tuple[int, Any]:
+        return self._device.sensors[self.uuid].signal_strength, None
 
-    def get_mea_batterylevel(self) -> int:
-        return self._device.sensors[self.uuid].battery_level
+    def get_mea_batterylevel(self) -> Tuple[int, Any]:
+        return self._device.sensors[self.uuid].battery_level, None
 
     def get_par_location(self) -> str:
         return self._par_location
diff --git a/src/robot/hwc/com_mobilerobot.py b/src/robot/hwc/com_mobilerobot.py
index d428f15..3665c28 100644
--- a/src/robot/hwc/com_mobilerobot.py
+++ b/src/robot/hwc/com_mobilerobot.py
@@ -1,4 +1,4 @@
-from typing import List
+from typing import List, Any, Tuple
 
 import numpy
 
@@ -13,11 +13,11 @@ class COMMobileRobot(COMMobileRobotTOP):
         self._mea_position = numpy.array([0, 0, 0], dtype=float)
         self._device.start()
 
-    def get_mea_position(self) -> List[float]:
-        return self._device.position.tolist()
+    def get_mea_position(self) -> Tuple[List[float], Any]:
+        return self._device.position.tolist(), None
 
-    def get_mea_batterylevel(self) -> int:
-        return self._device.batterylevel
+    def get_mea_batterylevel(self) -> Tuple[int, Any]:
+        return self._device.batterylevel, None
 
     def get_par_auto(self) -> bool:
         return self._device.mode == Mode.AUTO
diff --git a/src/robot/hwc/com_robot.py b/src/robot/hwc/com_robot.py
index 62cfdd3..db212f0 100644
--- a/src/robot/hwc/com_robot.py
+++ b/src/robot/hwc/com_robot.py
@@ -1,5 +1,5 @@
 import time
-from typing import List
+from typing import List, Tuple, Any
 
 import numpy
 
@@ -16,8 +16,8 @@ class COMRobot(COMRobotTOP):
         self._mea_position = numpy.array([0, 0, 0], dtype=float)
         self._com_gripper = COMGripper(device, open=True)
 
-    def get_mea_position(self) -> List[float]:
-        return self._device.position.tolist()
+    def get_mea_position(self) -> Tuple[List[float], Any]:
+        return self._device.position.tolist(), None
 
     def fun_goto(self, arg_position: List[float] = None):
         self._device.goto(numpy.array(arg_position))
-- 
GitLab