From 8649b34f6ad9f2da63e504a55953839cfa7af74c Mon Sep 17 00:00:00 2001
From: Matthias Bodenbenner <m.bodenbenner@wzl-mq.rwth-aachen.de>
Date: Thu, 14 Apr 2022 13:40:56 +0200
Subject: [PATCH] repo-clean up & bug fix

---
 .gitignore                                    |  10 +-
 .gitlab-ci.yml                                |  39 ++---
 README.md                                     |  72 +++++-----
 build.bat                                     |   7 -
 doc/source/index.rst                          |   2 +-
 doc/source/modules.rst                        |   7 -
 .../device/com_base_top.py                    |   2 +-
 .../device/com_basestations.py                |   2 +-
 .../device/com_lasertracker_top.py            |   6 +-
 .../device/com_mobileentities.py              |   4 +-
 .../device/com_target_top.py                  |   2 +-
 examples/virtual_lasertracker/device/const.py |   0
 .../virtual_lasertracker/device/device.py     |   2 -
 examples/virtual_lasertracker/device/start.py |  16 +--
 examples/virtual_lasertracker/hwc/com_base.py |   2 +-
 .../hwc/com_lasertracker.py                   |   2 +-
 .../virtual_lasertracker/hwc/com_target.py    |   2 +-
 examples/virtual_lasertracker/main.py         |   2 +-
 install.bat                                   |   1 -
 setup.py                                      |  15 +-
 src/http/server.py                            |   2 +-
 src/soil/component.py                         |   7 -
 src/soil/docstring_parser.py                  |   1 -
 src/soil/measurement.py                       |   5 +-
 src/soil/object.py                            |   7 -
 src/soil/variable.py                          |   5 +-
 start.bat                                     |   7 -
 test.bat                                      |   7 -
 test/__init__.py                              |   0
 .../server}/__init__.py                       |   0
 test/server/main.py                           |  23 +++
 test/server/start.py                          | 133 ++++++++++++++++++
 32 files changed, 259 insertions(+), 133 deletions(-)
 delete mode 100644 build.bat
 delete mode 100644 doc/source/modules.rst
 delete mode 100644 examples/virtual_lasertracker/device/const.py
 delete mode 100644 install.bat
 delete mode 100644 start.bat
 delete mode 100644 test.bat
 delete mode 100644 test/__init__.py
 rename {examples/virtual_lasertracker => test/server}/__init__.py (100%)
 create mode 100644 test/server/main.py
 create mode 100644 test/server/start.py

diff --git a/.gitignore b/.gitignore
index ca1efaf..8a99bb3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,8 +1,12 @@
-**/.idea/
+.idea
 **/__pycache__/
 **/logs/
 **/log/
 **/build/
 **/*.egg-info/
-**/venv*/
-**/dist
+dist
+wiki
+
+*.pyc
+
+test/server/server_config.toml
\ No newline at end of file
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index ea66582..3816750 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -18,7 +18,7 @@ Build Wheel:
     - master
     - legacy
   script:
-    - '& cmd /k "C:\Anaconda3\Scripts\activate.bat E:\environments\$env:UUID & python setup.py sdist bdist_wheel"'
+    - python setup.py sdist bdist_wheel
 
 Copy To Package Registry:
   image: python:3.9
@@ -32,24 +32,25 @@ Copy To Package Registry:
     - master
     - legacy
   script:
-    - '& cmd /k "C:\Anaconda3\Scripts\activate.bat E:\environments\$env:UUID & pip install twine & python -m twine upload -u gitlab-ci-token -p $CI_JOB_TOKEN --repository-url https://git-ce.rwth-aachen.de/api/v4/projects/${CI_PROJECT_ID}/packages/pypi dist/*"'
-
-pages:
-  image: python:3.9
-  stage: deploy
-  tags:
-    - frodo
-  only:
-    - master
-  script:
-    - pip3 install -r requirements.txt
-    - mkdir public
-    - cd doc
-    - make html
-    - cp -r ./build/html/. ../public
-  artifacts:
-    paths:
-      - public
+    - pip install twine
+    - python -m twine upload -u gitlab-ci-token -p $CI_JOB_TOKEN --repository-url https://git-ce.rwth-aachen.de/api/v4/projects/${CI_PROJECT_ID}/packages/pypi dist/*
+
+#pages:
+#  image: python:3.9
+#  stage: deploy
+#  tags:
+#    - frodo
+#  only:
+#    - master
+#  script:
+#    - pip3 install -r requirements.txt
+#    - mkdir public
+#    - cd doc
+#    - make html
+#    - cp -r ./build/html/. ../public
+#  artifacts:
+#    paths:
+#      - public
 
 
 
diff --git a/README.md b/README.md
index 68ec6ba..b2a2e68 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
 [![Build](https://git-ce.rwth-aachen.de/wzl-mq-ms/forschung-lehre/lava/unified-device-interface/python/badges/master/pipeline.svg)](https://git-ce.rwth-aachen.de/wzl-mq-ms/forschung-lehre/lava/unified-device-interface/python/commits/master)
 
 # Python Unified Device Interface
-Current stable version: 6.1.1  
+Current stable version: 6.1.2  
 Stable legacy version: 5.2.7
 
 ## Installation
@@ -20,46 +20,52 @@ pip install --extra-index-url https://package-read:sTkDQPiyBqkUyYVXZfeK@git-ce.r
 
 ## Usage
 For using the PUDI device interface you need to have a SOIL-Model of your device in JSON-Format. See the Wiki for more information.
-You can design the model by hand our use the [SOIL-Editor](https://git-ce.rwth-aachen.de/wzl-mq-ms/forschung-lehre/modelizer), to generate the source code and get a ready-to-run python script for your device server.
+You can design the model by hand our use the [SOIL-Web-Editor](https://iot.wzl-mq.rwth-aachen.de/soil/), to generate the source code and get a ready-to-run python script for your device server.
 Please keep in mind, that creating a service with the SOIL-Editor gives your more features you can use.
 
 ### Manual setup
 If you do not use the SOIL-Editor, create a JSON-Model of your device and a mapping dictionary by hand.
-  Please refer to the Servetize section of the repository's Wiki.
-Setup your server as follows:
-
-```python
-    # set up servers
-    executor = ThreadPoolExecutor(max_workers=100)
-    loop = asyncio.get_event_loop()
-    loop.set_default_executor(executor)
-
-    # configure model
-    macs = ['AC-DE-48-00-00-80', 'AE-DE-48-00-00-80', 'BC-DE-48-00-00-80']
-    sensors = [Sensor(macs[i]) for i in range(3)]
-    implementation = Environment(sensors)
-    
-    # configure HTTP server
-    mapping = src.test.devices.mapping.mapping(implementation)
-    model = Component.load('./src/test/devices/environmental_sensor/Environment.json', mapping)
-    http = HTTPServer(loop, HTTP_ADDRESS, HTTP_PORT, model)
-
-    # start servers
-    try:
-        loop.run_forever()
-    except:
-        pass
-    finally:
-        loop.close()
-```
+  Please refer to the Servetize section of the repository's Wiki. Additionally, you can check the _example_ directory for exemplary implementation(s).
+
+## Citation & References
+
+Scientific background and publications related to the _(Python) Unified Device Interface_ are:
+
+[Bodenbenner, M.](mailto:m.bodenbenner@wzl-mq.rwth-aachen.de); Sanders, M. P.; Montavon, B.; Schmitt, R. H. (2021): 
+Domain-Specific Language for Sensors in the Internet of Production. 
+In: Bernd-Arno Behrens, Alexander Brosius, Wolfgang Hintze, Steffen Ihlenfeldt und Jens Peter Wulfsberg (Hg.): 
+Production at the leading edge of technology. Proceedings of the 10th Congress of the German Academic Association for Production Technology (WGP), Dresden, 23-24 September 2020. Berlin, Heidelberg, 2021. 1st ed. 2021. Berlin, Heidelberg: Springer (Lecture Notes in Production Engineering), S. 448–456, 
+http://dx.doi.org/10.1007/978-3-662-62138-7_45
+
+[Bodenbenner, M.](mailto:m.bodenbenner@wzl-mq.rwth-aachen.de); Montavon, B.; Schmitt, R.H. (2021): 
+FAIR sensor services - Towards sustainable sensor data management. 
+In: Measurement: Sensors 18, S. 100206, 
+https://doi.org/10.1016/j.measen.2021.100206
+
+[Montavon, B.](mailto:b.montavon@wzl-mq.rwth-aachen.de)(2021): 
+Virtual Reference Frame Based on Distributed Large-Scale Metrology Providing Coordinates as a Service. 
+Aachen: Apprimus Verlag,
+https://doi.org/10.18154/RWTH-2021-10238
 
-1. Do not change the three lines below `# set up servers` and below `# start servers`.
-2. Replace the lines below `# configure model` by the initialization of your device.
-3. Pass your initialized device to the docstring parser for MQTT and HTTP.
-4. If you are not using MQTT or HTTP leave out the respective lines.
+[Montavon, B.](mailto:b.montavon@wzl-mq.rwth-aachen.de); Peterek, M.; Schmitt, R. H. (2019): 
+Model-based interfacing of large-scale metrology instruments. 
+In: Ettore Stella (Hg.): Multimodal Sensing: Technologies and Applications. 26-27 June 2019, Munich, Germany. Multimodal Sensing and Artificial Intelligence: Technologies and Applications. Munich, Germany, 6/24/2019 - 6/27/2019. Bellingham, Washington: SPIE (Proceedings of SPIE. 5200-, volume 11059), S. 11,
+https://doi.org/10.1117/12.2527461
+
+## Acknowledgements
+
+The authors acknowledge funding from the LaVA project (Large Volume Applications, contract 17IND03 of the
+European Metrology Programme for Innovation and Research EMPIR). The EMPIR initiative is co-funded by
+the European Union’s Horizon 2020 research and innovation programme and the EMPIR Participating States.
+
+Funded by the Deutsche Forschungsgemeinschaft (DFG, German Research Foundation) under Germany's Excellence Strategy – EXC-2023 Internet of Production – 390621612.
 
 ## Recent changes
 
+6.1.2 | 5.2.7 - 2022-04-14
+
+- bug fix of loop handling of aiohttp web application
+
 6.1.1 | 5.2.7 - 2021-05-19
 
 - improved error output for developers
diff --git a/build.bat b/build.bat
deleted file mode 100644
index 1b35742..0000000
--- a/build.bat
+++ /dev/null
@@ -1,7 +0,0 @@
-@echo off
-rmdir dist /s /q
-rmdir build /s /q
-rmdir wzl_udi.egg-info /s /q
-python setup.py bdist_wheel
-:: TODO complete line below
-:: copy "../dist/"   "D:\users\bdn\Forschungsprojekte\Virtual Metrology Frame\SourceCode\PyPi server"
\ No newline at end of file
diff --git a/doc/source/index.rst b/doc/source/index.rst
index c151183..39b5037 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -4,7 +4,7 @@
    contain the root `toctree` directive.
 
 Welcome to Python Unified Device Interface's documentation!
-==============================================
+===========================================================
 
 .. toctree::
    :maxdepth: 2
diff --git a/doc/source/modules.rst b/doc/source/modules.rst
deleted file mode 100644
index e9ff8ac..0000000
--- a/doc/source/modules.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-src
-===
-
-.. toctree::
-   :maxdepth: 4
-
-   src
diff --git a/examples/virtual_lasertracker/device/com_base_top.py b/examples/virtual_lasertracker/device/com_base_top.py
index d8c1368..8337d53 100644
--- a/examples/virtual_lasertracker/device/com_base_top.py
+++ b/examples/virtual_lasertracker/device/com_base_top.py
@@ -1,5 +1,5 @@
 from typing import List
-from .enums import StateEnum
+from device.enums import StateEnum
 
 
 class COMBaseTOP(object):
diff --git a/examples/virtual_lasertracker/device/com_basestations.py b/examples/virtual_lasertracker/device/com_basestations.py
index d4022e8..be47842 100644
--- a/examples/virtual_lasertracker/device/com_basestations.py
+++ b/examples/virtual_lasertracker/device/com_basestations.py
@@ -1,4 +1,4 @@
-from ..hwc.com_base import COMBase
+from hwc.com_base import COMBase
 
 
 class COMBaseStations(object):
diff --git a/examples/virtual_lasertracker/device/com_lasertracker_top.py b/examples/virtual_lasertracker/device/com_lasertracker_top.py
index d3fb3d5..cdba60e 100644
--- a/examples/virtual_lasertracker/device/com_lasertracker_top.py
+++ b/examples/virtual_lasertracker/device/com_lasertracker_top.py
@@ -1,8 +1,8 @@
 import datetime
 
-from .com_basestations import COMBaseStations
-from .com_mobileentities import COMMobileEntities
-from .enums import StateEnum
+from device.com_basestations import COMBaseStations
+from device.com_mobileentities import COMMobileEntities
+from device.enums import StateEnum
 
 
 class COMLasertrackerTOP(object):
diff --git a/examples/virtual_lasertracker/device/com_mobileentities.py b/examples/virtual_lasertracker/device/com_mobileentities.py
index 45989fd..dfc1eb1 100644
--- a/examples/virtual_lasertracker/device/com_mobileentities.py
+++ b/examples/virtual_lasertracker/device/com_mobileentities.py
@@ -1,5 +1,5 @@
-from ..hwc.com_target import COMTarget
-from .enums import StateEnum, ModeEnum
+from hwc.com_target import COMTarget
+from device.enums import StateEnum, ModeEnum
 
 
 class COMMobileEntities(object):
diff --git a/examples/virtual_lasertracker/device/com_target_top.py b/examples/virtual_lasertracker/device/com_target_top.py
index 73a58b5..89cf1aa 100644
--- a/examples/virtual_lasertracker/device/com_target_top.py
+++ b/examples/virtual_lasertracker/device/com_target_top.py
@@ -1,7 +1,7 @@
 from typing import Callable
 from typing import List
 
-from .enums import StateEnum, ModeEnum
+from device.enums import StateEnum, ModeEnum
 
 
 class COMTargetTOP(object):
diff --git a/examples/virtual_lasertracker/device/const.py b/examples/virtual_lasertracker/device/const.py
deleted file mode 100644
index e69de29..0000000
diff --git a/examples/virtual_lasertracker/device/device.py b/examples/virtual_lasertracker/device/device.py
index f47c3bd..2ecd6e6 100644
--- a/examples/virtual_lasertracker/device/device.py
+++ b/examples/virtual_lasertracker/device/device.py
@@ -1,9 +1,7 @@
 class Device(object):
 
     def __init__(self):
-        # TODO implement
         pass
 
     def __del__(self):
-        # TODO implement
         pass
\ No newline at end of file
diff --git a/examples/virtual_lasertracker/device/start.py b/examples/virtual_lasertracker/device/start.py
index 2835b9e..78413a9 100644
--- a/examples/virtual_lasertracker/device/start.py
+++ b/examples/virtual_lasertracker/device/start.py
@@ -7,17 +7,17 @@ from typing import Dict
 from wzl.mqtt import MQTTPublisher
 from wzl.utilities import root_logger
 
-from src.http.server import HTTPServer
-from src.soil.component import Component
-from src.soil.event import Event, EventSeverity, EventTrigger
-from src.soil.stream import FixedJob, EventJob, UpdateJob, StreamScheduler
-from .enums import StateEnum
-from ..hwc.com_lasertracker import COMLasertracker
+from wzl.http.server import HTTPServer
+from wzl.soil.component import Component
+from wzl.soil.event import Event, EventSeverity, EventTrigger
+from wzl.soil.stream import FixedJob, EventJob, UpdateJob, StreamScheduler
+from device.enums import StateEnum
+from hwc.com_lasertracker import COMLasertracker
 
 sys.setswitchinterval(0.0005)
 
 
-def start(com_lasertracker: COMLasertracker, config: Dict):
+def start(com_lasertracker: COMLasertracker, config: Dict, soil_model_file: str):
     # server settings
     address = config['http']['address']
     port = config['http']['port']
@@ -119,7 +119,7 @@ def start(com_lasertracker: COMLasertracker, config: Dict):
         submapping['FUN-Trigger'] = {
             'method': com_lasertracker._com_mobileentities._com_target[child0_uuid].fun_trigger,
             'signature': {'arguments': {}, 'returns': []}, 'mqtt_callback': mqtt.publish}
-    model = Component.load('../Lasertracker.json', mapping['COM-Lasertracker'])
+    model = Component.load(soil_model_file, mapping['COM-Lasertracker'])
 
     http = HTTPServer(loop, address, port, model)
 
diff --git a/examples/virtual_lasertracker/hwc/com_base.py b/examples/virtual_lasertracker/hwc/com_base.py
index c918499..9586d61 100644
--- a/examples/virtual_lasertracker/hwc/com_base.py
+++ b/examples/virtual_lasertracker/hwc/com_base.py
@@ -1,7 +1,7 @@
 import random
 from math import pi, cos, sin
 
-from ..device.com_base_top import COMBaseTOP
+from device.com_base_top import COMBaseTOP
 
 
 class COMBase(COMBaseTOP):
diff --git a/examples/virtual_lasertracker/hwc/com_lasertracker.py b/examples/virtual_lasertracker/hwc/com_lasertracker.py
index 1887e52..a613868 100644
--- a/examples/virtual_lasertracker/hwc/com_lasertracker.py
+++ b/examples/virtual_lasertracker/hwc/com_lasertracker.py
@@ -1,6 +1,6 @@
 import datetime
 
-from ..device.com_lasertracker_top import COMLasertrackerTOP
+from device.com_lasertracker_top import COMLasertrackerTOP
 
 
 class COMLasertracker(COMLasertrackerTOP):
diff --git a/examples/virtual_lasertracker/hwc/com_target.py b/examples/virtual_lasertracker/hwc/com_target.py
index f0b0f9c..923da54 100644
--- a/examples/virtual_lasertracker/hwc/com_target.py
+++ b/examples/virtual_lasertracker/hwc/com_target.py
@@ -1,7 +1,7 @@
 import json
 import time
 
-from ..device.com_target_top import COMTargetTOP
+from device.com_target_top import COMTargetTOP
 
 
 class COMTarget(COMTargetTOP):
diff --git a/examples/virtual_lasertracker/main.py b/examples/virtual_lasertracker/main.py
index e9a04b9..27c0eb7 100644
--- a/examples/virtual_lasertracker/main.py
+++ b/examples/virtual_lasertracker/main.py
@@ -12,4 +12,4 @@ if __name__ == "__main__":
     lasertracker = COMLasertracker(device)
     device.start()
 
-    start(lasertracker, config)
+    start(lasertracker, config, './Lasertracker.json')
diff --git a/install.bat b/install.bat
deleted file mode 100644
index 838c3f2..0000000
--- a/install.bat
+++ /dev/null
@@ -1 +0,0 @@
-FOR /F "delims=~" %f in (packages.txt) DO conda install --yes "%f" || pip install "%f"
\ No newline at end of file
diff --git a/setup.py b/setup.py
index a2d7aec..4ad8f42 100644
--- a/setup.py
+++ b/setup.py
@@ -1,13 +1,20 @@
 from setuptools import setup, find_packages
 
 setup(name='wzl-udi',
-      version='6.1.1',
-      url='',
+      version='6.1.2',
+      url='', # TODO add url after moving to public group
       author='Matthias Bodenbenner',
-      author_email='m.bodenbenner@wzl.rwth-aachen.de',
+      author_email='m.bodenbenner@wzl.mq.rwth-aachen.de',
       description='Provides REST-server, publisher-interface and serializer for the Unified Device Interface in Python.',
       package_dir={'wzl': 'src'},
       packages=['wzl.http', 'wzl.soil', 'wzl.utils'],
       long_description=open('./README.md').read(),
-      install_requires=['nest-asyncio', 'strict-rfc3339', 'docstring_parser==0.7.1', 'aiohttp', 'wzl-mqtt', 'wzl-utilities', 'deprecated'],
+      install_requires=['aiohttp==3.8.1',
+                        'Deprecated==1.2.13',
+                        'docstring_parser==0.7.3',
+                        'nest-asyncio==1.4.3',
+                        'strict-rfc3339==0.7',
+                        'wzl-mqtt==2.5.1',
+                        'wzl-utilities==1.2.2'
+                        ],
       zip_safe=False)
diff --git a/src/http/server.py b/src/http/server.py
index 225e668..ee6c219 100644
--- a/src/http/server.py
+++ b/src/http/server.py
@@ -66,7 +66,7 @@ class HTTPServer(object):
         self.app.router.add_put(r'/{uuids:(' + BASE_UUID_PATTERN + r'($|/))*}', self.put)
         self.app.router.add_patch(r'/objects{uuids:(/' + BASE_UUID_PATTERN + r')*/?}', self.patch)
         self.app.router.add_patch(r'/{uuids:(' + BASE_UUID_PATTERN + r'($|/))*}', self.patch)
-        web.run_app(self.app, host=self.host, port=self.port)
+        web.run_app(self.app, host=self.host, port=self.port, loop=loop)
         logger.info('HTTP-Server serving on {}:{}'.format(host, port))
 
     @staticmethod
diff --git a/src/soil/component.py b/src/soil/component.py
index 77cd5c2..c0bb6eb 100644
--- a/src/soil/component.py
+++ b/src/soil/component.py
@@ -208,13 +208,6 @@ class Component(Element):
         if 'children' in keys:
             everything = self._components + self._measurements + self._parameters + self._functions
             dictionary['children'] = list(map(lambda x: x.serialize(['name', 'uuid']), everything))
-        # if 'update' in keys:
-        #     # TODO implement (update all children)
-        #     pass
-        #     # if self._implementation is not None:
-        #     #     dict['update'] = self._implementation()
-        # else:
-        #     dict['children'] = list(map(lambda x: x.serialize(['value', 'uuid']), self._measurements))
         return dictionary
 
     @staticmethod
diff --git a/src/soil/docstring_parser.py b/src/soil/docstring_parser.py
index 39c48aa..e1d7559 100644
--- a/src/soil/docstring_parser.py
+++ b/src/soil/docstring_parser.py
@@ -89,7 +89,6 @@ def parse_docstrings_for_mqtt(implementation, parent_url="", *args, **kwargs):
 
 @deprecated(version='6.0.0', reason='Building service model from docstrings is too error-prone.')
 def parse_docstrings_for_soil(implementation: Any, parent_url="", *args, **kwargs):
-    # TODO replace assertions by reasonable error handling
     if implementation is None:
         return None
 
diff --git a/src/soil/measurement.py b/src/soil/measurement.py
index c38f792..875980e 100644
--- a/src/soil/measurement.py
+++ b/src/soil/measurement.py
@@ -16,8 +16,8 @@ class Measurement(Figure):
         if uuid[:3] != 'MEA':
             raise Exception('{}: The UUID must start with MEA!'.format(uuid))
         self._unit = unit
-        self._covariance = None  # TODO init
-        self._uncertainty = None  # TODO init
+        self._covariance = None
+        self._uncertainty = None
         self._timestamp = None
         self._nonce = nonce
 
@@ -90,7 +90,6 @@ class Measurement(Figure):
         if not keys:
             keys = ['uuid', 'name', 'description', 'datatype', 'value', 'dimension', 'range', 'timestamp', 'nonce', 'covariance', 'uncertainty',
                     'unit', 'ontology']
-        # TODO temporary workaround, forces the server to always return the timestamp if the value is queried
         if 'value' in keys and 'timestamp' not in keys:
             keys += ['timestamp']
         dictionary = {}
diff --git a/src/soil/object.py b/src/soil/object.py
index ade708a..270af62 100644
--- a/src/soil/object.py
+++ b/src/soil/object.py
@@ -134,13 +134,6 @@ class Object(Element):
         if 'children' in keys:
             everything = self._objects + self._variables + self._parameters + self._functions
             dictionary['children'] = list(map(lambda x: x.serialize(['name', 'uuid']), everything))
-        # if 'update' in keys:
-        #     # TODO implement (update all children)
-        #     pass
-        #     # if self._implementation is not None:
-        #     #     dict['update'] = self._implementation()
-        # else:
-        #     dict['children'] = list(map(lambda x: x.serialize(['value', 'uuid']), self._variables))
         return dictionary
 
     @staticmethod
diff --git a/src/soil/variable.py b/src/soil/variable.py
index 8367966..0279eaa 100644
--- a/src/soil/variable.py
+++ b/src/soil/variable.py
@@ -15,8 +15,8 @@ class Variable(Figure):
     def __init__(self, uuid, name, description, datatype, dimension, range, getter, unit, nonce=None, ontology: str = None):
         Figure.__init__(self, uuid, name, description, datatype, dimension, range, None, getter, ontology)
         self._unit = unit
-        self._covariance = None  # TODO init
-        self._uncertainty = None  # TODO init
+        self._covariance = None
+        self._uncertainty = None
         self._timestamp = None
         self._nonce = nonce
 
@@ -89,7 +89,6 @@ class Variable(Figure):
         if not keys:
             keys = ['uuid', 'name', 'description', 'datatype', 'value', 'dimension', 'range', 'timestamp', 'nonce', 'covariance', 'uncertainty',
                     'unit', 'ontology']
-        # TODO temporary workaround, forces the server to always return the timestamp if the value is queried
         if 'value' in keys and 'timestamp' not in keys:
             keys += ['timestamp']
         dictionary = {}
diff --git a/start.bat b/start.bat
deleted file mode 100644
index 8e8141d..0000000
--- a/start.bat
+++ /dev/null
@@ -1,7 +0,0 @@
-@echo off
-TITLE Distributed Environmental Sensor System (Server)
-call conda activate pudi
-cls
-set PYTHONPATH=%PYTHONPATH%;./src
-echo ##### Start server...
-python main.py 0.0.0.0 8015
diff --git a/test.bat b/test.bat
deleted file mode 100644
index 60949af..0000000
--- a/test.bat
+++ /dev/null
@@ -1,7 +0,0 @@
-@echo off
-start cmd /c "TITLE TestServer& call conda activate udi & python main.py localhost 8015"
-cd ./test
-call conda activate udi
-python test.py
-set /p DUMMY=Hit ENTER to close the test...
-taskkill /IM cmd.exe /FI "WINDOWTITLE  eq TestServer"
\ No newline at end of file
diff --git a/test/__init__.py b/test/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/examples/virtual_lasertracker/__init__.py b/test/server/__init__.py
similarity index 100%
rename from examples/virtual_lasertracker/__init__.py
rename to test/server/__init__.py
diff --git a/test/server/main.py b/test/server/main.py
new file mode 100644
index 0000000..5162fbb
--- /dev/null
+++ b/test/server/main.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+import os
+import sys
+
+import toml as toml
+
+sys.path.insert(0, os.path.abspath(os.path.join('..', '..', 'examples', 'virtual_lasertracker')))
+sys.path.insert(0, os.path.abspath(os.path.join('..', '..')))
+
+from start import start
+from hwc.com_lasertracker import COMLasertracker
+from lt_device import LTDevice
+
+if __name__ == "__main__":
+
+    config = toml.load('server_config.toml')
+    soil_model_file = os.path.join('../..', 'examples', 'virtual_lasertracker', 'Lasertracker.json')
+
+    device = LTDevice()
+    lasertracker = COMLasertracker(device)
+    device.start()
+
+    start(lasertracker, config, soil_model_file)
diff --git a/test/server/start.py b/test/server/start.py
new file mode 100644
index 0000000..b4c01c1
--- /dev/null
+++ b/test/server/start.py
@@ -0,0 +1,133 @@
+# -*- coding: utf-8 -*-
+import asyncio
+import sys
+from concurrent.futures import ThreadPoolExecutor
+from typing import Dict
+
+from wzl.mqtt import MQTTPublisher
+from wzl.utilities import root_logger
+
+from src.http.server import HTTPServer
+from src.soil.component import Component
+from src.soil.event import Event, EventSeverity, EventTrigger
+from src.soil.stream import FixedJob, EventJob, UpdateJob, StreamScheduler
+from device.enums import StateEnum
+from hwc.com_lasertracker import COMLasertracker
+
+sys.setswitchinterval(0.0005)
+
+
+def start(com_lasertracker: COMLasertracker, config: Dict, soil_model_file: str):
+    # server settings
+    address = config['http']['address']
+    port = config['http']['port']
+
+    # mqtt settings
+    mqtt_host = config['mqtt']['host']
+    mqtt_port = config['mqtt']['port']
+    mqtt_vhost = config['mqtt']['vhost']
+    mqtt_username = config['mqtt']['username']
+    mqtt_password = config['mqtt']['password']
+    mqtt_ssl = config['mqtt']['ssl']
+    mqtt_websockets = config['mqtt']['websockets']
+
+    main_logger = root_logger.get(__name__)
+
+    # set up servers
+    executor = ThreadPoolExecutor(max_workers=100)
+    loop = asyncio.get_event_loop()
+    loop.set_default_executor(executor)
+
+    # configure mqtt
+    mqtt = MQTTPublisher(mqtt_username, prefix=mqtt_username)
+    mqtt.connect(mqtt_host, mqtt_username, mqtt_password, vhost=mqtt_vhost, port=mqtt_port, ssl=mqtt_ssl,
+                 websocket=mqtt_websockets)
+
+    # configure messages
+    schedule = []
+    schedule += [FixedJob("COM-Lasertracker/COM-BaseStations/COM-Base/MEA-Position", 30,
+                          com_lasertracker._com_basestations._com_base.get_mea_position)]
+    for child0_uuid in com_lasertracker._com_mobileentities._com_target:
+        schedule += [UpdateJob("COM-Lasertracker/COM-MobileEntities/{}/MEA-Position".format(child0_uuid),
+                               com_lasertracker._com_mobileentities._com_target[child0_uuid].get_mea_position)]
+        schedule += [UpdateJob("COM-Lasertracker/COM-MobileEntities/{}/MEA-Quaternion".format(child0_uuid),
+                               com_lasertracker._com_mobileentities._com_target[child0_uuid].get_mea_quaternion)]
+
+    # configure events
+    schedule += [EventJob("COM-Lasertracker/COM-BaseStations/COM-Base/MEA-Distance", 10,
+                          com_lasertracker._com_basestations._com_base.get_mea_distance,
+                          Event(EventSeverity.WARNING, EventTrigger.LARGER, "double", 50,
+                                "The target is distance is large. Uncertainty might be high."))]
+    schedule += [EventJob("COM-Lasertracker/COM-BaseStations/COM-Base/PAR-State", 10,
+                          com_lasertracker._com_basestations._com_base.get_par_state,
+                          Event(EventSeverity.ERROR, EventTrigger.EQUALS, "enum", StateEnum.ERROR,
+                                "An error occured!"))]
+    for child0_uuid in com_lasertracker._com_mobileentities._com_target:
+        schedule += [EventJob("COM-Lasertracker/COM-MobileEntities/{}/PAR-Locked".format(child0_uuid), 10,
+                              com_lasertracker._com_mobileentities._com_target[child0_uuid].get_par_locked,
+                              Event(EventSeverity.WARNING, EventTrigger.EQUALS, "bool", False,
+                                    "Target is not locked!"))]
+
+    scheduler = StreamScheduler(loop, schedule, [mqtt], start_immediately=True)
+
+    # configure model
+    mapping = {}
+    mapping['COM-Lasertracker'] = {'add': None, 'remove': None}
+    submapping = mapping['COM-Lasertracker']
+    submapping['PAR-State'] = {'getter': com_lasertracker.get_par_state, 'setter': com_lasertracker.set_par_state}
+    submapping['PAR-Manufacturer'] = {'getter': com_lasertracker.get_par_manufacturer, 'setter': None}
+    submapping['PAR-Version'] = {'getter': com_lasertracker.get_par_version, 'setter': None}
+    submapping['PAR-Time'] = {'getter': com_lasertracker.get_par_time, 'setter': com_lasertracker.set_par_time}
+    submapping['FUN-Reset'] = {'method': com_lasertracker.fun_reset, 'signature': {'arguments': {}, 'returns': []}}
+    submapping['FUN-Shutdown'] = {'method': com_lasertracker.fun_shutdown,
+                                  'signature': {'arguments': {}, 'returns': []}}
+    mapping['COM-Lasertracker']['COM-BaseStations'] = {'add': None, 'remove': None}
+    submapping = mapping['COM-Lasertracker']['COM-BaseStations']
+    mapping['COM-Lasertracker']['COM-BaseStations']['COM-Base'] = {'add': None, 'remove': None}
+    submapping = mapping['COM-Lasertracker']['COM-BaseStations']['COM-Base']
+    submapping['MEA-Position'] = com_lasertracker._com_basestations._com_base.get_mea_position
+    submapping['MEA-Quaternion'] = com_lasertracker._com_basestations._com_base.get_mea_quaternion
+    submapping['MEA-Azimuth'] = com_lasertracker._com_basestations._com_base.get_mea_azimuth
+    submapping['MEA-Elevation'] = com_lasertracker._com_basestations._com_base.get_mea_elevation
+    submapping['MEA-Distance'] = com_lasertracker._com_basestations._com_base.get_mea_distance
+    submapping['PAR-State'] = {'getter': com_lasertracker._com_basestations._com_base.get_par_state, 'setter': None}
+    submapping['PAR-Interval'] = {'getter': com_lasertracker._com_basestations._com_base.get_par_interval,
+                                  'setter': com_lasertracker._com_basestations._com_base.set_par_interval}
+    submapping['FUN-Jog'] = {'method': com_lasertracker._com_basestations._com_base.fun_jog, 'signature': {
+        'arguments': {'ARG-Azimuth': 'arg_azimuth', 'ARG-Elevation': 'arg_elevation'}, 'returns': []}}
+    submapping['FUN-PointTo'] = {'method': com_lasertracker._com_basestations._com_base.fun_pointto,
+                                 'signature': {'arguments': {'ARG-Position': 'arg_position'}, 'returns': []}}
+    mapping['COM-Lasertracker']['COM-MobileEntities'] = {'add': com_lasertracker._com_mobileentities.add,
+                                                         'remove': com_lasertracker._com_mobileentities.remove}
+    submapping = mapping['COM-Lasertracker']['COM-MobileEntities']
+    for child0_uuid in com_lasertracker._com_mobileentities._com_target:
+        mapping['COM-Lasertracker']['COM-MobileEntities'][child0_uuid] = {'add': None, 'remove': None}
+        submapping = mapping['COM-Lasertracker']['COM-MobileEntities'][child0_uuid]
+        submapping['MEA-Position'] = com_lasertracker._com_mobileentities._com_target[child0_uuid].get_mea_position
+        submapping['MEA-Quaternion'] = com_lasertracker._com_mobileentities._com_target[child0_uuid].get_mea_quaternion
+        submapping['PAR-State'] = {
+            'getter': com_lasertracker._com_mobileentities._com_target[child0_uuid].get_par_state, 'setter': None}
+        submapping['PAR-Mode'] = {'getter': com_lasertracker._com_mobileentities._com_target[child0_uuid].get_par_mode,
+                                  'setter': None}
+        submapping['PAR-Type'] = {'getter': com_lasertracker._com_mobileentities._com_target[child0_uuid].get_par_type,
+                                  'setter': None}
+        submapping['PAR-Locked'] = {
+            'getter': com_lasertracker._com_mobileentities._com_target[child0_uuid].get_par_locked,
+            'setter': com_lasertracker._com_mobileentities._com_target[child0_uuid].set_par_locked}
+        submapping['FUN-Reset'] = {'method': com_lasertracker._com_mobileentities._com_target[child0_uuid].fun_reset,
+                                   'signature': {'arguments': {}, 'returns': []}}
+        submapping['FUN-Trigger'] = {
+            'method': com_lasertracker._com_mobileentities._com_target[child0_uuid].fun_trigger,
+            'signature': {'arguments': {}, 'returns': []}, 'mqtt_callback': mqtt.publish}
+    model = Component.load(soil_model_file, mapping['COM-Lasertracker'])
+
+    http = HTTPServer(loop, address, port, model)
+
+    # start servers
+    main_logger.info("Starting main asynchronous loop")
+    try:
+        loop.run_forever()
+    except:
+        pass
+    finally:
+        loop.close()
-- 
GitLab