diff --git a/README.md b/README.md
index 7a62c7b904617294c45d2a32877266d5e7936fae..8b04c2eddd9b87e8558d36ac594d2a782bfc6ec5 100644
--- a/README.md
+++ b/README.md
@@ -53,6 +53,22 @@ Setup your server as follows:
 2. If you are not using MQTT or HTTP leave out the respective lines. 
 
 ## Recent changes
+7.0.0
+- Completely reworked the architecture of the library
+  - changed from *framework* to more convenient *library* structure
+
+- major changes 
+  - no JSON-model and mapping required anymore
+  - json-model is no deduced from the class structure
+
+- removed deprecated features
+  - docstring parsing due to its error-prone behaviour
+
+- refactoring
+  - simplified StreamScheduler to a single class for all jobs (instead of dedicated classes for different job types)
+  - renamed class Figure to Variable
+  - jobs are now stored within the Variable the job belongs to
+
 6.0.2 | 5.2.5
 - bug fix
   - fixed parsing of parameters and variables/ measurements of type "time" for higher dimensions 
diff --git a/Todo.md b/Todo.md
new file mode 100644
index 0000000000000000000000000000000000000000..f5f06835601db9aa261115cd578d9c5e8e33fd11
--- /dev/null
+++ b/Todo.md
@@ -0,0 +1,10 @@
+# Todos
+
+- [ ] refactor component
+- [ ] refactor function
+- [ ] figure out how to implement arguments and returns
+- [ ] improve error handling
+- [ ] improve verbosity (i.e. the thrown errors)
+- [ ] apply new structure to example laser tracker
+- [ ] handover sibling parameter if used as dynamic interval for a job
+- [ ] develop a nice solution how to hand over mqtt publish method to a function without passing it down the complete component tree
\ No newline at end of file
diff --git a/build.bat b/build.bat
deleted file mode 100644
index 1b357428ac738cb4b5d780811941e972bab9ab03..0000000000000000000000000000000000000000
--- 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/classes.png b/classes.png
new file mode 100644
index 0000000000000000000000000000000000000000..5ebeb11d1bb4e48441b3d012c6c9cb886c69c470
Binary files /dev/null and b/classes.png differ
diff --git a/environment.yml b/environment.yml
deleted file mode 100644
index 96849da192beff519b92f98efa20101f6454a8f6..0000000000000000000000000000000000000000
--- a/environment.yml
+++ /dev/null
@@ -1,32 +0,0 @@
-name: udi
-channels:
-  - conda-forge
-  - defaults
-dependencies:
-  - aiohttp=3.7.3=py37hcc03f2d_0
-  - async-timeout=3.0.1=py_1000
-  - attrs=20.3.0=pyhd3deb0d_0
-  - ca-certificates=2020.11.8=h5b45459_0
-  - certifi=2020.11.8=py37h03978a9_0
-  - chardet=3.0.4=py37hf50a25e_1008
-  - idna=2.10=pyh9f0ad1d_0
-  - multidict=4.7.5=py37h4ab8f01_2
-  - nest-asyncio=1.4.3=pyhd8ed1ab_0
-  - openssl=1.1.1h=he774522_0
-  - pip=20.2.4=py_0
-  - python=3.7.8=h7840368_2_cpython
-  - python_abi=3.7=1_cp37m
-  - setuptools=49.6.0=py37hf50a25e_2
-  - sqlite=3.33.0=he774522_1
-  - strict-rfc3339=0.7=py_1
-  - typing-extensions=3.7.4.3=0
-  - typing_extensions=3.7.4.3=py_0
-  - vc=14.1=h869be7e_1
-  - vs2015_runtime=14.16.27012=h30e32a0_2
-  - wheel=0.35.1=pyh9f0ad1d_0
-  - wincertstore=0.2=py37hc8dfbb8_1005
-  - yarl=1.6.3=py37hcc03f2d_0
-  - zlib=1.2.11=h62dcd97_1010
-  - pip:
-    - docstring-parser==0.7.3
-    - paho-mqtt==1.5.1
\ No newline at end of file
diff --git a/packages.png b/packages.png
new file mode 100644
index 0000000000000000000000000000000000000000..1279a094f75924331b205846ae375c32b83d8512
Binary files /dev/null and b/packages.png differ
diff --git a/requirements.txt b/requirements.txt
index d504927dc9f35d68b9ff8aa18b28dc626181c025..944862904ad4db43aa0b8517e25d352265507c2c 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,11 +1,5 @@
-aiohttp==3.7.3
-async-timeout==3.0.1
-attrs @ file:///home/conda/feedstock_root/build_artifacts/attrs_1605083924122/work
-certifi==2020.11.8
-chardet @ file:///D:/bld/chardet_1602255574834/work
-docstring-parser==0.7.3
-nest-asyncio==1.4.3
+fastapi==0.75.0
+toml==0.10.2
+fastapi-utils==0.2.1
+wzl-utilites==1.2.2
 strict-rfc3339==0.7
-wzl-mqtt==2.3.0
-wzl-utilities==1.2.2
-yarl @ file:///D:/bld/yarl_1605429655746/work
diff --git a/setup.py b/setup.py
deleted file mode 100644
index 25fa7e0c1e51fc4a49df2a8fe52d96e5b373b617..0000000000000000000000000000000000000000
--- a/setup.py
+++ /dev/null
@@ -1,13 +0,0 @@
-from setuptools import setup, find_packages
-
-setup(name='wzl-udi',
-      version='6.0.2',
-      url='',
-      author='Matthias Bodenbenner',
-      author_email='m.bodenbenner@wzl.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'],
-      zip_safe=False)
diff --git a/src/http/__init__.py b/src/http/__init__.py
index 3f49305b8fd0a9fba599bbcade8c137fe32d6e94..8d1bf30a5fc420d2eccb5734dfe15bcd8bd01b01 100644
--- a/src/http/__init__.py
+++ b/src/http/__init__.py
@@ -1 +1 @@
-from .server import HTTPServer
\ No newline at end of file
+from .server_old import HTTPServer
\ No newline at end of file
diff --git a/src/http/app.py b/src/http/app.py
new file mode 100644
index 0000000000000000000000000000000000000000..9d57cc76812678ca67e4563235028b6566f34d38
--- /dev/null
+++ b/src/http/app.py
@@ -0,0 +1,33 @@
+import io
+import json
+import os
+import traceback
+
+import toml
+from fastapi import FastAPI, Request, HTTPException
+from fastapi import File
+from fastapi.middleware.cors import CORSMiddleware
+from fastapi_utils.inferring_router import InferringRouter
+from wzl.utilities import root_logger
+
+from http.server import ROUTER
+
+logger = root_logger.get(__name__)
+
+CONFIGURATION = toml.load(open('../config.toml'))
+
+app = FastAPI(root_path=CONFIGURATION['env']['root_path'])
+
+origins = [
+    "*",
+]
+
+app.add_middleware(
+    CORSMiddleware,
+    allow_origins=origins,
+    allow_credentials=True,
+    allow_methods=["*"],
+    allow_headers=["*"],
+)
+
+app.include_router(ROUTER)
diff --git a/src/http/server.py b/src/http/server.py
index 5468b66d3acd49f274073d0a95f260d8d1cae8e2..e4230cf9f49022d1db2f79ac0beb57da1ca18b5f 100644
--- a/src/http/server.py
+++ b/src/http/server.py
@@ -1,46 +1,51 @@
 # -*- coding: utf-8 -*-
 import functools
+from typing import List
+
+from fastapi import Depends, FastAPI
+from fastapi_utils.cbv import cbv
+from fastapi_utils.inferring_router import InferringRouter
 
-from aiohttp import web
-from aiohttp.web import middleware
 from wzl.utilities import root_logger
 
 from .error import ServerException
 from ..soil.error import InvokationException, ReadOnlyException, ChildNotFoundException
 from ..utils.error import DeviceException, UserException
 from ..soil.function import Function
-from ..soil.figure import Figure
+from ..soil.variable import Variable
 from ..soil.object import Object
 from ..soil.component import Component
 from ..soil.parameter import Parameter
-from ..utils.constants import BASE_UUID_PATTERN, HTTP_GET, HTTP_OPTIONS
+from ..utils.const import BASE_UUID_PATTERN, HTTP_GET, HTTP_OPTIONS
 
 logger = root_logger.get(__name__)
 
-
-@middleware
-async def cors(request, handler):
-    logger.info("CORS Middleware handles request from {}".format(request.url))
-    logger.debug('Request Headers: {}'.format(request.headers))
-    response = web.Response()
-    # check if the request is a preflight request
-    if 'Access-Control-Request-Method' in request.headers and request.headers['Access-Control-Request-Method'] in ["POST", "PATCH"]:
-        response.headers.update({'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH',
-                                 'Access-Control-Allow-Headers': request.headers['Access-Control-Request-Headers']})
-        response.headers.update({'Access-Control-Allow-Origin': request.headers['Origin']})
-        logger.debug('Preflight Response Headers :{}'.format(response.headers))
-        return response
-    try:
-        response = await handler(request)
-    except Exception as e:
-        response = web.json_response({"description": str(e)}, status=500)
-        logger.error(["[CORS] {} at {}".format(str(e), request.url)])
-    finally:
-        response.headers.update({'Access-Control-Allow-Origin': request.headers.get('Origin', "*")})
-        logger.debug('Response Headers :{}'.format(response.headers))
-        return response
-
-
+ROUTER = InferringRouter()
+
+
+# @middleware
+# async def cors(request, handler):
+#     logger.info("CORS Middleware handles request from {}".format(request.url))
+#     logger.debug('Request Headers: {}'.format(request.headers))
+#     response = web.Response()
+#     # check if the request is a preflight request
+#     if 'Access-Control-Request-Method' in request.headers and request.headers['Access-Control-Request-Method'] in ["POST", "PATCH"]:
+#         response.headers.update({'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH',
+#                                  'Access-Control-Allow-Headers': request.headers['Access-Control-Request-Headers']})
+#         response.headers.update({'Access-Control-Allow-Origin': request.headers['Origin']})
+#         logger.debug('Preflight Response Headers :{}'.format(response.headers))
+#         return response
+#     try:
+#         response = await handler(request)
+#     except Exception as e:
+#         response = web.json_response({"description": str(e)}, status=500)
+#         logger.error(["[CORS] {} at {}".format(str(e), request.url)])
+#     finally:
+#         response.headers.update({'Access-Control-Allow-Origin': request.headers.get('Origin', "*")})
+#         logger.debug('Response Headers :{}'.format(response.headers))
+#         return response
+
+@cbv(ROUTER)
 class HTTPServer(object):
 
     def __init__(self, loop, host, port, model):
@@ -49,37 +54,34 @@ class HTTPServer(object):
         self.port = port
         self.root = model
 
-        self.app = web.Application(loop=self.loop, middlewares=[cors])
-
         # define two routes for each request to make the 'objects' part optional
-        self.app.router.add_get(r'/objects{uuids:(/' + BASE_UUID_PATTERN + r')*/?}', self.get)
-        self.app.router.add_get(r'/{uuids:(' + BASE_UUID_PATTERN + r'($|/))*}', self.get)
-        self.app.router.add_post(r'/objects{uuids:(/' + BASE_UUID_PATTERN + r')*/?}', self.post)
-        self.app.router.add_post(r'/{uuids:(' + BASE_UUID_PATTERN + r'($|/))*}', self.post)
-        self.app.router.add_delete(r'/objects{uuids:(/' + BASE_UUID_PATTERN + r')*/?}', self.delete)
-        self.app.router.add_delete(r'/{uuids:(' + BASE_UUID_PATTERN + r'($|/))*}', self.delete)
-        self.app.router.add_route('OPTIONS', r'/objects{uuids:(/' + BASE_UUID_PATTERN + r')*/?}', self.options)
-        self.app.router.add_route('OPTIONS', r'/{uuids:(' + BASE_UUID_PATTERN + r'($|/))*}', self.options)
-        self.app.router.add_put(r'/objects{uuids:(/' + BASE_UUID_PATTERN + r')*/?}', self.put)
-        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)
+        self.app.ROUTER.add_get(r'/objects{uuids:(/' + BASE_UUID_PATTERN + r')*/?}', self.get)
+        self.app.ROUTER.add_get(r'/{uuids:(' + BASE_UUID_PATTERN + r'($|/))*}', self.get)
+        self.app.ROUTER.add_post(r'/objects{uuids:(/' + BASE_UUID_PATTERN + r')*/?}', self.post)
+        self.app.ROUTER.add_post(r'/{uuids:(' + BASE_UUID_PATTERN + r'($|/))*}', self.post)
+        self.app.ROUTER.add_delete(r'/objects{uuids:(/' + BASE_UUID_PATTERN + r')*/?}', self.delete)
+        self.app.ROUTER.add_delete(r'/{uuids:(' + BASE_UUID_PATTERN + r'($|/))*}', self.delete)
+        self.app.ROUTER.add_route('OPTIONS', r'/objects{uuids:(/' + BASE_UUID_PATTERN + r')*/?}', self.options)
+        self.app.ROUTER.add_route('OPTIONS', r'/{uuids:(' + BASE_UUID_PATTERN + r'($|/))*}', self.options)
+        self.app.ROUTER.add_put(r'/objects{uuids:(/' + BASE_UUID_PATTERN + r')*/?}', self.put)
+        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)
         logger.info('HTTP-Server serving on {}:{}'.format(host, port))
 
     @staticmethod
-    def parse_uuids(request):
-        uuids = request.match_info.get('uuids', 'uuids')
+    def parse_uuids(uuids: str) -> List[str]:
         uuid_list = uuids.split('/')
         while '' in uuid_list:
             uuid_list.remove('')
         return uuid_list
 
-    async def get(self, request):
+    @ROUTER.get(r"/{uuids:(/' + BASE_UUID_PATTERN + r')*/?}")
+    async def get(self, request, uuids: str):
         logger.info("GET Request from {}".format(request.url))
         logger.debug('Request: {}'.format(request))
         logger.debug('Query Parameters: {}'.format(request.query_string))
-        item = self.root[HTTPServer.parse_uuids(request)]
+        item = self.root.get_element(HTTPServer.parse_uuids(uuids))
         if request.query_string == "":
             keys = []
         else:
@@ -94,6 +96,7 @@ class HTTPServer(object):
             logger.error('Response: {}'.format(response))
         return web.json_response(response, status=status)
 
+    @ROUTER.post("/")
     async def post(self, request):
         logger.info("POST Request from {}".format(request.url))
         logger.debug('Request: {}'.format(request))
@@ -117,6 +120,7 @@ class HTTPServer(object):
 
         return web.json_response(response, status=status)
 
+    @ROUTER.delete("/")
     async def delete(self, request):
         logger.info("PUT Request from {}".format(request.url))
         logger.debug('Request: {}'.format(request))
@@ -127,7 +131,8 @@ class HTTPServer(object):
         if not isinstance(item, Object) and not isinstance(item, Component):
             return web.json_response({}, status=405)
         try:
-            response = await self.loop.run_in_executor(None, functools.partial(item.remove, uuids[-1], *data['args'], **data['kwargs']))
+            response = await self.loop.run_in_executor(None, functools.partial(item.remove, uuids[-1], *data['args'],
+                                                                               **data['kwargs']))
             status = 200
             logger.info('Response: {}'.format(response))
         except ChildNotFoundException as e:
@@ -140,12 +145,13 @@ class HTTPServer(object):
             logger.error('Response: {}'.format(response))
         return web.json_response(response, status=status)
 
+    @ROUTER.options("/")
     async def options(self, request):
         logger.info("HEAD Request from {}".format(request.url))
         logger.debug('Request: {}'.format(request))
         logger.debug('Query Parameters: {}'.format(request.query_string))
         item = self.root[HTTPServer.parse_uuids(request)]
-        if not isinstance(item, Figure):
+        if not isinstance(item, Variable):
             return web.json_response({}, status=405)
         if request.query_string == "":
             keys = []
@@ -155,6 +161,7 @@ class HTTPServer(object):
         logger.info('Response: {}'.format(response))
         return web.json_response(response)
 
+    @ROUTER.patch("/")
     async def patch(self, request):
         logger.info("PATCH Request from {}".format(request.url))
         logger.debug('Request: {}'.format(request))
@@ -180,6 +187,7 @@ class HTTPServer(object):
             logger.error('Response: {}'.format(response))
         return web.json_response(response, status=status)
 
+    @ROUTER.put("/")
     async def put(self, request):
         logger.info("PUT Request from {}".format(request.url))
         logger.debug('Request: {}'.format(request))
@@ -191,7 +199,8 @@ class HTTPServer(object):
             return web.json_response({}, status=405)
         try:
             response = await self.loop.run_in_executor(None,
-                                                       functools.partial(item.add, uuids[-1], data['class_name'], data['json_file'], *data['args'],
+                                                       functools.partial(item.add, uuids[-1], data['class_name'],
+                                                                         data['json_file'], *data['args'],
                                                                          **data['kwargs']))
             status = 200
             logger.info('Response: {}'.format(response))
diff --git a/src/http/server_old.py b/src/http/server_old.py
new file mode 100644
index 0000000000000000000000000000000000000000..ea6ec99b6c0c1fc8875931dee381da5f9198db2a
--- /dev/null
+++ b/src/http/server_old.py
@@ -0,0 +1,202 @@
+# -*- coding: utf-8 -*-
+import functools
+
+from aiohttp import web
+from aiohttp.web import middleware
+from wzl.utilities import root_logger
+
+from .error import ServerException
+from ..soil.error import InvokationException, ReadOnlyException, ChildNotFoundException
+from ..utils.error import DeviceException, UserException
+from ..soil.function import Function
+from ..soil.variable import Variable
+from ..soil.object import Object
+from ..soil.component import Component
+from ..soil.parameter import Parameter
+from ..utils.const import BASE_UUID_PATTERN, HTTP_GET, HTTP_OPTIONS
+
+logger = root_logger.get(__name__)
+
+
+@middleware
+async def cors(request, handler):
+    logger.info("CORS Middleware handles request from {}".format(request.url))
+    logger.debug('Request Headers: {}'.format(request.headers))
+    response = web.Response()
+    # check if the request is a preflight request
+    if 'Access-Control-Request-Method' in request.headers and request.headers['Access-Control-Request-Method'] in ["POST", "PATCH"]:
+        response.headers.update({'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH',
+                                 'Access-Control-Allow-Headers': request.headers['Access-Control-Request-Headers']})
+        response.headers.update({'Access-Control-Allow-Origin': request.headers['Origin']})
+        logger.debug('Preflight Response Headers :{}'.format(response.headers))
+        return response
+    try:
+        response = await handler(request)
+    except Exception as e:
+        response = web.json_response({"description": str(e)}, status=500)
+        logger.error(["[CORS] {} at {}".format(str(e), request.url)])
+    finally:
+        response.headers.update({'Access-Control-Allow-Origin': request.headers.get('Origin', "*")})
+        logger.debug('Response Headers :{}'.format(response.headers))
+        return response
+
+
+class HTTPServer(object):
+
+    def __init__(self, loop, host, port, model):
+        self.loop = loop
+        self.host = host
+        self.port = port
+        self.root = model
+
+        self.app = web.Application(loop=self.loop, middlewares=[cors])
+
+        # define two routes for each request to make the 'objects' part optional
+        self.app.ROUTER.add_get(r'/objects{uuids:(/' + BASE_UUID_PATTERN + r')*/?}', self.get)
+        self.app.ROUTER.add_get(r'/{uuids:(' + BASE_UUID_PATTERN + r'($|/))*}', self.get)
+        self.app.ROUTER.add_post(r'/objects{uuids:(/' + BASE_UUID_PATTERN + r')*/?}', self.post)
+        self.app.ROUTER.add_post(r'/{uuids:(' + BASE_UUID_PATTERN + r'($|/))*}', self.post)
+        self.app.ROUTER.add_delete(r'/objects{uuids:(/' + BASE_UUID_PATTERN + r')*/?}', self.delete)
+        self.app.ROUTER.add_delete(r'/{uuids:(' + BASE_UUID_PATTERN + r'($|/))*}', self.delete)
+        self.app.ROUTER.add_route('OPTIONS', r'/objects{uuids:(/' + BASE_UUID_PATTERN + r')*/?}', self.options)
+        self.app.ROUTER.add_route('OPTIONS', r'/{uuids:(' + BASE_UUID_PATTERN + r'($|/))*}', self.options)
+        self.app.ROUTER.add_put(r'/objects{uuids:(/' + BASE_UUID_PATTERN + r')*/?}', self.put)
+        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)
+        logger.info('HTTP-Server serving on {}:{}'.format(host, port))
+
+    @staticmethod
+    def parse_uuids(request):
+        uuids = request.match_info.get('uuids', 'uuids')
+        uuid_list = uuids.split('/')
+        while '' in uuid_list:
+            uuid_list.remove('')
+        return uuid_list
+
+    async def get(self, request):
+        logger.info("GET Request from {}".format(request.url))
+        logger.debug('Request: {}'.format(request))
+        logger.debug('Query Parameters: {}'.format(request.query_string))
+        item = self.root[HTTPServer.parse_uuids(request)]
+        if request.query_string == "":
+            keys = []
+        else:
+            keys = [x.split('=')[0] for x in request.query_string.split('&')]
+        try:
+            response = item.serialize(keys, HTTP_GET)
+            status = 200
+            logger.info('Response: {}'.format(response))
+        except (DeviceException, ServerException, UserException) as e:
+            response = {'error': str(e)}
+            status = 500
+            logger.error('Response: {}'.format(response))
+        return web.json_response(response, status=status)
+
+    async def post(self, request):
+        logger.info("POST Request from {}".format(request.url))
+        logger.debug('Request: {}'.format(request))
+        data = await request.json()
+        logger.debug('Body: {}'.format(data))
+        uuids = HTTPServer.parse_uuids(request)
+        item = self.root[uuids]
+
+        if isinstance(item, Function):
+            try:
+                response = await item.invoke(data["arguments"], topic='/'.join(uuids))
+                status = 200
+                logger.info('Response: {}'.format(response))
+            except (DeviceException, ServerException, UserException) as e:
+                response = {'error': str(e)}
+                status = 500
+                logger.error('Response: {}'.format(response))
+        else:
+            response, status = {}, 405
+            logger.error('Response: {}'.format(response))
+
+        return web.json_response(response, status=status)
+
+    async def delete(self, request):
+        logger.info("PUT Request from {}".format(request.url))
+        logger.debug('Request: {}'.format(request))
+        data = await request.json()
+        uuids = HTTPServer.parse_uuids(request)
+        item = self.root[uuids[:-1]]
+
+        if not isinstance(item, Object) and not isinstance(item, Component):
+            return web.json_response({}, status=405)
+        try:
+            response = await self.loop.run_in_executor(None, functools.partial(item.remove, uuids[-1], *data['args'], **data['kwargs']))
+            status = 200
+            logger.info('Response: {}'.format(response))
+        except ChildNotFoundException as e:
+            response = {'error': str(e)}
+            status = 404
+            logger.error('Response: {}'.format(response))
+        except (DeviceException, ServerException, UserException) as e:
+            response = {'error': str(e)}
+            status = 500
+            logger.error('Response: {}'.format(response))
+        return web.json_response(response, status=status)
+
+    async def options(self, request):
+        logger.info("HEAD Request from {}".format(request.url))
+        logger.debug('Request: {}'.format(request))
+        logger.debug('Query Parameters: {}'.format(request.query_string))
+        item = self.root[HTTPServer.parse_uuids(request)]
+        if not isinstance(item, Variable):
+            return web.json_response({}, status=405)
+        if request.query_string == "":
+            keys = []
+        else:
+            keys = [x.split('=')[0] for x in request.query_string.split('&')]
+        response = item.serialize(keys, HTTP_OPTIONS)
+        logger.info('Response: {}'.format(response))
+        return web.json_response(response)
+
+    async def patch(self, request):
+        logger.info("PATCH Request from {}".format(request.url))
+        logger.debug('Request: {}'.format(request))
+        data = await request.json()
+        logger.debug('Body: {}'.format(data))
+        item = self.root[HTTPServer.parse_uuids(request)]
+
+        if isinstance(item, Parameter):
+            try:
+                response = await self.loop.run_in_executor(None, item.set, data["value"])
+                status = 200
+                logger.info('Response: {}'.format(response))
+            except ReadOnlyException as e:
+                response = {'error': str(e)}
+                status = 403
+                logger.error('Response: {}'.format(response))
+            except InvokationException as e:
+                response = {'error': str(e)}
+                status = 500
+                logger.error('Response: {}'.format(response))
+        else:
+            response, status = {}, 405
+            logger.error('Response: {}'.format(response))
+        return web.json_response(response, status=status)
+
+    async def put(self, request):
+        logger.info("PUT Request from {}".format(request.url))
+        logger.debug('Request: {}'.format(request))
+        data = await request.json()
+        logger.debug('Body: {}'.format(data))
+        uuids = HTTPServer.parse_uuids(request)
+        item = self.root[uuids[:-1]]
+        if not isinstance(item, Object) and not isinstance(item, Component):
+            return web.json_response({}, status=405)
+        try:
+            response = await self.loop.run_in_executor(None,
+                                                       functools.partial(item.add, uuids[-1], data['class_name'], data['json_file'], *data['args'],
+                                                                         **data['kwargs']))
+            status = 200
+            logger.info('Response: {}'.format(response))
+        except (DeviceException, ServerException, UserException) as e:
+            response = {'error': str(e)}
+            status = 500
+            logger.error('Response: {}'.format(response))
+        return web.json_response(response, status=status)
diff --git a/src/soil/component.py b/src/soil/component.py
index dc6418df7015f34e3006e32fe3cda7256e030a33..6b910a6dd9e2df36a08c37736467321e320c4336 100644
--- a/src/soil/component.py
+++ b/src/soil/component.py
@@ -13,13 +13,14 @@ import sys
 from typing import List, Any, Union, Dict
 from wzl.utilities import root_logger
 
-from . import docstring_parser
+from .device import Device
 from .element import Element
-from .error import ChildNotFoundException
+from .error import ElementNotExistException
 from .function import Function
 from .measurement import Measurement
 from .parameter import Parameter
-from ..utils.constants import HTTP_GET
+from .stream import Job
+from ..utils.const import HTTP_GET
 from ..utils.error import SerialisationException, DeviceException, UserException
 
 logger = root_logger.get(__name__)
@@ -27,8 +28,9 @@ logger = root_logger.get(__name__)
 
 class Component(Element):
 
-    def __init__(self, uuid: str, name: str, description: str, functions: List[Function], measurements: List[Measurement],
-                 parameters: List[Parameter], components: List['Component'], mapping: Dict, ontology: str = None):
+    def __init__(self, uuid: str, name: str, description: str, functions: List[Function] = None,
+                 measurements: List[Measurement] = None, parameters: List[Parameter] = None,
+                 components: List['Component'] = None, ontology: str = None, device: Device = None):
         """
 
         Args:
@@ -40,21 +42,6 @@ class Component(Element):
             measurements: List of all children measurements.
             parameters: List of all children parameters.
             components: List of all children components. Might contain dynamic-components.
-            mapping: Dictionary containing a mapping of the underlying device implementation to the HTTP-endpoints.
-                The mapping of a component looks as follows :
-
-                {
-                    'getter': com_implementation.get,
-                    'setter': com_implementation.set,
-                    'MEA-Temperature': com_implementation.get_mea_temperature,
-                    'PAR-State': {...},
-                    'FUN-Reset: {...},
-                    'COM-Part': {...},
-                }
-
-                If the component does not have dynamic children components, 'getter' and 'setter' are set to None.
-                For all children there is a key-value pair where the UUID of the child is the key and the mapping of the child is the value.
-                For the structure of the childrens' mappings please refer to the respective documentation.
             ontology: Optional field containing the reference to a semantic definition of the components name or purpose.
 
         Raises:
@@ -63,36 +50,93 @@ class Component(Element):
             InvalidModelException: One of the lists containing the components' children is not a list or contains elements which are not of the correct type.
             InvalidMappingException: If something is wrong with the provided mapping.
         """
-        Element.__init__(self, uuid, name, description, ontology)
+        Element.__init__(self, uuid, name, description, ontology, device)
         if uuid[:3] != 'COM':
-            raise Exception('{}: The UUID must start with COM!'.format(uuid))
-        if not isinstance(functions, list):
-            raise Exception('{}: Given functions are not a list!'.format(uuid))
-        for f in functions:
+            raise Exception(f'{uuid}: The UUID of component must start with COM!')
+
+        self._components = [] if components is None else components
+        if not isinstance(self._components, list):
+            raise Exception(f'{uuid}: Given components are not a list!')
+        for c in self._components:
+            if not isinstance(c, Component):
+                raise Exception(f'{uuid}: Given component is not of type Components!')
+
+        self._functions = [] if functions is None else functions
+        if not isinstance(self._functions, list):
+            raise Exception(f'{uuid}: Given functions are not a list!')
+        for f in self._functions:
             if not isinstance(f, Function):
-                raise Exception('{}: Given function is not of type Function!'.format(uuid))
-        if not isinstance(measurements, list):
-            raise Exception('{}: Given measurements are not a list!'.format(uuid))
-        for v in measurements:
-            if not isinstance(v, Measurement):
-                raise Exception('{}: Given measurement is not of type Variables!'.format(uuid))
-        if not isinstance(parameters, list):
-            raise Exception('{}: Given measurements are not a list!'.format(uuid))
-        for p in parameters:
+                raise Exception(f'{uuid}: Given function is not of type Function!')
+
+        self._measurements = [] if measurements is None else measurements
+        if not isinstance(self._measurements, list):
+            raise Exception(f'{uuid}: Given measurements are not a list!')
+        for m in self._measurements:
+            if not isinstance(m, Measurement):
+                raise Exception(f'{uuid}: Given measurement is not of type Variables!')
+
+        self._parameters = [] if parameters is None else parameters
+        if not isinstance(self._parameters, list):
+            raise Exception(f'{uuid}: Given measurements are not a list!')
+        for p in self._parameters:
             if not isinstance(p, Parameter):
-                raise Exception('{}: Given measurement is not of type Variables!'.format(uuid))
-        if not isinstance(components, list):
-            raise Exception('{}: Given components are not a list!'.format(uuid))
-        for o in components:
-            if not isinstance(o, Component):
-                raise Exception('{}: Given component is not of type Components!'.format(uuid))
-
-        self._functions = functions
-        self._measurements = measurements
-        self._components = components
-        self._parameters = parameters
-        self._implementation_add = implementation['add'] if 'add' in implementation else None
-        self._implementation_remove = implementation['remove'] if 'remove' in implementation else None
+                raise Exception(f'{uuid}: Given measurement is not of type Variables!')
+
+    @property
+    def components(self) -> List['Component']:
+        return self._components
+
+    @property
+    def functions(self) -> List[Function]:
+        return self._functions
+
+    @property
+    def measurements(self) -> List[Measurement]:
+        return self._measurements
+
+    @property
+    def parameters(self) -> List[Parameter]:
+        return self._parameters
+
+    @property
+    def children(self) -> List[Element]:
+        return self.components + self.functions + self.measurements + self.parameters
+
+    @property
+    def jobs(self) -> List[Job]:
+        jobs = []
+        for child in self._measurements + self._parameters:
+            jobs += child.jobs
+        return jobs
+
+    def get_element(self, fqid: Union[str, List[str]]) -> Element:
+        """Goes down the component and tree and searches for the element with the given fqid.
+
+        If the fqid contains only one entry (i.e. a single uuid), it must be identical to the uuid of the component, the method is invoked of.
+
+        Args:
+            fqid: Unique identifier.
+
+        Returns:
+            The element having the specified fqid.
+        """
+        if not fqid:
+            raise ValueError(f'The fqid must not be empty!')
+
+        if isinstance(fqid, str):
+            fqid = fqid.split('/')
+
+        if fqid[0] == self.uuid:
+            if len(fqid) == 1:
+                return self
+            else:
+                for child in self.children:
+                    try:
+                        return child.get_element(fqid[1:])
+                    except ElementNotExistException as e:
+                        continue
+
+        raise ElementNotExistException(f'An element with the uuid {"/".join(fqid)} does not exist.')
 
     def __getitem__(self, item: Union[str, List[str]], method: int = HTTP_GET) -> Any:
         """Returns the value of the specified item.
@@ -142,7 +186,8 @@ class Component(Element):
                         return o
                     else:
                         return child.__getitem__(item[1:], method)
-            raise ChildNotFoundException(f'{self.uuid}: Given uuid {item} is not the id of a child of the current component!')
+            raise ChildNotFoundException(
+                f'{self.uuid}: Given uuid {item} is not the id of a child of the current component!')
         return super().__getitem__(item, method)
 
     def __setitem__(self, key: str, value: Any):
@@ -228,9 +273,11 @@ class Component(Element):
                 if 'description' in function:
                     component_list[idx]['description'] = function['description']
                 if 'arguments' in function:
-                    component_list[idx]['arguments'] = merge_measurements(function['arguments'], component_list[idx]['arguments'])
+                    component_list[idx]['arguments'] = merge_measurements(function['arguments'],
+                                                                          component_list[idx]['arguments'])
                 if 'returns' in function:
-                    component_list[idx]['returns'] = merge_measurements(function['returns'], component_list[idx]['returns'])
+                    component_list[idx]['returns'] = merge_measurements(function['returns'],
+                                                                        component_list[idx]['returns'])
             return component_list
 
         # merge components, i.e. overwrite fields of 'static' children dictionary with the 'dynamic' fields of the parents dictionary
@@ -241,7 +288,8 @@ class Component(Element):
         if 'description' in parent_dict:
             component_dict['description'] = parent_dict['description']
         if 'measurements' in parent_dict:
-            component_dict['measurements'] = merge_measurements(parent_dict['measurements'], component_dict['measurements'])
+            component_dict['measurements'] = merge_measurements(parent_dict['measurements'],
+                                                                component_dict['measurements'])
         if 'parameters' in parent_dict:
             component_dict['paramters'] = merge_measurements(parent_dict['parameters'], component_dict['parameters'])
         if 'functions' in parent_dict:
@@ -252,7 +300,8 @@ class Component(Element):
                 if len(index) != 1:
                     raise Exception('Mismatching UUID: {}.'.format(obj['uuid']))
                 index = index[0]
-                component_dict['components'][index] = Component.merge_dictionaries(obj, component_dict['components'][index])
+                component_dict['components'][index] = Component.merge_dictionaries(obj,
+                                                                                   component_dict['components'][index])
         return component_dict
 
     @staticmethod
@@ -262,26 +311,34 @@ class Component(Element):
         uuid = dictionary['uuid']
         if uuid[:3] != 'COM':
             raise SerialisationException(
-                'The component can not be deserialized. The UUID must start with COM, but actually starts with {}!'.format(uuid[:3]))
+                'The component can not be deserialized. The UUID must start with COM, but actually starts with {}!'.format(
+                    uuid[:3]))
         if 'file' in dictionary:
             try:
                 with open(os.path.normpath(os.path.join(filepath, dictionary['file']))) as file:
                     component_dict = json.load(file)
                 dictionary = Component.merge_dictionaries(dictionary, component_dict)
             except Exception as e:
-                raise SerialisationException('{}: The component can not be deserialized. Provided JSON-file can not be parsed! {}'.format(uuid, e))
+                raise SerialisationException(
+                    '{}: The component can not be deserialized. Provided JSON-file can not be parsed! {}'.format(uuid,
+                                                                                                                 e))
         if 'name' not in dictionary:
             raise SerialisationException('{}: The component can not be deserialized. Name is missing!'.format(uuid))
         if 'description' not in dictionary:
-            raise SerialisationException('{}: The component can not be deserialized. Description is missing!'.format(uuid))
+            raise SerialisationException(
+                '{}: The component can not be deserialized. Description is missing!'.format(uuid))
         if 'measurements' not in dictionary:
-            raise SerialisationException('{}: The component can not be deserialized. List of measurements is missing!'.format(uuid))
+            raise SerialisationException(
+                '{}: The component can not be deserialized. List of measurements is missing!'.format(uuid))
         if 'parameters' not in dictionary:
-            raise SerialisationException('{}: The component can not be deserialized. List of parameters is missing!'.format(uuid))
+            raise SerialisationException(
+                '{}: The component can not be deserialized. List of parameters is missing!'.format(uuid))
         if 'functions' not in dictionary:
-            raise SerialisationException('{}: The component can not be deserialized. List of functions is missing!'.format(uuid))
+            raise SerialisationException(
+                '{}: The component can not be deserialized. List of functions is missing!'.format(uuid))
         if 'components' not in dictionary:
-            raise SerialisationException('{}: The component can not be deserialized. List of components is missing!'.format(uuid))
+            raise SerialisationException(
+                '{}: The component can not be deserialized. List of components is missing!'.format(uuid))
 
         try:
             measurements = []
@@ -292,7 +349,8 @@ class Component(Element):
                 else:
                     measurements += [Measurement.deserialize(var)]
         except Exception as e:
-            raise SerialisationException('{}: A measurement of the component can not be deserialized. {}'.format(uuid, e))
+            raise SerialisationException(
+                '{}: A measurement of the component can not be deserialized. {}'.format(uuid, e))
         try:
             parameters = []
             for par in dictionary['parameters']:
@@ -322,10 +380,12 @@ class Component(Element):
                 else:
                     components += [Component.deserialize(obj, filepath=filepath)]
         except Exception as e:
-            raise SerialisationException('{}: An component of the component can not be deserialized. {}'.format(uuid, e))
+            raise SerialisationException(
+                '{}: An component of the component can not be deserialized. {}'.format(uuid, e))
         try:
             ontology = dictionary['ontology'] if 'ontology' in dictionary else None
-            return Component(dictionary['uuid'], dictionary['name'], dictionary['description'], functions, measurements, parameters, components, mapping,
+            return Component(dictionary['uuid'], dictionary['name'], dictionary['description'], functions, measurements,
+                             parameters, components, mapping,
                              ontology)
         except Exception as e:
             raise SerialisationException('{}: The component can not be deserialized. {}'.format(uuid, e))
diff --git a/src/soil/datatype.py b/src/soil/datatype.py
new file mode 100644
index 0000000000000000000000000000000000000000..98f6422a0602120f23d7d77a0e7bd7184b381b49
--- /dev/null
+++ b/src/soil/datatype.py
@@ -0,0 +1,25 @@
+import enum
+
+
+class Datatype(enum.Enum):
+    BOOL = 0
+    INT = 1
+    FLOAT = 2
+    STRING = 3
+    ENUM = 4
+    TIME = 5
+
+    def __str__(self):
+        return Datatype.all()[self.value]
+
+    @staticmethod
+    def all():
+        return ['bool', 'int', 'float', 'string', 'enum', 'time']
+
+    @staticmethod
+    def from_string(name):
+        match = [i for i, x in enumerate(['bool', 'int', 'float', 'string', 'enum', 'time']) if x == name]
+        if len(match) == 1:
+            return Datatype(match[0])
+        else:
+            raise Exception('There is no datatype with this name!')
diff --git a/src/soil/device.py b/src/soil/device.py
new file mode 100644
index 0000000000000000000000000000000000000000..f47c3bd7052c2f922e6efb565bf012d5cf20d27c
--- /dev/null
+++ b/src/soil/device.py
@@ -0,0 +1,9 @@
+class Device(object):
+
+    def __init__(self):
+        # TODO implement
+        pass
+
+    def __del__(self):
+        # TODO implement
+        pass
\ No newline at end of file
diff --git a/src/soil/docstring_parser.py b/src/soil/docstring_parser.py
deleted file mode 100644
index b776036427483789379559e2a5c3880e357145d3..0000000000000000000000000000000000000000
--- a/src/soil/docstring_parser.py
+++ /dev/null
@@ -1,145 +0,0 @@
-from deprecated import deprecated
-import docstring_parser
-import types
-from typing import Any, Callable
-from wzl.utilities import root_logger
-
-from .stream import ConfigurableJob, FixedJob
-from ..utils.error import SerialisationException
-
-logger = root_logger.get(__name__)
-
-
-@deprecated(version='6.0.0', reason='Building service model from docstrings is too error-prone.')
-def parse_children(parse_docstrings: Callable, parent_url: str, implementation: Any):
-    export_dict = {}
-    children_attribute_list, uuid_attribute_list = [], []
-
-    # parse docstring parameters
-    doc = docstring_parser.parse(implementation.__doc__)
-    try:
-        uuid = doc.short_description.split('.')[0]
-        if uuid[:3] != 'OBJ':
-            raise SerialisationException('Short description of class {} misses UUID of the Object.'.format(implementation.__class__.__name__))
-    except:
-        raise SerialisationException('Short description of the class {} is missing.'.format(implementation.__class__.__name__))
-
-    if len(doc.params) > 0:
-        for element in doc.params:
-            if isinstance(element, docstring_parser.common.DocstringParam) and element.args[1] == 'uuids':
-                uuid_attribute_list = element.description.replace('\t', '').replace(' ', '').split(',')
-            elif isinstance(element, docstring_parser.common.DocstringParam) and element.args[1] == 'children':
-                children_attribute_list = element.description.replace('\t', '').replace(' ', '').split(',')
-
-    # check if the uuid and children list parameters are matching
-    if len(uuid_attribute_list) != len(children_attribute_list):
-        raise SerialisationException(
-            'The number of attributes of uuids and and corresponding children differ!\nlen(uuids) != len(children): {} != {}'.format(
-                len(uuid_attribute_list), len(children_attribute_list)))
-
-    # try to retrieve attribute from implementation and parse the children
-    for uuid_attribute, children_attribute in zip(uuid_attribute_list, children_attribute_list):
-        try:
-            uuid_list = implementation.__getattribute__(uuid_attribute)
-            children_list = implementation.__getattribute__(children_attribute)
-            for child_uuid, child in zip(uuid_list, children_list):
-                child_url = '/'.join([uuid, child_uuid]) if parent_url == '' else '/'.join([parent_url, uuid, child_uuid])
-                export_dict[child_uuid] = parse_docstrings(child, child_url)
-        except AttributeError as e:
-            raise SerialisationException('AttributeError: {}'.format(str(e)))
-
-    return export_dict
-
-
-@deprecated(version='6.0.0', reason='Building service model from docstrings is too error-prone.')
-def parse_docstrings_for_mqtt(implementation, parent_url="", *args, **kwargs):
-    if implementation is None:
-        return None
-
-    schedule = []
-    child_schedule = parse_children(parse_docstrings_for_mqtt, parent_url, implementation)
-    for key in child_schedule:
-        schedule += [*child_schedule[key]]
-    print(schedule)
-
-    # parse functions and setters/getters of variables and parameters
-    object_dict = implementation.__class__.__dict__
-    funcs = [object_dict[x] for x in object_dict if isinstance(object_dict[x], types.FunctionType)]
-    for func in funcs:
-        doc = docstring_parser.parse(func.__doc__)
-        if func.__name__ == '__init__' or doc.short_description == 'PUT.' or doc.short_description == 'DELETE.':
-            continue
-        short = doc.short_description
-        if short is not None and short[:3] != 'FUN' and len(doc.params) == 0:
-            returns = doc.returns.description.split('.')[0].split(',')
-            assert (len(returns) == 1)
-            if returns[0][:3] == 'VAR':
-                lines = doc.long_description.split('\n')
-                for line in lines:
-                    if line[:4] == 'MQTT':
-                        interval = line.replace(' ', '').split(':')[1]
-                        if len(interval) > 4 and interval[:4] == 'self':
-                            interval = implementation.__getattribute__(interval.split('.')[1])
-                            schedule += [
-                                ConfigurableJob('{}/{}'.format(parent_url, returns[0]), interval, implementation.__getattribute__(func.__name__))]
-                        else:
-                            schedule += [
-                                FixedJob('{}/{}'.format(parent_url, returns[0]), eval(interval), implementation.__getattribute__(func.__name__))]
-    return schedule
-
-
-@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
-
-    export_dict = parse_children(parse_docstrings_for_soil, parent_url, implementation)
-
-    # parse functions and setters/getters of variables and parameters
-    object_dict = implementation.__class__.__dict__
-    funcs = [object_dict[x] for x in object_dict if isinstance(object_dict[x], types.FunctionType)]
-    for func in funcs:
-        try:
-            if func.__name__[0] == '_' or func.__doc__ is None:
-                continue
-            doc = docstring_parser.parse(func.__doc__)
-            short = doc.short_description
-            if short is not None and short.split('-')[0] == 'FUN':
-                d = {'method': implementation.__getattribute__(func.__name__), 'signature': {'arguments': {}, 'returns': []}}
-                for arg in doc.params:
-                    desc = arg.description.split('.')
-                    d['signature']['arguments'][desc[0]] = arg.arg_name
-                if doc.returns is not None:
-                    returns_string = doc.returns.description.split('.')[0]
-                    if ', ' in returns_string:
-                        d['signature']['returns'] = returns_string.split(', ')
-                    if ',\n' in returns_string:
-                        d['signature']['returns'] = returns_string.split(',\n')
-                    else:
-                        d['signature']['returns'] = returns_string.split(',')
-                export_dict[short.split('.')[0]] = d
-            elif short is not None and short.split('.')[0] == 'PUT':
-                export_dict['add'] = implementation.__getattribute__(func.__name__)
-            elif short is not None and short.split('.')[0] == 'DELETE':
-                export_dict['remove'] = implementation.__getattribute__(func.__name__)
-            elif (short is None or short[:3] != 'FUN') and len(doc.params) == 0 and doc.returns is not None:
-                returns = doc.returns.description.split('.')[0].split(',')
-                assert (len(returns) == 1)
-                if returns[0][:3] == 'VAR':
-                    export_dict[returns[0]] = implementation.__getattribute__(func.__name__)
-                else:
-                    if returns[0] not in export_dict:
-                        export_dict[returns[0]] = {'setter': None, 'getter': None}
-                    export_dict[returns[0]]['getter'] = implementation.__getattribute__(func.__name__)
-            elif len(list(doc.params)) == 1:
-                # assert (doc.returns.description.split('.')[0] == 'None')
-                arg = doc.params[0].description.split('.')[0]
-                assert (arg[:3] == 'PAR')
-                if arg not in export_dict:
-                    export_dict[arg] = {'setter': None, 'getter': None}
-                export_dict[arg]['setter'] = implementation.__getattribute__(func.__name__)
-        except SerialisationException as e:
-            logger.error("{}: Error in docstring parsing: {}".format(func.__name__, e))
-
-    return export_dict
diff --git a/src/soil/element.py b/src/soil/element.py
index 323180f1aff28670b8f87ae5f7004b973332feac..db584cee11781db746383639eb1793309f2702e3 100644
--- a/src/soil/element.py
+++ b/src/soil/element.py
@@ -1,68 +1,97 @@
 from abc import abstractmethod, ABC
 
 import re
-from typing import Any, Dict, List
+from typing import Any, Dict, List, Union
 from wzl.utilities import root_logger
 
-from ..utils.constants import BASE_UUID_PATTERN, HTTP_GET
+from .device import Device
+from .error import ElementNotExistException
+from ..utils.const import BASE_UUID_PATTERN, HTTP_GET
 
 logger = root_logger.get(__name__)
 
 
 class Element(ABC):
+    """SOIL base class. All objects of a SOIL model are of type Element.
+
+    """
     UUID_PATTERN = re.compile(BASE_UUID_PATTERN)
 
-    def __init__(self, uuid: str, name: str, description: str, ontology: str = None):
+    def __init__(self, uuid: str, name: str, description: str, ontology: str = None, device: Device = None):
+        """Constructor
+
+        Args:
+            uuid: Locally unique identifier of the element.
+            name: Human readable name of the element.
+            description: Brief description of the element to give users a short explanation of the purpose or meaning of the element.
+            ontology: URI of a formally defined term from an established ontology or controlled vocabulary.
+        """
         if not isinstance(name, str) or name == '':
-            raise Exception('{}: Name is no string or the empty string!'.format(uuid))
+            raise Exception(f'{uuid}: Name is no string or the empty string!')
         if not isinstance(description, str) or description == '':
-            raise Exception('{}: Description is no string or the empty string!'.format(uuid))
+            raise Exception(f'{uuid}: Description is no string or the empty string!')
         if ontology is not None and not isinstance(ontology, str):
-            raise Exception('{}: Onthology is no string!'.format(uuid))
+            raise Exception(f'{uuid}: Ontology is no string!')
         if not isinstance(uuid, str) or not Element.UUID_PATTERN.match(uuid):
-            raise Exception('Cannot use uuid {}. Wrong format!'.format(uuid))
+            raise Exception(f'Cannot use uuid {uuid}. Wrong format!')
         else:
             self._uuid = uuid
         self._name = name
         self._description = description
         self._ontology = ontology
+        self._device = device
 
     @property
-    def uuid(self):
+    def uuid(self) -> str:
         return self._uuid
 
-    def __getitem__(self, item: str, method: int = HTTP_GET) -> Any:
-        if item == "uuid":
-            return self._uuid
-        if item == "name":
-            return self._name
-        if item == "description":
-            return self._description
-        if item == "ontology":
-            return self._ontology
-        raise Exception("{}: Key error. No attribute is named '{}'".format(self.uuid, item))
+    @property
+    def name(self) -> str:
+        return self._name
+
+    @property
+    def description(self) -> str:
+        return self._description
+
+    @property
+    def ontology(self) -> str:
+        return self._description
+
+    @abstractmethod
+    def get_element(self, fqid: Union[str, List[str]]) -> 'Element':
+        """Goes down the component and tree and searches for the element with the given fqid.
+
+        If the fqid contains only one entry (i.e. a single uuid), it must be identical to the uuid of the component, the method is invoked of.
+
+        Args:
+            fqid: Unique identifier.
+
+        Returns:
+            The element having the specified fqid.
+        """
+        ...
+
+    def __getitem__(self, item: str) -> Any:
+        if item == 'uuid':
+            return self.uuid
+        if item == 'name':
+            return self.name
+        if item == 'description':
+            return self.description
+        if item == 'ontology':
+            return self.ontology
+        raise Exception(f'{self.uuid}: Key error. No attribute is named "{item}"')
 
     def __setitem__(self, key: str, value: Any):
-        if key == "name":
-            if not isinstance(value, str) or value == '':
-                raise Exception('{}: Name is no string or the empty string!'.format(self.uuid))
-            self._name = value
-        elif key == "description":
-            if not isinstance(value, str) or value == '':
-                raise Exception('{}: Description is no string or the empty string!'.format(self.uuid))
-            self._description = value
-        elif key == "ontology":
-            if value is not None and not isinstance(value, str):
-                raise Exception('{}: Ontology is no string!'.format(self.uuid))
-            self._ontology = value
+        if key in ['name', 'description', 'ontology', 'uuid']:
+            raise Exception(f'{self.uuid}: The {key} can not be changed during runtime.')
         else:
-            raise Exception(
-                "{}: Key error. No attribute is named '{}' or it should not be changed".format(self.uuid, key))
+            raise KeyError(f'{self.uuid}: No attribute is named "{key}".')
 
-    def serialize(self, keys: List[str], method: int = HTTP_GET) -> Dict:
+    def serialize(self, keys: List[str]) -> Dict[str, Any]:
         res = {'uuid': self._uuid}
         for key in keys:
-            res[key] = self.__getitem__(key, method)
+            res[key] = self.__getitem__(key)
         if not keys:  # list is empty => serialize complete component
             res['name'] = self._name
             res['description'] = self._description
diff --git a/src/soil/error.py b/src/soil/error.py
index 173189b1f71787daf140547a5a9c0d48981b417a..a6582b5f6865866fe763b2e0e1fcb1ab92c93ddd 100644
--- a/src/soil/error.py
+++ b/src/soil/error.py
@@ -24,7 +24,7 @@ class ChildNotFound(UserException):
         UserException.__init__(self, description)
 
 
-class ChildNotFoundException(DeviceException):
+class ElementNotExistException(DeviceException):
 
     def __init__(self, description):
         BasicException.__init__(self, description)
diff --git a/src/soil/event.py b/src/soil/event.py
index 7ab51bb53037e68d6dc9b7b23070be1696a2b154..a5af3cca647fdf0c6dcd2dfd0d619c573e877965 100644
--- a/src/soil/event.py
+++ b/src/soil/event.py
@@ -24,7 +24,7 @@ class EventSeverity(IntEnum):
 
 class EventTrigger(Flag):
     """Specifies under which conditions the associated event is triggered.
-    Flags can be logically combined. Not all combinations of cause are meaningful.
+    Flags can be logically combined. Not all combinations are meaningful, of cause.
     """
     NOT = auto()
     EQUALS = auto()
diff --git a/src/soil/figure.py b/src/soil/figure.py
deleted file mode 100644
index b885cf0b0ae1c4cab1346403fe3ad941d9f89d47..0000000000000000000000000000000000000000
--- a/src/soil/figure.py
+++ /dev/null
@@ -1,299 +0,0 @@
-from abc import ABC
-
-import asyncio
-import inspect
-
-import datetime
-import nest_asyncio
-import strict_rfc3339 as rfc3339
-import time
-from typing import Any, List, Callable, Union
-from wzl.utilities import root_logger
-
-nest_asyncio.apply()
-
-from .element import Element
-from .error import DimensionException, RangeException, TypeException, NotImplementedException
-from ..utils.constants import HTTP_GET, HTTP_OPTIONS
-from ..utils.error import DeviceException
-
-logger = root_logger.get(__name__)
-
-
-def parse_time(time_rfc3339: Union[str, List]):
-    if isinstance(time_rfc3339, list):
-        return [parse_time(e) for e in time_rfc3339]
-    else:
-        if time_rfc3339 is None or time_rfc3339 == "":
-            return None
-        timestamp = rfc3339.rfc3339_to_timestamp(time_rfc3339)
-        date = list(time.gmtime(int(timestamp)))[:6]
-        return datetime.datetime(*date, int((timestamp - int(timestamp)) * 1e6))
-
-
-def serialize_time(time):
-    timestamp = datetime.datetime.timestamp(time)
-    return rfc3339.timestamp_to_rfc3339_localoffset(timestamp)
-
-
-class Figure(Element, ABC):
-    def __init__(self, uuid: str, name: str, description: str, datatype: str, dimension: List, range: List, value: Any, getter: Callable,
-                 ontology: str = None):
-        Element.__init__(self, uuid, name, description, ontology)
-        if type(datatype) is not str:
-            raise Exception('{}: Datatype must be passed as string.'.format(uuid))
-        Figure.check_all(datatype, dimension, range, value)
-        if getter is not None and not callable(getter):
-            raise TypeError("{}: The getter of the Figure must be callable!".format(uuid))
-        self._datatype = datatype
-        self._dimension = dimension
-        self._range = range
-        if datatype == 'time':
-            self._value = parse_time(value)
-        else:
-            self._value = value
-        self._getter = getter
-
-
-    @property
-    def datatype(self):
-        return self._datatype
-
-    @property
-    def dimension(self):
-        return self._dimension
-
-    @property
-    def range(self):
-        return self._range
-
-    def __getitem__(self, item: str, method=HTTP_GET):
-        """
-        Getter-Method.
-        According to the given key the method returns the value of the corresponding attribute.
-        :param item: name of the attribute. Provided as string without leading underscores.
-        :param method: ???
-        :return: the value of the attribute indicated by 'item'.
-        """
-        if item == "datatype":
-            return self._datatype
-        if item == "value":
-            if method != HTTP_OPTIONS:
-                try:
-                    if inspect.iscoroutinefunction(self.get):
-                        loop = asyncio.get_event_loop()
-                        value = loop.run_until_complete(asyncio.gather(self.get()))[0]
-                    else:
-                        value = self.get()
-
-                    if self._datatype == 'time':
-                        value = serialize_time(value)
-                    elif self._datatype == 'enum':
-                        value = str(value)
-
-                except Exception as e:
-                    raise DeviceException('Could not provide value of Measurement/Parameter {}: {}'.format(self.uuid, str(e)), predecessor=e)
-
-                Figure.check_all(self._datatype, self._dimension, self._range, value)
-                return value
-            else:
-                return self._value
-        if item == 'dimension':
-            return self._dimension
-        if item == 'range':
-            return self._range
-        if item == []:
-            return self
-        return super().__getitem__(item, method)
-
-    # def __setitem__(self, key: str, value):
-    #     """
-    #     Setter - Method
-    #     If key is "value" datatype, dimension and range is checked for correctness.
-    #     :param key: sets the value of the attribute with name 'item' to the provided value.
-    #     :param value: value to be set
-    #     """
-    #     super().__setitem__(key, value)
-
-    def serialize(self, keys: [str], method=HTTP_GET):
-        """
-        Serializes an object of type Figure into a JSON-like dictionary.
-        :param keys: All attributes given in the "keys" array are serialized.
-        :param method: ???
-        :return: a dictionary having all "keys" as keys and the values of the corresponding attributes as value.
-        """
-        # list is empty provide all attributes of the default-serialization
-        if not keys:
-            keys = ['uuid', 'name', 'description', 'datatype', 'value', 'dimension', 'range']
-        # get all attribute values
-        dictionary = {}
-        for key in keys:
-            value = self.__getitem__(key, method)
-            dictionary[key] = value
-        return dictionary
-
-    @staticmethod
-    def check_dimension(dimension: List, value: Any):
-        """
-        Checks whether the given value is of given dimension
-        :param dimension: the dimension the value provided by "value" should have
-        :param value: value to be checked for the dimension
-        """
-        # dimension of undefined value must not be checked => valid
-        if value is None:
-            return
-        # base case 1: dimension is empty and variable is not a scalar => not valid
-        if not dimension and not Figure.is_scalar(value):
-            raise DimensionException('Figure of dimension 0 can not be of type list!')
-        # base case 2: dimension is empty and variable is a scalar => valid
-        elif not dimension:
-            return
-        try:
-            # base case 3: current dimension is fixed size "x" and length of the value is not "x" => not valid
-            if dimension[0] != 0 and len(value) != dimension[0]:
-                raise DimensionException('Dimension of data does not match dimension of variable!')
-        except TypeError as te:
-            raise DimensionException(str(te))
-        # recursion case
-        # at this point value is guaranteed to be of type list
-        # => recursively check the dimension of each "subvalue"
-        for v in value:
-            try:
-                Figure.check_dimension(dimension[1:], v)
-            except DimensionException as e:
-                raise e
-
-    @staticmethod
-    def check_type(datatype, value):
-        """
-        Checks if the given value is of the correct datatype. If value is not a scale, it checks all "subvalues" for correct datatype.
-        :param datatype: datatype the value provided by "value" should have
-        :param value: value to be checked for correct datatype
-        """
-        # datatype of undefined value must not be checked => valid
-        if value is None:
-            return
-        # base case: value is a scalar
-        if Figure.is_scalar(value):
-            # check if the type of value corresponds to given datatype
-            if datatype == 'bool' and not isinstance(value, bool):
-                raise TypeException("Boolean field does not match non-boolean value {}!".format(value))
-            elif datatype == 'int' and not isinstance(value, int):
-                raise TypeException("Integer field does not match non-integer value {}!".format(value))
-            elif datatype == 'double' and not isinstance(value, float) and not isinstance(value, int):
-                raise TypeException("Double field does not match non-double value {}!".format(value))
-            elif datatype == 'string' and not isinstance(value, str):
-                raise TypeException("String field does not match non-string value {}!".format(value))
-            elif datatype == 'enum' and not isinstance(value, str):
-                raise TypeException(
-                    "Enum field {} must be a string!".format(value))
-            elif datatype == 'time' and not isinstance(value, str):
-                raise TypeException(
-                    "Time field {} must be string.".format(
-                        value))
-            elif datatype == 'time' and isinstance(value, str):
-                if value != "" and value is not None and not rfc3339.validate_rfc3339(value):
-                    raise TypeException("Value is not a valid RFC3339-formatted timestring: {}".format(value))
-            elif datatype not in ["bool", "int", "double", "string", "enum", "time"]:
-                raise TypeException("Unknown type descriptor: {}".format(datatype))
-        else:
-            # recursion case: value is an array or matrix => check datatype of each "subvalue" recursively
-            for v in value:
-                try:
-                    Figure.check_type(datatype, v)
-                except TypeException as e:
-                    raise e
-
-    @staticmethod
-    def check_range(datatype, range, value):
-        """
-        Checks if the given value is within provided range (depending on the given datatype)
-
-        IMPORTANT: It is not checked whether the value is of correct type. If the type of value is not correct, the result
-        of check_range is not meaningful! To get expressive result check datatype before calling check_range!
-
-        :param datatype: datatype of the value
-        :param range: the range the value should be within
-        :param value: value to be checked for range
-
-        For all datatypes (except "bool" and "enum")the range specification is of the following form: [lower bound (LB), upper bound (UB)]
-        If LB or UB are None the value is unrestricted to the bottom or top, respectively.
-        In case of "int", "double" and "time" the interpretation of LB and UB is straightforward.
-        For "string" LB and UB restrict the length of the string. (If LB is given as None, 0 is the natural LB of cause)
-        "bool" is naturally bounded to "True" and "False", thus the range is not checked.
-        In case of "enum" the range contains the list of all possible values.
-        """
-        # if the list is empty, all values are possible
-        if not range:
-            if datatype == 'enum':
-                raise RangeException('A value of type enum must provide a range with possible values!')
-            else:
-                return
-        # base case: value is scalar => check if the value is in range
-        if Figure.is_scalar(value):
-            # bool is not checked, since there is only true and false
-            if datatype == 'bool':
-                return
-            elif datatype == 'int' and value is not None:
-                if range[0] is not None and value < range[0]:
-                    raise RangeException("Integer value {} is smaller than lower bound {}!".format(value, range[0]))
-                elif range[1] is not None and value > range[1]:
-                    raise RangeException("Integer value {} is higher than upper bound {}!".format(value, range[1]))
-            elif datatype == 'double' and value is not None:
-                if range[0] is not None and value < range[0]:
-                    raise RangeException("Double value {} is smaller than lower bound {}!".format(value, range[0]))
-                elif range[1] is not None and value > range[1]:
-                    raise RangeException("Double value {} is higher than upper bound {}!".format(value, range[1]))
-            elif datatype == 'string' and value is not None:
-                if range[0] is not None and len(value) < range[0]:
-                    raise RangeException(
-                        "String value {} is too short. Minimal required length is {}!".format(value, range[0]))
-                elif range[1] is not None and len(value) > range[1]:
-                    raise RangeException(
-                        "String value {} is too long. Maximal allowed length is {}!".format(value, range[1]))
-            elif datatype == 'enum' and value is not None:
-                if value not in range:
-                    raise RangeException("Enum value {} is not within the set of allowed values!".format(value))
-            elif datatype == 'time' and value is not None and value != "":
-                if range[0] is not None:
-                    if not rfc3339.validate_rfc3339(range[0]):
-                        raise TypeException(
-                            "Can not check range of time value. Lower bound {} is not a valid RFC3339 timestring.".format(
-                                range[0]))
-                    if parse_time(value) < parse_time(range[0]):
-                        raise RangeException(
-                            "Time value {} is smaller than lower bound {}!".format(parse_time(value),
-                                                                                   parse_time(range[0])))
-                elif range[1] is not None:
-                    if not rfc3339.validate_rfc3339(range[1]):
-                        raise TypeException(
-                            "Can not check range of time value. Upper bound {} is not a valid RFC3339 timestring.".format(
-                                range[0]))
-                    if parse_time(value) > parse_time(range[1]):
-                        raise RangeException(
-                            "Time value {} is greater than upper bound {}!".format(parse_time(value),
-                                                                                   parse_time(range[1])))
-        else:
-            # recursion case: value is an array or matrix => check range of each "subvalue" recursively
-            for v in value:
-                try:
-                    Figure.check_range(datatype, range, v)
-                except RangeException as e:
-                    raise e
-
-    @staticmethod
-    def is_scalar(value):
-        return not isinstance(value, list)
-
-    @staticmethod
-    def check_all(datatype, dimension, range, value):
-        Figure.check_type(datatype, value)
-        Figure.check_dimension(dimension, value)
-        Figure.check_range(datatype, range, value)
-
-    @property
-    def get(self):
-        if self._getter is not None:
-            return self._getter
-        else:
-            raise NotImplementedException(self._uuid, self._name)
diff --git a/src/soil/function.py b/src/soil/function.py
index b4e42cd08ec50814b12bbe480306f582615ba8b7..2dab6b6c0242f7ce479c914cfd6971c70a317427 100644
--- a/src/soil/function.py
+++ b/src/soil/function.py
@@ -1,46 +1,93 @@
+import abc
 import functools
 import inspect
-from typing import Any, Dict, List, Union
+from typing import Any, Dict, List, Union, Callable
 from wzl.utilities import root_logger
 
+from device.device import Device
 from .element import Element
-from .error import InvokationException, NotImplementedException
+from .error import InvokationException, NotImplementedException, ElementNotExistException
 from ..utils.error import SerialisationException, DeviceException
-from .figure import Figure
+from .variable import Variable
 from .parameter import Parameter
-from ..utils.constants import HTTP_GET, HTTP_OPTIONS
+from ..utils.const import HTTP_GET, HTTP_OPTIONS
 
 logger = root_logger.get(__name__)
 
 
-class Function(Element):
+def raise_publish_exception(uuid: str, name: str):
+    raise InvokationException(uuid, name)
 
-    def __init__(self, uuid: str, name: str, description: str, arguments: List[Figure], returns: List[Figure],
-                 implementation: Dict, ontology: str = None):
-        Element.__init__(self, uuid, name, description, ontology)
+
+class Function(Element, abc.ABC):
+
+    def __init__(self, uuid: str, name: str, description: str, arguments: List[Parameter] = None,
+                 returns: List[Parameter] = None, ontology: str = None, publish: Callable = None, device: Device = None):
+        Element.__init__(self, uuid, name, description, ontology, device)
         if uuid[:3] != 'FUN':
-            raise Exception('{}: The UUID must start with FUN!'.format(uuid))
+            raise Exception(f'{uuid}: The UUID must start with FUN!')
+
+        self._arguments = [] if arguments is None else arguments
         if not isinstance(arguments, list):
-            raise Exception('{}: Given arguments are not a list!'.format(uuid))
-        for i in arguments:
-            if not isinstance(i, Parameter):
-                raise Exception('{}: Given argument is not of type Parameter!'.format(uuid))
+            raise Exception(f'{uuid}: Given arguments are not a list!')
+        for a in arguments:
+            if not isinstance(a, Parameter):
+                raise Exception(f'{uuid}: Given argument is not of type Parameter!')
+
+        self._returns = [] if returns is None else returns
         if not isinstance(returns, list):
-            raise Exception('{}: Given returns are not a list!'.format(uuid))
-        for o in returns:
-            if not isinstance(o, Parameter):
-                raise Exception('{}: Given return is not of type Parameter!'.format(uuid))
-
-        self._arguments = arguments
-        self._returns = returns
-        self._implementation = implementation['method']
-        self._signature = implementation['signature']
-        self._mqtt_callback = implementation['mqtt_callback'] if 'mqtt_callback' in implementation.keys() else None
-
-    def __getitem__(self, item: Union[str, List[str]], method: int = HTTP_GET) -> Any:
-        if item == "arguments":
+            raise Exception(f'{uuid}: Given returns are not a list!')
+        for r in returns:
+            if not isinstance(r, Parameter):
+                raise Exception(f'{uuid}: Given return is not of type Parameter!')
+
+        self._publish = raise_publish_exception if publish is None else publish
+
+    @property
+    def arguments(self) -> List[Variable]:
+        return self._arguments
+
+    @property
+    def returns(self) -> List[Variable]:
+        return self._returns
+
+    @property
+    def children(self) -> List[Element]:
+        return self.arguments + self.returns
+
+    def get_element(self, fqid: Union[str, List[str]]) -> Element:
+        """Goes down the component and tree and searches for the element with the given fqid.
+
+        If the fqid contains only one entry (i.e. a single uuid), it must be identical to the uuid of the component, the method is invoked of.
+
+        Args:
+            fqid: Unique identifier.
+
+        Returns:
+            The element having the specified fqid.
+        """
+        if not fqid:
+            raise ValueError(f'The fqid must not be empty!')
+
+        if isinstance(fqid, str):
+            fqid = fqid.split('/')
+
+        if fqid[0] == self.uuid:
+            if len(fqid) == 1:
+                return self
+            else:
+                for child in self.children:
+                    try:
+                        return child.get_element(fqid[1:])
+                    except ElementNotExistException as e:
+                        continue
+
+        raise ElementNotExistException(f'An element with the uuid {"/".join(fqid)} does not exist.')
+
+    def __getitem__(self, item: Union[str, List[str]]) -> Any:
+        if item == 'arguments':
             return self._arguments
-        if item == "returns":
+        if item == 'returns':
             return self._returns
         if isinstance(item, list):
             if len(item) == 0:
@@ -49,11 +96,12 @@ class Function(Element):
             for o in everything:
                 if o.uuid == item[0]:
                     return o[item[1:]]
-            raise Exception("{}: Given uuid {} is not the id of a child of the current component!".format(self.uuid, item))
-        return super().__getitem__(item, method)
+            raise Exception(f'{self.uuid}: Given uuid {item} is not the id of a child of the current component!')
+        return super().__getitem__(item)
 
     def __setitem__(self, key: str, value: Any):
-        if key == "arguments":
+        # TODO refactor
+        if key == 'arguments':
             if not isinstance(value, list):
                 raise Exception('{}: Given arguments are not a list!'.format(self.uuid))
             if len(value) != len(self._arguments):
@@ -61,32 +109,34 @@ class Function(Element):
                     '{}: The number of given arguments does not match the number of required arguments!'.format(
                         self.uuid))
             for v in value:
-                if not isinstance(v, Figure):
+                if not isinstance(v, Variable):
                     raise Exception('{}: Given argument is not of type Figure!'.format(self.uuid))
             self._arguments = value
-        elif key == "returns":
+        elif key == 'returns':
             if not isinstance(value, list):
                 raise Exception('{}: Given returns are not a list!'.format(self.uuid))
             if len(value) != len(self._returns):
                 raise Exception(
                     '{}: The number of given returns does not match the number of required returns!'.format(self.uuid))
             for v in value:
-                if not isinstance(v, Figure):
+                if not isinstance(v, Variable):
                     raise Exception('{}: Given return is not of type Figure!'.format(self.uuid))
             self._returns = value
         else:
             super().__setitem__(key, value)
 
-    async def invoke(self, arguments: List[Figure], topic) -> Dict[str, List[Dict[str, Any]]]:
-        returns = {"returns": []}
+    @abc.abstractmethod
+    def execute(self, *args, **kwargs):
+        ...
+
+    async def invoke(self, arguments: List[Variable], topic: str) -> List[Variable]:
+        returns = {'returns': []}
         args = {}
-        if self._implementation is None:
-            raise NotImplementedException(self._uuid, self._name)
 
         for a in arguments:
-            var = self.__getitem__([a["uuid"]])
-            Figure.check_all(var.datatype, var.dimension, var.range, a["value"])
-            args[self._signature['arguments'][a["uuid"]]] = a["value"]
+            var = self.__getitem__([a['uuid']])
+            Variable.check_all(var.datatype, var.dimension, var.range, a['value'])
+            args[self._signature['arguments'][a['uuid']]] = a['value']
 
         # set up servers
         try:
@@ -109,17 +159,18 @@ class Function(Element):
                 result = (result,)
             if len(result) != len(self._returns):
                 raise InvokationException(self._uuid, self._name,
-                                          "Internal Server Error. Function with UUID {} should return {} parameters, but invoked method returned {} values!".format(
+                                          'Internal Server Error. Function with UUID {} should return {} parameters, but invoked method returned {} values!'.format(
                                               self.uuid, len(result), len(self._returns)))
 
             for value, uuid in zip(result, self._signature['returns']):
                 var = [x for x in self._returns if x['uuid'] == uuid]
                 if len(var) != 1:
                     raise InvokationException(self._uuid, self._name,
-                                              "Internal Server Error. UUID {} of returned parameter does not match!".format(uuid))
+                                              'Internal Server Error. UUID {} of returned parameter does not match!'.format(
+                                                  uuid))
                 else:
                     var = var[0]
-                    Figure.check_all(var.datatype, var.dimension, var.range, value)
+                    Variable.check_all(var.datatype, var.dimension, var.range, value)
                     returns['returns'] += [{'uuid': uuid, 'value': value}]
 
         return returns
@@ -130,11 +181,14 @@ class Function(Element):
         dictionary = super().serialize(keys)
         if 'arguments' in keys:
             dictionary['arguments'] = list(
-                map(lambda x: x.serialize(['name', 'uuid', 'description', 'datatype', 'value', 'dimension', 'range', 'ontology'], HTTP_OPTIONS),
+                map(lambda x: x.serialize(
+                    ['name', 'uuid', 'description', 'datatype', 'value', 'dimension', 'range', 'ontology'],
+                    HTTP_OPTIONS),
                     self._arguments))
         if 'returns' in keys:
             dictionary['returns'] = list(
-                map(lambda x: x.serialize(['name', 'uuid', 'description', 'datatype', 'dimension', 'ontology'], HTTP_OPTIONS), self._returns))
+                map(lambda x: x.serialize(['name', 'uuid', 'description', 'datatype', 'dimension', 'ontology'],
+                                          HTTP_OPTIONS), self._returns))
         return dictionary
 
     @staticmethod
@@ -144,15 +198,19 @@ class Function(Element):
         uuid = dictionary['uuid']
         if uuid[:3] != 'FUN':
             raise SerialisationException(
-                'The Function can not be deserialized. The UUID must start with FUN, but actually starts with {}!'.format(uuid[:3]))
+                'The Function can not be deserialized. The UUID must start with FUN, but actually starts with {}!'.format(
+                    uuid[:3]))
         if 'name' not in dictionary:
             raise SerialisationException('{}: The function can not be deserialized. Name is missing!'.format(uuid))
         if 'description' not in dictionary:
-            raise SerialisationException('{}: The function can not be deserialized. Description is missing!'.format(uuid))
+            raise SerialisationException(
+                '{}: The function can not be deserialized. Description is missing!'.format(uuid))
         if 'arguments' not in dictionary:
-            raise SerialisationException('{}: The function can not be deserialized. List of arguments is missing!'.format(uuid))
+            raise SerialisationException(
+                '{}: The function can not be deserialized. List of arguments is missing!'.format(uuid))
         if 'returns' not in dictionary:
-            raise SerialisationException('{}: The function can not be deserialized. List of returns is missing!'.format(uuid))
+            raise SerialisationException(
+                '{}: The function can not be deserialized. List of returns is missing!'.format(uuid))
 
         try:
             arguments = []
@@ -168,6 +226,7 @@ class Function(Element):
             raise SerialisationException('{}: A return of the function can not be deserialized. {}'.format(uuid, e))
         try:
             ontology = dictionary['ontology'] if 'ontology' in dictionary else None
-            return Function(dictionary['uuid'], dictionary['name'], dictionary['description'], arguments, returns, implementation, ontology)
+            return Function(dictionary['uuid'], dictionary['name'], dictionary['description'], arguments, returns,
+                            implementation, ontology)
         except Exception as e:
             raise SerialisationException('{}: The function can not be deserialized. {}'.format(uuid, e))
diff --git a/src/soil/measurement.py b/src/soil/measurement.py
index c38f79210e81eced6e27f6b63d673011e04af087..3707765e22843628579e4fda8af20e635183aabc 100644
--- a/src/soil/measurement.py
+++ b/src/soil/measurement.py
@@ -1,47 +1,47 @@
-from deprecated import deprecated
-from typing import Dict
+from typing import Dict, List, Any, Union
+
 from wzl.utilities import root_logger
 
-from ..utils.constants import HTTP_GET
+from device.device import Device
+from .datatype import Datatype
+from .element import Element
+from .error import ElementNotExistException
+from .variable import Variable
+from ..utils.const import Range
 from ..utils.error import SerialisationException
-from .figure import Figure
 
 logger = root_logger.get(__name__)
 
 
-class Measurement(Figure):
+class Measurement(Variable):
 
-    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)
+    def __init__(self, uuid: str, name: str, description: str, datatype: Datatype, dimension: List[int],
+                 range: Range, unit: str = None, nonce=None, ontology: str = None, device: Device = None):
+        Variable.__init__(self, uuid, name, description, datatype, dimension, range, unit, ontology, device)
         if uuid[:3] != 'MEA':
-            raise Exception('{}: The UUID must start with MEA!'.format(uuid))
-        self._unit = unit
+            raise Exception(f'{uuid}: The UUID of a measurement must start with MEA!')
         self._covariance = None  # TODO init
         self._uncertainty = None  # TODO init
         self._timestamp = None
         self._nonce = nonce
 
     @property
-    def unit(self):
-        return self._unit
-
-    @property
-    def covariance(self):
+    def covariance(self) -> Any:
         return self._covariance
 
     @property
-    def uncertainty(self):
+    def uncertainty(self) -> Any:
         return self._uncertainty
 
     @property
-    def timestamp(self):
+    def timestamp(self) -> str:
         return self._timestamp
 
     @property
-    def nonce(self):
+    def nonce(self) -> str:
         return self._nonce
 
-    def __getitem__(self, item: str, method=HTTP_GET):
+    def __getitem__(self, item: str):
         """
         Getter-Method.
         According to the given key the method returns the value of the corresponding attribute.
@@ -49,8 +49,6 @@ class Measurement(Figure):
         :param method: ???
         :return: the value of the attribute indicated by 'item'.
         """
-        if item == "unit":
-            return self._unit
         if item == 'nonce':
             return self._nonce
         if item == 'covariance':
@@ -61,7 +59,7 @@ class Measurement(Figure):
             return self._timestamp
         if item == []:
             return self
-        return super().__getitem__(item, method)
+        return super().__getitem__(item)
 
     def __setitem__(self, key: str, value):
         """
@@ -71,15 +69,13 @@ class Measurement(Figure):
         :param value: value to be set
         """
         if key in ['value', 'timestamp', 'covariance', 'uncertainty']:
-            raise KeyError('The {} attribute of a measurement can not be set manually!'.format(key))
-        elif key == "nonce":
+            raise KeyError(f'The {key} of a measurement can not be set manually!')
+        elif key == 'nonce':
             self._nonce = self._nonce
-        elif key == 'unit':
-            self._unit = self._unit
         else:
             super().__setitem__(key, value)
 
-    def serialize(self, keys: [str], method=HTTP_GET):
+    def serialize(self, keys: [str]):
         """
         Serializes an object of type Measurement into a JSON-like dictionary.
         :param keys: All attributes given in the "keys" array are serialized.
@@ -88,23 +84,26 @@ class Measurement(Figure):
         """
         # list is empty provide all attributes of the default-serialization
         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
+            keys = ['uuid', 'name', 'description', 'datatype', 'value', 'dimension', 'range', 'timestamp', 'nonce',
+                    'covariance', 'uncertainty', 'unit', 'ontology']
+
+        # if the value is returned, always return the timestamp!
         if 'value' in keys and 'timestamp' not in keys:
             keys += ['timestamp']
         dictionary = {}
+
         # get all attribute values
         for key in keys:
-            value = self.__getitem__(key, method)
+            value = self.__getitem__(key)
             # in case of timestamp convert into RFC3339 string
+            # TODO fix time formatting
             if key == 'timestamp' or (key == 'value' and self._datatype == 'time'):
-                value = value.isoformat() + 'Z' if value is not None else ""
+                value = value.isoformat() + 'Z' if value is not None else ''
             dictionary[key] = value
         return dictionary
 
     @staticmethod
-    def deserialize(dictionary: Dict, implementation=None):
+    def deserialize(dictionary: Dict[str, Any]) -> 'Measurement':
         """
         Takes a JSON-like dictionary, parses it, performs a complete correctness check and returns an object of type Figure with the
          values provided in the dictionary, if dictionary is a valid serialization of a Figure.
@@ -112,30 +111,23 @@ class Measurement(Figure):
         :param implementation: implementation wrapper object,
         :return: an object of type Figure
         """
-        # check if all required attributes are present
+
         if 'uuid' not in dictionary:
             raise SerialisationException('The measurement can not be deserialized. UUID is missing!')
         uuid = dictionary['uuid']
         if uuid[:3] != 'MEA':
             raise SerialisationException(
-                'The Measurement can not be deserialized. The UUID must start with MEA, but actually starts with {}!'.format(uuid[:3]))
-        if 'name' not in dictionary:
-            raise SerialisationException('{}: The measurement can not be deserialized. Name is missing!'.format(uuid))
-        if 'description' not in dictionary:
-            raise SerialisationException('{}: The measurement can not be deserialized. Description is missing!'.format(uuid))
-        if 'datatype' not in dictionary:
-            raise SerialisationException('{}: The measurement can not be deserialized. Datatype is missing!'.format(uuid))
-        if 'dimension' not in dictionary:
-            raise SerialisationException('{}: The measurement can not be deserialized. Dimension is missing!'.format(uuid))
-        if 'value' not in dictionary:
-            raise SerialisationException('{}: The measurement can not be deserialized. Value is missing!'.format(uuid))
-        if 'range' not in dictionary:
-            raise SerialisationException('{}: The measurement can not be deserialized. Range is missing!'.format(uuid))
-        if 'unit' not in dictionary:
-            raise SerialisationException('{}: The measurement can not be deserialized. Unit is missing!'.format(uuid))
+                f'The Measurement can not be deserialized. The UUID must start with MEA, but actually starts with {uuid[:3]}!')
+
+        # check if all required attributes are present
+        for key in ['name', 'description', 'datatype', 'dimension', 'range']:
+            if key not in dictionary:
+                raise SerialisationException(f'{uuid}: The measurement can not be deserialized. {key.capitalize()} is missing!')
         try:
             ontology = dictionary['ontology'] if 'ontology' in dictionary else None
-            return Measurement(dictionary['uuid'], dictionary['name'], dictionary['description'], dictionary['datatype'], dictionary['dimension'],
-                            dictionary['range'], implementation, dictionary['unit'], ontology)
+            unit = dictionary['unit'] if 'unit' in dictionary else None
+            return Measurement(dictionary['uuid'], dictionary['name'], dictionary['description'],
+                               dictionary['datatype'], dictionary['dimension'],
+                               dictionary['range'], unit, ontology)
         except Exception as e:
             raise SerialisationException('{}: The measurement can not be deserialized. {}'.format(uuid, e))
diff --git a/src/soil/object.py b/src/soil/object.py
deleted file mode 100644
index ade708a9e5c64fd67d8370958a75588b486da8f5..0000000000000000000000000000000000000000
--- a/src/soil/object.py
+++ /dev/null
@@ -1,333 +0,0 @@
-# from __future__ import annotations
-import json
-import os
-import sys
-from deprecated import deprecated
-from typing import List, Any, Union, Dict
-from wzl.utilities import root_logger
-
-from . import docstring_parser
-from .element import Element
-from .error import ChildNotFoundException
-from ..utils.error import SerialisationException, DeviceException, UserException
-from .function import Function
-from .variable import Variable
-from .parameter import Parameter
-from ..utils.constants import HTTP_GET
-
-logger = root_logger.get(__name__)
-
-
-@deprecated(version='6.0.0', reason='"Object" has been renamed to "Component" to avoid ambiguity.')
-class Object(Element):
-
-    def __init__(self, uuid: str, name: str, description: str, functions: List[Function], variables: List[Variable], parameters: List[Parameter],
-                 objects: List['Object'], implementation: Dict, ontology: str = None):
-        Element.__init__(self, uuid, name, description, ontology)
-        if not isinstance(functions, list):
-            raise Exception('{}: Given functions are not a list!'.format(uuid))
-        for f in functions:
-            if not isinstance(f, Function):
-                raise Exception('{}: Given function is not of type Function!'.format(uuid))
-        if not isinstance(variables, list):
-            raise Exception('{}: Given variables are not a list!'.format(uuid))
-        for v in variables:
-            if not isinstance(v, Variable):
-                raise Exception('{}: Given variable is not of type Variables!'.format(uuid))
-        if not isinstance(parameters, list):
-            raise Exception('{}: Given variables are not a list!'.format(uuid))
-        for p in parameters:
-            if not isinstance(p, Parameter):
-                raise Exception('{}: Given variable is not of type Variables!'.format(uuid))
-        if not isinstance(objects, list):
-            raise Exception('{}: Given objects are not a list!'.format(uuid))
-        for o in objects:
-            if not isinstance(o, Object):
-                raise Exception('{}: Given object is not of type Objects!'.format(uuid))
-
-        self._functions = functions
-        self._variables = variables
-        self._objects = objects
-        self._parameters = parameters
-        self._implementation_add = implementation['add'] if 'add' in implementation else None
-        self._implementation_remove = implementation['remove'] if 'remove' in implementation else None
-
-    def __getitem__(self, item: Union[str, List[str]], method: int = HTTP_GET) -> Any:
-        attribute = False
-        if isinstance(item, str):
-            attribute = hasattr(self, item)
-        if item == "functions":
-            return self._functions
-        if item == "variables":
-            return self._variables
-        if item == "parameters":
-            return self._variables
-        if item == "objects":
-            return self._objects
-        if item == "children":
-            ret = []
-            everything = self._objects + self._variables + self._parameters + self._functions
-            for o in everything:
-                ret += [o.uuid]
-            return ret
-        # if the item is a list, the list contains the uuid of the descendants
-        if isinstance(item, list):
-            if len(item) > 0 and super().__getitem__('uuid', method) == item[0]:
-                item = item[1:]
-            if len(item) == 0:
-                return self
-            everything = self._objects + self._variables + self._parameters + self._functions
-            for o in everything:
-                if o.uuid == item[0]:
-                    if len(item) == 1:
-                        return o
-                    else:
-                        return o.__getitem__(item[1:], method)
-            raise Exception("{}: Given uuid {} is not the id of a child of the current object!".format(self.uuid, item))
-        return super().__getitem__(item, method)
-
-    def __setitem__(self, key: str, value: Any):
-        if key == "functions":
-            if not isinstance(value, list):
-                raise Exception('{}: Given functions are not a list!'.format(self.uuid))
-            for f in value:
-                if not isinstance(f, Function):
-                    raise Exception('{}: Given function is not of type Function!'.format(self.uuid))
-            self._functions = value
-        elif key == "variables":
-            if not isinstance(value, list):
-                raise Exception('{}: Given variables are not a list!'.format(self.uuid))
-            for v in value:
-                if not isinstance(v, Variable):
-                    raise Exception('{}: Given variable is not of type Variable!'.format(self.uuid))
-            self._variables = value
-        elif key == "parameters":
-            if not isinstance(value, list):
-                raise Exception('{}: Given parameters are not a list!'.format(self.uuid))
-            for v in value:
-                if not isinstance(v, Parameter):
-                    raise Exception('{}: Given parameter is not of type Parameter!'.format(self.uuid))
-            self._variables = value
-        elif key == "objects":
-            if not isinstance(value, list):
-                raise Exception('{}: Given objects are not a list!'.format(self.uuid))
-            for o in value:
-                if not isinstance(o, Object):
-                    raise Exception('{}: Given object is not of type Object!'.format(self.uuid))
-            self._objects = value
-        else:
-            super().__setitem__(key, value)
-
-    def serialize(self, keys: List[Any], method: int = HTTP_GET) -> Dict[str, Any]:
-        if not keys:  # list is empty
-            keys = ['uuid', 'name', 'description', 'children', 'ontology']
-
-        if 'all' in keys:  # serialize complete tree recursively (overrides all other keys)
-            dictionary = super().serialize([])
-            dictionary['variables'] = list(map(lambda x: x.serialize([]), self._variables))
-            dictionary['functions'] = list(map(lambda x: x.serialize(['all']), self._functions))
-            dictionary['objects'] = list(map(lambda x: x.serialize(['all']), self._objects))
-            dictionary['parameters'] = list(map(lambda x: x.serialize(['all']), self._parameters))
-            return dictionary
-
-        dictionary = super().serialize(keys, method)
-        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
-    def merge_dictionaries(parent_dict, object_dict):
-        def merge_variables(parent_list, object_list):
-            for variable in parent_list:
-                if 'uuid' not in variable:
-                    raise Exception('UUID {} not given for variable to be overwritten.'.format(variable['uuid']))
-                idx = [i for i, v in enumerate(object_list) if v['uuid'] == variable['uuid']]
-                if len(idx) != 1:
-                    raise Exception('Mismatching UUID: {}.'.format(variable['uuid']))
-                idx = idx[0]
-                object_list[idx].update(variable)
-            return object_list
-
-        def merge_functions(parent_list, object_list):
-            for function in parent_list:
-                if 'uuid' not in function:
-                    raise Exception('UUID {} not given for function to be overwritten.'.format(function['uuid']))
-                idx = [i for i, v in enumerate(object_list) if v['uuid'] == function['uuid']]
-                if len(idx) != 1:
-                    raise Exception('Mismatching UUID: {}.'.format(function['uuid']))
-                idx = idx[0]
-                if 'name' in function:
-                    object_list[idx]['name'] = function['name']
-                if 'description' in function:
-                    object_list[idx]['description'] = function['description']
-                if 'arguments' in function:
-                    object_list[idx]['arguments'] = merge_variables(function['arguments'], object_list[idx]['arguments'])
-                if 'returns' in function:
-                    object_list[idx]['returns'] = merge_variables(function['returns'], object_list[idx]['returns'])
-            return object_list
-
-        # merge objects, i.e. overwrite fields of "static" children dictionary with the "dynamic" fields of the parents dictionary
-        uuid = parent_dict['uuid']
-        object_dict['uuid'] = uuid
-        if 'name' in parent_dict:
-            object_dict['name'] = parent_dict['name']
-        if 'description' in parent_dict:
-            object_dict['description'] = parent_dict['description']
-        if 'variables' in parent_dict:
-            object_dict['variables'] = merge_variables(parent_dict['variables'], object_dict['variables'])
-        if 'parameters' in parent_dict:
-            object_dict['paramters'] = merge_variables(parent_dict['parameters'], object_dict['parameters'])
-        if 'functions' in parent_dict:
-            object_dict['functions'] = merge_functions(parent_dict['functions'], object_dict['functions'])
-        if 'objects' in parent_dict:
-            for obj in parent_dict['objects']:
-                index = [i for i, o in enumerate(object_dict['objects']) if o['uuid'] == obj['uuid']]
-                if len(index) != 1:
-                    raise Exception('Mismatching UUID: {}.'.format(obj['uuid']))
-                index = index[0]
-                object_dict['objects'][index] = Object.merge_dictionaries(obj, object_dict['objects'][index])
-        return object_dict
-
-    @staticmethod
-    def deserialize(dictionary, mapping=None, filepath=''):
-        if 'uuid' not in dictionary:
-            raise SerialisationException('The object can not be deserialized. UUID is missing!')
-        uuid = dictionary['uuid']
-        if 'file' in dictionary:
-            try:
-                with open(os.path.normpath(os.path.join(filepath, dictionary['file']))) as file:
-                    object_dict = json.load(file)
-                dictionary = Object.merge_dictionaries(dictionary, object_dict)
-            except Exception as e:
-                raise SerialisationException('{}: The object can not be deserialized. Provided JSON-file can not be parsed! {}'.format(uuid, e))
-        if 'name' not in dictionary:
-            raise SerialisationException('{}: The object can not be deserialized. Name is missing!'.format(uuid))
-        if 'description' not in dictionary:
-            raise SerialisationException('{}: The object can not be deserialized. Description is missing!'.format(uuid))
-        if 'variables' not in dictionary:
-            raise SerialisationException('{}: The object can not be deserialized. List of variables is missing!'.format(uuid))
-        if 'parameters' not in dictionary:
-            raise SerialisationException('{}: The object can not be deserialized. List of parameters is missing!'.format(uuid))
-        if 'functions' not in dictionary:
-            raise SerialisationException('{}: The object can not be deserialized. List of functions is missing!'.format(uuid))
-        if 'objects' not in dictionary:
-            raise SerialisationException('{}: The object can not be deserialized. List of objects is missing!'.format(uuid))
-
-        try:
-            variables = []
-            for var in dictionary['variables']:
-                if mapping is not None:
-                    submapping = mapping[var["uuid"]] if var['uuid'] in mapping else None
-                    variables += [Variable.deserialize(var,submapping)]
-                else:
-                    variables += [Variable.deserialize(var)]
-        except Exception as e:
-            raise SerialisationException('{}: A variable of the object can not be deserialized. {}'.format(uuid, e))
-        try:
-            parameters = []
-            for par in dictionary['parameters']:
-                if mapping is not None:
-                    submapping = mapping[par["uuid"]] if par['uuid'] in mapping else None
-                    parameters += [Parameter.deserialize(par, submapping)]
-                else:
-                    parameters += [Parameter.deserialize(par)]
-        except Exception as e:
-            raise SerialisationException('{}: A parameter of the object can not be deserialized. {}'.format(uuid, e))
-        try:
-            functions = []
-            for func in dictionary['functions']:
-                if mapping is not None:
-                    submapping = mapping[func["uuid"]] if func['uuid'] in mapping else None
-                    functions += [Function.deserialize(func, submapping)]
-                else:
-                    functions += [Function.deserialize(func)]
-        except Exception as e:
-            raise SerialisationException('{}: A function of the object can not be deserialized. {}'.format(uuid, e))
-        try:
-            objects = []
-            for obj in dictionary['objects']:
-                if mapping is not None:
-                    submapping = mapping[obj["uuid"]] if obj['uuid'] in mapping else None
-                    objects += [Object.deserialize(obj, submapping, filepath)]
-                else:
-                    objects += [Object.deserialize(obj, filepath=filepath)]
-        except Exception as e:
-            raise SerialisationException('{}: An object of the object can not be deserialized. {}'.format(uuid, e))
-        try:
-            ontology = dictionary['ontology'] if 'ontology' in dictionary else None
-            return Object(dictionary['uuid'], dictionary['name'], dictionary['description'], functions, variables, parameters, objects, mapping,
-                          ontology)
-        except Exception as e:
-            raise SerialisationException('{}: The object can not be deserialized. {}'.format(uuid, e))
-
-    def write(self, filename: str):
-        if filename[-5:] != ".json":
-            raise Exception('{} is not a json file!'.format(filename))
-
-        model_dict = self.serialize(['all'])
-
-        f = open(filename, "w")
-        f.write(json.dumps(model_dict))
-        f.close()
-
-    def update(self, element: Union['Object', Function, Variable, Parameter]):
-        if isinstance(element, Object):
-            for i, o in enumerate(self._objects):
-                if o.uuid == element.uuid:
-                    self._objects[i] = element
-                    return
-            # self._objects.append(element)
-        else:
-            raise Exception("Wrong type updating element on existing model!")
-
-    def add(self, uuid: str, class_name: str, json_file: str, *args, **kwargs):
-        if uuid[:3] == 'OBJ':
-            if uuid not in [o.uuid for o in self._objects]:
-                try:
-                    __import__(class_name)
-                    implementation = getattr(sys.modules[class_name], class_name)(*args, **kwargs)
-                    mapping = docstring_parser.parse_docstrings_for_soil(implementation)
-                    self._objects += [Object.load(json_file, mapping)]
-                    if self._implementation_add is not None:
-                        self._implementation_add(implementation)
-                except Exception as e:
-                    raise DeviceException('Can not add object with UUID {}. {}'.format(uuid, e), predecessor=e)
-            else:
-                raise UserException('Object has already a child with UUID {}.'.format(uuid))
-        else:
-            raise UserException('UUID {} is not of the UUID of an object.'.format(uuid))
-
-    def remove(self, uuid: str, *args, **kwargs):
-        for o in self._objects:
-            if o.uuid == uuid:
-                if self._implementation_remove is not None:
-                    try:
-                        self._implementation_remove(*args, **kwargs)
-                    except Exception as e:
-                        raise DeviceException(str(e), predecessor=e)
-                self._objects.remove(o)
-                return
-        raise ChildNotFoundException('{}: Child {} not found!'.format(self.uuid, uuid))
-
-    @staticmethod
-    def load(file: Union[str, dict], implementation: Any) -> 'Object':
-        if isinstance(file, str):
-            if not os.path.isfile(file):
-                raise Exception('There is no file named {}!'.format(file))
-            if file[-5:] != ".json":
-                raise Exception('{} is not a json file!'.format(file))
-            with open(file, 'r') as f:
-                model_dict = json.load(f)
-            return Object.deserialize(model_dict, implementation, os.path.dirname(file))
-        elif isinstance(file, dict):
-            return Object.deserialize(file, implementation)
-        else:
-            raise Exception('Given file is not a name of a json-file nor a json-like dictionary.')
\ No newline at end of file
diff --git a/src/soil/parameter.py b/src/soil/parameter.py
index 24f0f33e71d0b92fc109e74687e35c77fccd1ead..5ba81591dac95cc18539ae0be57e8ce9685b8096 100644
--- a/src/soil/parameter.py
+++ b/src/soil/parameter.py
@@ -1,47 +1,48 @@
 import asyncio
 import inspect
-from typing import Dict
+from typing import Dict, Any, List
 from wzl.utilities import root_logger
 
-from ..utils.constants import HTTP_GET
+from .datatype import Datatype
+from .device import Device
+from ..utils.const import HTTP_GET, Range
 from ..utils.error import DeviceException, SerialisationException
 from .error import ReadOnlyException
-from .figure import Figure
+from .variable import Variable
 
 logger = root_logger.get(__name__)
 
 
-class Parameter(Figure):
+class Parameter(Variable):
 
-    def __init__(self, uuid, name, description, datatype, dimension, range, value, getter=None, setter=None, ontology: str = None):
-        Figure.__init__(self, uuid, name, description, datatype, dimension, range, value, getter, ontology)
+    def __init__(self, uuid: str, name: str, description: str, datatype: Datatype, dimension: List[int], range: Range,
+                 constant: bool, value: Any, unit: str = None, ontology: str = None, device: Device = None):
+        Variable.__init__(self, uuid, name, description, datatype, dimension, range, value, unit, ontology, device)
         if uuid[:3] not in ['PAR', 'ARG', 'RET']:
-            raise Exception('{}: The UUID must start with PAR, ARG or RET!'.format(uuid))
-        if setter is not None and not callable(setter):
-            raise TypeError("{}: The setter of the variable must be callable!".format(uuid))
-        self._setter = setter
+            raise Exception(f'{uuid}: The UUID must start with PAR, ARG or RET!')
+        self._constant = constant
 
-    def __setitem__(self, key: str, value):
+    @property
+    def constant(self) -> bool:
+        return self._constant
+
+    def set_value(self, value: Any):
+        self._value = value
+
+    def __setitem__(self, key: str, value: Any):
         """
         Setter - Method
         If key is "value" datatype, dimension and range is checked for correctness.
         :param key: sets the value of the attribute with name 'item' to the provided value.
         :param value: value to be set
         """
-        if key == "value":
-            Figure.check_all(self._datatype, self._dimension, self._range, value)
-            # self._timestamp, value = self._implementation()
+        if key == 'value':
+            if self.constant:
+                raise Exception(f'{self.uuid}: The parameter is a constant. Value can not be changed!')
+
+            Variable.check_all(self._datatype, self._dimension, self._range, value)
             try:
-                if inspect.iscoroutinefunction(self.set):
-                    try:
-                        loop = asyncio.get_running_loop()
-                    except:
-                        loop = asyncio.get_event_loop()
-                    loop.run_until_complete(self.set(value))
-                    self._value = value
-                else:
-                    self.set(value)
-                    self._value = value
+                self.set_value(value)
             except Exception as e:
                 raise DeviceException(str(e), predecessor=e)
         else:
@@ -91,32 +92,17 @@ class Parameter(Figure):
         uuid = dictionary['uuid']
         if uuid[:3] not in ['PAR', 'ARG', 'RET']:
             raise SerialisationException(
-                'The Parameter can not be deserialized. The UUID must start with PAR, ARG or RET, but actually starts with {}!'.format(uuid[:3]))
-        if 'name' not in dictionary:
-            raise SerialisationException('{}: The parameter can not be deserialized. Name is missing!'.format(uuid))
-        if 'description' not in dictionary:
-            raise SerialisationException('{}: The parameter can not be deserialized. Description is missing!'.format(uuid))
-        if 'datatype' not in dictionary:
-            raise SerialisationException('{}: The parameter can not be deserialized. Datatype is missing!'.format(uuid))
-        if 'dimension' not in dictionary:
-            raise SerialisationException('{}: The parameter can not be deserialized. Dimension is missing!'.format(uuid))
-        if 'value' not in dictionary:
-            raise SerialisationException('{}: The parameter can not be deserialized. Value is missing!'.format(uuid))
-        if 'range' not in dictionary:
-            raise SerialisationException('{}: The parameter can not be deserialized. Range is missing!'.format(uuid))
+               f'The Parameter can not be deserialized. The UUID must start with PAR, ARG or RET, but actually starts with {uuid[:3]}!')
+
+        # check if all required attributes are present
+        for key in ['name', 'description', 'datatype', 'dimension', 'range', 'value']:
+            if key not in dictionary:
+                raise SerialisationException(f'{uuid}: The parameter can not be deserialized. {key.capitalize()} is missing!')
+
         try:
-            # create Parameter
-            getter = implementation['getter'] if implementation is not None else None
-            setter = implementation['setter'] if implementation is not None else None
             ontology = dictionary['ontology'] if 'ontology' in dictionary else None
-            return Parameter(dictionary['uuid'], dictionary['name'], dictionary['description'], dictionary['datatype'], dictionary['dimension'],
-                             dictionary['range'], dictionary['value'], getter, setter, ontology)
+            unit = dictionary['unit'] if 'unit' in dictionary else None
+            return Parameter(dictionary['uuid'], dictionary['name'], dictionary['description'], dictionary['datatype'],
+                             dictionary['dimension'], dictionary['range'], dictionary['value'], unit, ontology)
         except Exception as e:
-            raise SerialisationException('{}: The variable can not be deserialized. {}'.format(uuid, e))
-
-    @property
-    def set(self):
-        if self._setter is not None:
-            return self._setter
-        else:
-            raise ReadOnlyException(self._uuid, self._name)
+            raise SerialisationException(f'{uuid}: The parameter can not be deserialized. {e}')
diff --git a/src/soil/stream.py b/src/soil/stream.py
index c6064696a9912080c112a0d63de05f4a29f5a079..522b8bc391e2f5fbdd82450eacd0a043d3edbfb1 100644
--- a/src/soil/stream.py
+++ b/src/soil/stream.py
@@ -58,7 +58,7 @@ class FixedJob(Job):
         return self._interval
 
 
-class ConfigurableJob(Job):
+class DynamicJob(Job):
     """
     Works exactly as a Job, despite interval is a callable which returns an integer value, used for determining delay between two job executions.
     """
@@ -112,9 +112,24 @@ class StreamScheduler(ABC):
         if start_immediately:
             self._update()
 
-    @abstractmethod
     def _process_job(self, job):
-        ...
+        if isinstance(job, UpdateJob):
+            if job.updated:
+                for publisher in self._publishers:
+                    publisher.publish(job.topic, json.dumps({'uuid': job.topic, 'value': job.value}), 1)
+
+        elif isinstance(job, EventJob):
+            value = job.callback()
+            event = job.event
+            if event.is_triggered(value):
+                event.trigger(value)
+                for publisher in self._publishers:
+                    publisher.publish('events/' + job.topic, json.dumps(event.serialize()), 1)
+
+        else:
+            ret = {'uuid': job.topic, 'value': job.callback()}
+            for publisher in self._publishers:
+                publisher.publish(job.topic, json.dumps(ret), 1)
 
     def start(self):
         self._running = True
@@ -158,38 +173,3 @@ class StreamScheduler(ABC):
 
             self._loop.call_later((next - now).seconds + (next - now).microseconds / 1e6, self._update)
 
-
-class MessageScheduler(StreamScheduler):
-
-    def __init__(self, loop, schedule: List[Job], publishers: List[MQTTPublisher] = None, start_immediately: bool = False):
-        StreamScheduler.__init__(self, loop, schedule, publishers, start_immediately or len(schedule) > 0)
-
-    def _process_job(self, job):
-        ret = {'uuid': job.topic, 'value': job.callback()}
-        for publisher in self._publishers:
-            publisher.publish(job.topic, json.dumps(ret), 1)
-
-
-class EventScheduler(StreamScheduler):
-
-    def __init__(self, loop, schedule: List[Job], publishers: List[MQTTPublisher] = None, start_immediately: bool = False):
-        StreamScheduler.__init__(self, loop, schedule, publishers, start_immediately or len(schedule) > 0)
-
-    def _process_job(self, job):
-        value = job.callback()
-        event = job.event
-        if event.is_triggered(value):
-            event.trigger(value)
-            for publisher in self._publishers:
-                publisher.publish('events/' + job.topic, json.dumps(event.serialize()), 1)
-
-
-class UpdateScheduler(StreamScheduler):
-
-    def __init__(self, loop, schedule: List[UpdateJob], publishers: List[MQTTPublisher] = None, start_immediately: bool = False):
-        StreamScheduler.__init__(self, loop, schedule, publishers, start_immediately or len(schedule) > 0)
-
-    def _process_job(self, job: UpdateJob):
-        if job.updated:
-            for publisher in self._publishers:
-                publisher.publish(job.topic, json.dumps({'uuid': job.topic, 'value': job.value}), 1)
diff --git a/src/soil/variable.py b/src/soil/variable.py
index 836796635bb2a972b953353f8cc17a5404d56eb7..01bf567df38f5e0aced15cec3b40a201b0f1ffe4 100644
--- a/src/soil/variable.py
+++ b/src/soil/variable.py
@@ -1,46 +1,104 @@
-from deprecated import deprecated
-from typing import Dict
+import datetime
+import enum
+import time
+from abc import ABC
+from typing import Any, List, Union, Dict, Tuple
+
+import strict_rfc3339 as rfc3339
 from wzl.utilities import root_logger
 
-from ..utils.constants import HTTP_GET
-from ..utils.error import SerialisationException
-from .figure import Figure
+from utils.const import Range
+from .datatype import Datatype
+from .device import Device
+from .stream import Job
+
+
+from .element import Element
+from .error import DimensionException, RangeException, TypeException, ElementNotExistException
+from ..utils.error import DeviceException
 
 logger = root_logger.get(__name__)
 
 
-@deprecated(version='6.0.0', reason='"Variable" has been renamed to "Measurement" to be consistent with VIM and published articles.')
-class Variable(Figure):
+def parse_time(time_rfc3339: Union[str, List]):
+    if isinstance(time_rfc3339, list):
+        return [parse_time(e) for e in time_rfc3339]
+    else:
+        if time_rfc3339 is None or time_rfc3339 == "":
+            return None
+        timestamp = rfc3339.rfc3339_to_timestamp(time_rfc3339)
+        date = list(time.gmtime(int(timestamp)))[:6]
+        return datetime.datetime(*date, int((timestamp - int(timestamp)) * 1e6))
+
+
+def serialize_time(time):
+    timestamp = datetime.datetime.timestamp(time)
+    return rfc3339.timestamp_to_rfc3339_localoffset(timestamp)
+
 
-    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)
+class Variable(Element, ABC):
+    def __init__(self, uuid: str, name: str, description: str, datatype: Datatype, dimension: List, range: Range,
+                 value: Any, unit: str = None, ontology: str = None, device: Device = None):
+        Element.__init__(self, uuid, name, description, ontology, device)
+        if not isinstance(datatype, Datatype):
+            raise Exception(f'{uuid} - InitializationError: Datatype must be an object of class Datatype.')
+        Variable.check_all(datatype, dimension, range, value)
+        self._datatype = datatype
+        self._dimension = dimension
+        self._range = range
+        if datatype in [Datatype.INT, Datatype.FLOAT] and unit is None:
+            raise Exception(
+                f'{uuid} - InitializationError: If the datatype is integer or float a unit must be specified!')
         self._unit = unit
-        self._covariance = None  # TODO init
-        self._uncertainty = None  # TODO init
-        self._timestamp = None
-        self._nonce = nonce
+        if datatype == 'time':
+            self._value = parse_time(value)
+        else:
+            self._value = value
+
+        self._jobs: List[Job] = []
 
     @property
-    def unit(self):
-        return self._unit
+    def datatype(self) -> Datatype:
+        return self._datatype
 
     @property
-    def covariance(self):
-        return self._covariance
+    def dimension(self) -> List[int]:
+        return self._dimension
 
     @property
-    def uncertainty(self):
-        return self._uncertainty
+    def range(self) -> Range:
+        return self._range
 
     @property
-    def timestamp(self):
-        return self._timestamp
+    def unit(self) -> str:
+        return self._unit
 
     @property
-    def nonce(self):
-        return self._nonce
+    def jobs(self) -> List[Job]:
+        return self.jobs
+
+    def get_value(self) -> Any:
+        return self._value
 
-    def __getitem__(self, item: str, method=HTTP_GET):
+    def add_job(self, job: Job):
+        self._jobs += [job]
+
+    def remove_job(self, index: int):
+        del self._jobs[index]
+
+    def get_element(self, fqid: Union[str, List[str]]) -> Element:
+        if not fqid:
+            raise ValueError(f'The fqid must not be empty!')
+
+        if isinstance(fqid, str):
+            fqid = fqid.split('/')
+
+        if fqid[0] == self.uuid and len(fqid) == 1:
+            return self
+
+        raise ElementNotExistException(f'An element with the uuid {"/".join(fqid)} does not exist.')
+
+    def __getitem__(self, item: str) -> Any:
         """
         Getter-Method.
         According to the given key the method returns the value of the corresponding attribute.
@@ -48,90 +106,208 @@ class Variable(Figure):
         :param method: ???
         :return: the value of the attribute indicated by 'item'.
         """
-        if item == "unit":
+        if item == 'datatype':
+            return self.datatype
+        if item == 'value':
+            try:
+                value = self.get_value()
+                if self._datatype == 'time':
+                    value = serialize_time(value)
+                elif self._datatype == 'enum':
+                    value = str(value)
+            except Exception as e:
+                raise DeviceException(f'Could not provide value of Measurement/Parameter {self.uuid}: {e}',
+                                      predecessor=e)
+
+            Variable.check_all(self._datatype, self._dimension, self._range, value)
+            return value
+        if item == 'dimension':
+            return self.dimension
+        if item == 'range':
+            return self.range
+        if item == 'unit':
             return self._unit
-        if item == 'nonce':
-            return self._nonce
-        if item == 'covariance':
-            return self._covariance
-        if item == 'uncertainty':
-            return self._uncertainty
-        if item == 'timestamp':
-            return self._timestamp
         if item == []:
             return self
-        return super().__getitem__(item, method)
+        return super().__getitem__(item)
 
-    def __setitem__(self, key: str, value):
+    def __setitem__(self, key: str, value: Any):
         """
         Setter - Method
         If key is "value" datatype, dimension and range is checked for correctness.
         :param key: sets the value of the attribute with name 'item' to the provided value.
         :param value: value to be set
         """
-        if key in ['value', 'timestamp', 'covariance', 'uncertainty']:
-            raise KeyError('The {} attribute of a measurement can not be set manually!'.format(key))
-        elif key == "nonce":
-            self._nonce = self._nonce
-        elif key == 'unit':
-            self._unit = self._unit
-        else:
-            super().__setitem__(key, value)
+        if key in ['datatype', 'range', 'unit', 'dimension', 'value']:
+            raise Exception(f'{self.uuid}: The {key} can not be changed externally during runtime.')
+        super().__setitem__(key, value)
 
-    def serialize(self, keys: [str], method=HTTP_GET):
+    def serialize(self, keys: List[str]) -> Dict[str, Any]:
         """
-        Serializes an object of type Variable into a JSON-like dictionary.
+        Serializes an object of type Figure into a JSON-like dictionary.
         :param keys: All attributes given in the "keys" array are serialized.
         :param method: ???
         :return: a dictionary having all "keys" as keys and the values of the corresponding attributes as value.
         """
         # list is empty provide all attributes of the default-serialization
         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 = {}
+            keys = ['uuid', 'name', 'description', 'datatype', 'value', 'dimension', 'range', 'ontology']
         # get all attribute values
+        dictionary = {}
         for key in keys:
-            value = self.__getitem__(key, method)
-            # in case of timestamp convert into RFC3339 string
-            if key == 'timestamp' or (key == 'value' and self._datatype == 'time'):
-                value = value.isoformat() + 'Z' if value is not None else ""
+            value = self.__getitem__(key)
             dictionary[key] = value
         return dictionary
 
     @staticmethod
-    def deserialize(dictionary: Dict, implementation=None):
+    def check_dimension(dimension: List[int], value: Any):
         """
-        Takes a JSON-like dictionary, parses it, performs a complete correctness check and returns an object of type Figure with the
-         values provided in the dictionary, if dictionary is a valid serialization of a Figure.
-        :param dictionary: serialized variable
-        :param implementation: implementation wrapper object,
-        :return: an object of type Figure
+        Checks whether the given value is of given dimension
+        :param dimension: the dimension the value provided by "value" should have
+        :param value: value to be checked for the dimension
         """
-        # check if all required attributes are present
-        if 'uuid' not in dictionary:
-            raise SerialisationException('The variable can not be deserialized. UUID is missing!')
-        uuid = dictionary['uuid']
-        if 'name' not in dictionary:
-            raise SerialisationException('{}: The variable can not be deserialized. Name is missing!'.format(uuid))
-        if 'description' not in dictionary:
-            raise SerialisationException('{}: The variable can not be deserialized. Description is missing!'.format(uuid))
-        if 'datatype' not in dictionary:
-            raise SerialisationException('{}: The variable can not be deserialized. Datatype is missing!'.format(uuid))
-        if 'dimension' not in dictionary:
-            raise SerialisationException('{}: The variable can not be deserialized. Dimension is missing!'.format(uuid))
-        if 'value' not in dictionary:
-            raise SerialisationException('{}: The variable can not be deserialized. Value is missing!'.format(uuid))
-        if 'range' not in dictionary:
-            raise SerialisationException('{}: The variable can not be deserialized. Range is missing!'.format(uuid))
-        if 'unit' not in dictionary:
-            raise SerialisationException('{}: The variable can not be deserialized. Unit is missing!'.format(uuid))
+        # dimension of undefined value must not be checked => valid
+        if value is None:
+            return
+
+        if not dimension:
+            if Variable.is_scalar(value):
+                # base case 1: dimension is empty and variable is a scalar => valid
+                return
+            else:
+                # base case 2: dimension is empty and variable is not a scalar => not valid
+                raise DimensionException('Figure of dimension 0 can not be of type list!')
+
         try:
-            ontology = dictionary['ontology'] if 'ontology' in dictionary else None
-            return Variable(dictionary['uuid'], dictionary['name'], dictionary['description'], dictionary['datatype'], dictionary['dimension'],
-                            dictionary['range'], implementation, dictionary['unit'], ontology)
-        except Exception as e:
-            raise SerialisationException('{}: The variable can not be deserialized. {}'.format(uuid, e))
+            # base case 3: current dimension is fixed size "x" and length of the value is not "x" => not valid
+            if dimension[0] != 0 and len(value) != dimension[0]:
+                raise DimensionException('Dimension of data does not match dimension of variable!')
+        except TypeError as te:
+            raise DimensionException(str(te))
+        # recursion case
+        # at this point value is guaranteed to be of type list
+        # => recursively check the dimension of each "subvalue"
+        for v in value:
+            try:
+                Variable.check_dimension(dimension[1:], v)
+            except DimensionException as e:
+                raise e
+
+    @staticmethod
+    def check_type(datatype: Datatype, value: Any):
+        """
+        Checks if the given value is of the correct datatype. If value is not a scale, it checks all "subvalues" for correct datatype.
+        :param datatype: datatype the value provided by "value" should have
+        :param value: value to be checked for correct datatype
+        """
+        # datatype of undefined value must not be checked => valid
+        if value is None:
+            return
+        # base case: value is a scalar
+        if Variable.is_scalar(value):
+            # check if the type of value corresponds to given datatype
+            if datatype == Datatype.BOOL and not isinstance(value, bool):
+                raise TypeException(f'Boolean field does not match non-boolean value {value}!')
+            elif datatype == Datatype.INT and not isinstance(value, int):
+                raise TypeException(f'Integer field does not match non-integer value {value}!')
+            elif datatype == Datatype.FLOAT and not isinstance(value, float) and not isinstance(value, int):
+                raise TypeException(f'Floating point field does not match non-double value {value}!')
+            elif datatype == Datatype.STRING and not isinstance(value, str):
+                raise TypeException(f'String field does not match non-string value {value}!')
+            elif datatype == Datatype.ENUM and not isinstance(value, str):  # TODO fix type check
+                raise TypeException(f'Enum field {value} must be a string!')
+            elif datatype == Datatype.TIME and not isinstance(value, str):  # TODO fix type check
+                raise TypeException(f'Time field {value} must be string.')
+            elif datatype == Datatype.TIME and isinstance(value, str):  # TODO fix type check
+                if value != '' and value is not None and not rfc3339.validate_rfc3339(value):
+                    raise TypeException(f'Value is not a valid RFC3339-formatted timestring: {value}')
+            else:
+                raise TypeException(f'Unknown type descriptor: {datatype}')
+        else:
+            # recursion case: value is an array or matrix => check datatype of each 'subvalue' recursively
+            for v in value:
+                try:
+                    Variable.check_type(datatype, v)
+                except TypeException as e:
+                    raise e
+
+    @staticmethod
+    def check_range(datatype: Datatype, range: Range, value: Any):
+        """
+        Checks if the given value is within provided range (depending on the given datatype)
+
+        IMPORTANT: It is not checked whether the value is of correct type. If the type of value is not correct, the result
+        of check_range is not meaningful! To get expressive result check datatype before calling check_range!
+
+        :param datatype: datatype of the value
+        :param range: the range the value should be within
+        :param value: value to be checked for range
+
+        For all datatypes (except "bool" and "enum")the range specification is of the following form: [lower bound (LB), upper bound (UB)]
+        If LB or UB are None the value is unrestricted to the bottom or top, respectively.
+        In case of "int", "double" and "time" the interpretation of LB and UB is straightforward.
+        For "string" LB and UB restrict the length of the string. (If LB is given as None, 0 is the natural LB of cause)
+        "bool" is naturally bounded to "True" and "False", thus the range is not checked.
+        In case of "enum" the range contains the list of all possible values.
+        """
+        # if the list is empty, all values are possible
+        if not range:
+            if datatype == Datatype.ENUM:  # TODO fix range check of enums
+                raise RangeException('A value of type enum must provide a range with possible values!')
+            else:
+                return
+        # base case: value is scalar => check if the value is in range
+        if Variable.is_scalar(value):
+            # bool is not checked, since there is only true and false
+            if datatype == Datatype.BOOL:
+                return
+            elif datatype == Datatype.INT and value is not None:
+                if range[0] is not None and value < range[0]:
+                    raise RangeException(f'Integer value {value} is smaller than lower bound {range[0]}!')
+                elif range[1] is not None and value > range[1]:
+                    raise RangeException(f'Integer value {value} is higher than upper bound {range[1]}!')
+            elif datatype == Datatype.FLOAT and value is not None:
+                if range[0] is not None and value < range[0]:
+                    raise RangeException(f'Double value {value} is smaller than lower bound {range[0]}!')
+                elif range[1] is not None and value > range[1]:
+                    raise RangeException(f'Double value {value} is higher than upper bound {range[1]}!')
+            elif datatype == Datatype.STRING and value is not None:
+                if range[0] is not None and len(value) < range[0]:
+                    raise RangeException(f'String value {value} is too short. Minimal required length is {range[0]}!')
+                elif range[1] is not None and len(value) > range[1]:
+                    raise RangeException(f'String value {value} is too long. Maximal allowed length is {range[1]}!')
+            elif datatype == Datatype.ENUM and value is not None:  # TODO fix range check of enums
+                if value not in range:
+                    raise RangeException(f'Enum value {value} is not within the set of allowed values!')
+            elif datatype == Datatype.TIME and value is not None and value != '':
+                if range[0] is not None:
+                    if not rfc3339.validate_rfc3339(range[0]):
+                        raise TypeException(
+                            f'Can not check range of time value. Lower bound {range[0]} is not a valid RFC3339 timestring.')
+                    if parse_time(value) < parse_time(range[0]):
+                        raise RangeException(
+                            f'Time value {parse_time(value)} is smaller than lower bound {parse_time(range[0])}!')
+                elif range[1] is not None:
+                    if not rfc3339.validate_rfc3339(range[1]):
+                        raise TypeException(
+                            'Can not check range of time value. Upper bound {} is not a valid RFC3339 timestring.')
+                    if parse_time(value) > parse_time(range[1]):
+                        raise RangeException(
+                            f'Time value {parse_time(value)} is greater than upper bound {parse_time(range[1])}!')
+        else:
+            # recursion case: value is an array or matrix => check range of each "subvalue" recursively
+            for v in value:
+                try:
+                    Variable.check_range(datatype, range, v)
+                except RangeException as e:
+                    raise e
+
+    @staticmethod
+    def is_scalar(value: Any) -> bool:
+        return not isinstance(value, list)
+
+    @staticmethod
+    def check_all(datatype: Datatype, dimension: List[int], range: Range, value: Any):
+        Variable.check_type(datatype, value)
+        Variable.check_dimension(dimension, value)
+        Variable.check_range(datatype, range, value)
diff --git a/src/utils/const.py b/src/utils/const.py
new file mode 100644
index 0000000000000000000000000000000000000000..553b2d21bb56500526c064f2af5fe8de8ae6445d
--- /dev/null
+++ b/src/utils/const.py
@@ -0,0 +1,8 @@
+import enum
+from typing import Union, Tuple
+
+HTTP_GET = 0
+HTTP_OPTIONS = 1
+BASE_UUID_PATTERN = r'[0-9A-Za-z-_]{3,}'
+
+Range = Union[Tuple[int, int], Tuple[str, str], enum.Enum]
\ No newline at end of file
diff --git a/src/utils/constants.py b/src/utils/constants.py
deleted file mode 100644
index 8ac340e21bace0baf074d4049a6729365a00868b..0000000000000000000000000000000000000000
--- a/src/utils/constants.py
+++ /dev/null
@@ -1,3 +0,0 @@
-HTTP_GET = 0
-HTTP_OPTIONS = 1
-BASE_UUID_PATTERN = r'[0-9A-Za-z-_]{3,}'
\ No newline at end of file
diff --git a/test/devices/experimental_lasertracker/Lasertracker.json b/test/devices/experimental_lasertracker/Lasertracker.json
new file mode 100644
index 0000000000000000000000000000000000000000..c32e28f2b938baddc24f6eb359210e650523ba43
--- /dev/null
+++ b/test/devices/experimental_lasertracker/Lasertracker.json
@@ -0,0 +1,355 @@
+{
+  "components": [
+    {
+      "components": [
+        {
+          "components": [],
+          "functions": [
+            {
+              "arguments": [
+                {
+                  "range": [
+                    null,
+                    null
+                  ],
+                  "datatype": "double",
+                  "dimension": [],
+                  "value": 0,
+                  "unit": "C81",
+                  "uuid": "ARG-Azimuth",
+                  "name": "Azimuth",
+                  "description": "Azimuth angle the laser tracker head is jogged."
+                },
+                {
+                  "range": [
+                    null,
+                    null
+                  ],
+                  "datatype": "double",
+                  "dimension": [],
+                  "value": 0,
+                  "unit": "C81",
+                  "uuid": "ARG-Elevation",
+                  "name": "Elevation",
+                  "description": "Elevation angle the laser tracker head is jogged."
+                }
+              ],
+              "returns": [],
+              "uuid": "FUN-Jog",
+              "name": "Jog",
+              "description": "Jogs the tracker head by the given angles for the azimuth and elevation."
+            },
+            {
+              "arguments": [
+                {
+                  "range": [
+                    null,
+                    null
+                  ],
+                  "datatype": "double",
+                  "dimension": [
+                    3
+                  ],
+                  "value": [
+                    0,
+                    0,
+                    0
+                  ],
+                  "unit": "MTR",
+                  "uuid": "ARG-Position",
+                  "name": "Position",
+                  "description": "Position to which the laser tracker should point."
+                }
+              ],
+              "returns": [],
+              "uuid": "FUN-PointTo",
+              "name": "PointTo",
+              "description": "Moves the tracker head so that the laser points to the specified position."
+            }
+          ],
+          "measurements": [
+            {
+              "range": [
+                null,
+                null
+              ],
+              "datatype": "double",
+              "dimension": [
+                3
+              ],
+              "value": [
+                0,
+                0,
+                0
+              ],
+              "unit": "MTR",
+              "uuid": "MEA-Position",
+              "name": "Position",
+              "description": "Most recently dispatched measured position."
+            },
+            {
+              "range": [
+                null,
+                null
+              ],
+              "datatype": "double",
+              "dimension": [
+                4
+              ],
+              "value": [
+                0,
+                0,
+                0,
+                0
+              ],
+              "unit": "NONE",
+              "uuid": "MEA-Quaternion",
+              "name": "Quaternion",
+              "description": "Most recently dispatched measured orientation as quaternion, if available."
+            },
+            {
+              "range": [
+                null,
+                null
+              ],
+              "datatype": "double",
+              "dimension": [],
+              "value": 0,
+              "unit": "C81",
+              "uuid": "MEA-Azimuth",
+              "name": "Azimuth",
+              "description": "Current position of azimuth rotation encoder in Radian."
+            },
+            {
+              "range": [
+                null,
+                null
+              ],
+              "datatype": "double",
+              "dimension": [],
+              "value": 0,
+              "unit": "C81",
+              "uuid": "MEA-Elevation",
+              "name": "Elevation",
+              "description": "Current position of elevation rotation encoder."
+            },
+            {
+              "range": [
+                null,
+                null
+              ],
+              "datatype": "double",
+              "dimension": [],
+              "value": 0,
+              "unit": "MTR",
+              "uuid": "MEA-Distance",
+              "name": "Distance",
+              "description": "Measured distance to the currently activate target."
+            },
+            {
+              "range": [
+                "False",
+                "True"
+              ],
+              "datatype": "bool",
+              "dimension": [
+                2,
+                3
+              ],
+              "value": [
+                [
+                  true,
+                  true,
+                  true
+                ],
+                [
+                  true,
+                  true,
+                  true
+                ]
+              ],
+              "unit": "NONE",
+              "uuid": "MEA-Online",
+              "name": "Online",
+              "description": "Some description..."
+            }
+          ],
+          "parameters": [
+            {
+              "constant": true,
+              "range": [
+                "OK",
+                "WARNING",
+                "ERROR",
+                "MAINTENANCE"
+              ],
+              "datatype": "enum",
+              "dimension": [],
+              "value": "ERROR",
+              "unit": "NONE",
+              "uuid": "PAR-State",
+              "name": "State",
+              "description": "Reflects the current state of the target. If logged in: OK. If not stable: WARNING. If lost: ERROR."
+            },
+            {
+              "constant": true,
+              "range": [
+                0,
+                null
+              ],
+              "datatype": "string",
+              "dimension": [],
+              "value": "",
+              "unit": "NONE",
+              "uuid": "PAR-Calibration",
+              "name": "Calibration",
+              "description": "Unique identifier that can be used to retrieve a corresponding calibration certificate."
+            },
+            {
+              "constant": false,
+              "range": [
+                null,
+                null
+              ],
+              "datatype": "int",
+              "dimension": [],
+              "value": 0,
+              "unit": "NONE",
+              "uuid": "PAR-Interval",
+              "name": "Interval",
+              "description": "Some description..."
+            }
+          ],
+          "uuid": "COM-Base",
+          "name": "Base",
+          "description": "Represents a base station in a distributed system. "
+        }
+      ],
+      "functions": [],
+      "measurements": [],
+      "parameters": [],
+      "uuid": "COM-BaseStations",
+      "name": "Base Stations",
+      "description": "Object acting as a list of base stations of the metrology system. "
+    },
+    {
+      "components": [
+        {
+          "uuid": "COM-Home-Target",
+          "name": "Home Target",
+          "description": "Represents an individual mobile entity ",
+          "parameters": [
+            {
+              "uuid": "PAR-State",
+              "value": "ERROR"
+            },
+            {
+              "uuid": "PAR-Mode",
+              "value": "Continuous"
+            },
+            {
+              "uuid": "PAR-Type",
+              "value": "SMR"
+            },
+            {
+              "uuid": "PAR-Calibration",
+              "value": ""
+            }
+          ],
+          "file": "./MobileEntities/Target.json"
+        }
+      ],
+      "functions": [],
+      "measurements": [],
+      "parameters": [],
+      "uuid": "COM-MobileEntities",
+      "name": "Mobile Entities",
+      "description": "Object acting as a list of mobile entities in the metrology system."
+    }
+  ],
+  "functions": [
+    {
+      "arguments": [],
+      "returns": [],
+      "uuid": "FUN-Reset",
+      "name": "Reset",
+      "description": "Resets the device into the state like directly after start-up."
+    },
+    {
+      "arguments": [],
+      "returns": [],
+      "uuid": "FUN-Shutdown",
+      "name": "Shutdown",
+      "description": "Gracefully shutdown the device."
+    }
+  ],
+  "measurements": [],
+  "parameters": [
+    {
+      "constant": false,
+      "range": [
+        "OK",
+        "WARNING",
+        "ERROR",
+        "MAINTENANCE"
+      ],
+      "datatype": "enum",
+      "dimension": [],
+      "value": "OK",
+      "unit": "NONE",
+      "uuid": "PAR-State",
+      "name": "State",
+      "description": "The current state of the device."
+    },
+    {
+      "constant": true,
+      "range": [
+        null,
+        null
+      ],
+      "datatype": "string",
+      "dimension": [],
+      "value": "Laboratory for Machine Tools and Production Engineering WZL of RWTH Aachen",
+      "unit": "NONE",
+      "uuid": "PAR-Manufacturer",
+      "name": "Manufacturer",
+      "description": "Name of manufacturing company."
+    },
+    {
+      "constant": true,
+      "range": [
+        0,
+        null
+      ],
+      "datatype": "int",
+      "dimension": [],
+      "value": 1,
+      "unit": "NONE",
+      "uuid": "PAR-Version",
+      "name": "Version",
+      "description": "Incremental API-Version."
+    },
+    {
+      "constant": true,
+      "range": [
+        null,
+        null
+      ],
+      "datatype": "time",
+      "dimension": [
+        2
+      ],
+      "value": [
+        "2021-05-03T15:30:00.0000Z",
+        "2021-05-03T15:30:00.0000Z"
+      ],
+      "unit": "NONE",
+      "uuid": "PAR-Time",
+      "name": "Time",
+      "description": "Current system time."
+    }
+  ],
+  "uuid": "COM-Lasertracker",
+  "name": "Lasertracker",
+  "description": "Active coordinate measurement device based on laser interferometry for Large-Scale metrology applications."
+}
\ No newline at end of file
diff --git a/test/devices/experimental_lasertracker/MobileEntities/Target.json b/test/devices/experimental_lasertracker/MobileEntities/Target.json
new file mode 100644
index 0000000000000000000000000000000000000000..105272190780e915b002b9c21c938fa89f3cebec
--- /dev/null
+++ b/test/devices/experimental_lasertracker/MobileEntities/Target.json
@@ -0,0 +1 @@
+{"components": [], "functions": [{"arguments": [], "returns": [], "uuid": "FUN-Reset", "name": "Reset", "description": "Starts the search routine around the current direction."}, {"arguments": [], "returns": [], "uuid": "FUN-Trigger", "name": "Trigger", "description": "Trigger count measurements and set the resulting label to nonce. This function is only allowed in triggered acquisition mode."}], "measurements": [{"range": [null, null], "datatype": "double", "dimension": [3], "value": [0, 0, 0], "unit": "MTR", "uuid": "MEA-Position", "name": "Position", "description": "Most recently dispatched measured position."}, {"range": [null, null], "datatype": "double", "dimension": [4], "value": [0, 0, 0, 0], "unit": "NONE", "uuid": "MEA-Quaternion", "name": "Quaternion", "description": "Most recently dispatched measured orientation as quaternion, if available."}], "parameters": [{"constant": true, "range": ["OK", "WARNING", "ERROR", "MAINTENANCE"], "datatype": "enum", "dimension": [], "value": "ERROR", "unit": "NONE", "uuid": "PAR-State", "name": "State", "description": "Reflects the current state of the target. If logged in: OK. If not stable: WARNING. If lost: ERROR."}, {"constant": true, "range": ["Continuous", "Triggered", "External", "Idle"], "datatype": "enum", "dimension": [], "value": "Continuous", "unit": "NONE", "uuid": "PAR-Mode", "name": "Mode", "description": "Current state of the entity. In CONTINUOUS mode, values are dispatched as fast as possible. In TRIGGERED mode, values are only dispatches after a software trigger. In EXTERNAL mode, values are dispatched in accordance to an external trigger, e.g. probe or TTL. IDLE means the entity is currently not used."}, {"constant": true, "range": [null, null], "datatype": "string", "dimension": [], "value": "SMR", "unit": "NONE", "uuid": "PAR-Type", "name": "Type", "description": "System specific identifier of the target Type, e.g. SMR or Active SMR."}, {"constant": true, "range": [0, null], "datatype": "string", "dimension": [], "value": "", "unit": "NONE", "uuid": "PAR-Calibration", "name": "Calibration", "description": "Unique identifier that can be used to retrieve a corresponding calibration certificate."}, {"constant": false, "range": ["False", "True"], "datatype": "bool", "dimension": [], "value": false, "unit": "NONE", "uuid": "PAR-Locked", "name": "Locked", "description": "Some description..."}], "uuid": "COM-Target", "name": "Target", "description": "Represents an individual mobile entity "}
\ No newline at end of file
diff --git a/test/devices/experimental_lasertracker/__init__.py b/test/devices/experimental_lasertracker/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/test/devices/experimental_lasertracker/const.py b/test/devices/experimental_lasertracker/const.py
new file mode 100644
index 0000000000000000000000000000000000000000..d1cd96ac3c340e9a462d96d05892966c632a3e9f
--- /dev/null
+++ b/test/devices/experimental_lasertracker/const.py
@@ -0,0 +1,10 @@
+MQTT_USERNAME = ''
+MQTT_PASSWORD = ''
+
+# MQTT_BROKER = 'wzl-mbroker01.wzl.rwth-aachen.de'
+# MQTT_PORT = 1883
+# MQTT_VHOST = "metrology"
+
+MQTT_BROKER = 'localhost'
+MQTT_PORT = 1883
+MQTT_VHOST = ""
diff --git a/test/devices/experimental_lasertracker/device/__init__.py b/test/devices/experimental_lasertracker/device/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/test/devices/experimental_lasertracker/device/com_base.py b/test/devices/experimental_lasertracker/device/com_base.py
new file mode 100644
index 0000000000000000000000000000000000000000..e75bccf59cab7077cf98a9ba0d030ad140995679
--- /dev/null
+++ b/test/devices/experimental_lasertracker/device/com_base.py
@@ -0,0 +1,38 @@
+from typing import List
+
+from device.enums import StateEnum
+from device.mea_azimuth import MEAAzimuth
+from device.mea_distance import MEADistance
+from device.mea_elevation import MEAElevation
+from device.mea_orientation import MEAOrientation
+from device.mea_position import MEAPosition
+from device.par_calibration import PARCalibration
+from device.par_interval import PARInterval
+from device.par_state import PARState
+from soil.component import Component
+from soil.device import Device
+
+
+class COMBase(Component):
+
+    def __init__(self, device: Device, topic: str):
+        Component.__init__(self, 'COM-Base', 'Base', 'Represents a base station in a distributed system.', device=device)
+        self._measurements += [MEAPosition(device, topic)]
+        self._measurements += [MEAOrientation(device, topic)]
+        self._measurements += [MEAAzimuth(device, topic)]
+        self._measurements += [MEAElevation(device, topic)]
+        self._measurements += [MEADistance(device, topic)]
+        self._parameters += [PARState(device, topic, StateEnum.OK)]
+        self._parameters += [PARCalibration(device, topic)]
+        self._parameters += [PARInterval(device, topic)]
+        self._functions += [FUNJog(device, topic)]
+        # TODO implement
+
+    def fun_jog(self, arg_azimuth: float = 0, arg_elevation: float = 0):
+        # TODO implement
+        pass
+
+    def fun_pointto(self, arg_position: List[float] = [0, 0, 0]):
+        arg_position = [0, 0, 0] if arg_position is None else arg_position
+        # TODO implement
+        pass
diff --git a/test/devices/experimental_lasertracker/device/com_basestations.py b/test/devices/experimental_lasertracker/device/com_basestations.py
new file mode 100644
index 0000000000000000000000000000000000000000..c270557adb6ac8160149cf075a637da615f0c00e
--- /dev/null
+++ b/test/devices/experimental_lasertracker/device/com_basestations.py
@@ -0,0 +1,11 @@
+from device.com_base import COMBase
+from soil.component import Component
+from soil.device import Device
+
+
+class COMBaseStations(Component):
+
+    def __init__(self, device: Device, topic: str):
+        Component.__init__(self, 'COM-BaseStations', 'Base Stations',
+                           'Object acting as a list of mobile entities in the metrology system.', device=device)
+        self._components += [COMBase(device, f'{topic}/COM-Base')]
diff --git a/test/devices/experimental_lasertracker/device/com_lasertracker.py b/test/devices/experimental_lasertracker/device/com_lasertracker.py
new file mode 100644
index 0000000000000000000000000000000000000000..56dca361e286e0f2baf3f67db9392a45477acb09
--- /dev/null
+++ b/test/devices/experimental_lasertracker/device/com_lasertracker.py
@@ -0,0 +1,60 @@
+import datetime
+from typing import List
+
+from device.com_basestations import COMBaseStations
+from device.com_mobileentities import COMMobileEntities
+from device.enums import StateEnum
+from soil.component import Component
+
+from soil.stream import Job
+
+
+class COMLasertracker(Component):
+
+    def __init__(self, device):
+        self._device = device
+        self._par_state = StateEnum.OK
+        self._par_manufacturer = "Laboratory for Machine Tools and Production Engineering WZL of RWTH Aachen"
+        self._par_version = 1
+        self._par_time = [datetime.datetime.fromtimestamp(1620048600.0), datetime.datetime.fromtimestamp(1620048600.0)]
+        self._com_basestations = COMBaseStations(device)
+        self._com_mobileentities = COMMobileEntities(device)
+        # TODO implement
+
+    def get_jobs(self, topic: str) -> List[Job]:
+        job_list = []
+        job_list += self._com_basestations.get_jobs(f"{topic}/COM-BaseStations")
+        job_list += self._com_mobileentities.get_jobs(f"{topic}/COM-MobileEntities")
+        return job_list
+
+
+    def get_par_state(self) -> StateEnum:
+        # TODO implement
+        return self._par_state
+    
+    def set_par_state(self, par_state: StateEnum):
+        # TODO implement
+        self._par_state = par_state
+    
+    def get_par_manufacturer(self) -> str:
+        # TODO implement
+        return self._par_manufacturer
+    
+    def get_par_version(self) -> int:
+        # TODO implement
+        return self._par_version
+    
+    def get_par_time(self) -> List['datetime']:
+        # TODO implement
+        return self._par_time
+    
+    
+    def fun_reset(self):
+        # TODO implement
+        pass
+    
+    
+    def fun_shutdown(self):
+        # TODO implement
+        pass
+    
\ No newline at end of file
diff --git a/test/devices/experimental_lasertracker/device/com_mobileentities.py b/test/devices/experimental_lasertracker/device/com_mobileentities.py
new file mode 100644
index 0000000000000000000000000000000000000000..1ed738fc674631147862a2d026f6c13d1ed2a928
--- /dev/null
+++ b/test/devices/experimental_lasertracker/device/com_mobileentities.py
@@ -0,0 +1,27 @@
+from typing import Dict, List
+
+from device.com_target import COMTarget
+from device.enums import StateEnum, ModeEnum
+from soil.stream import Job
+from device.component import Component
+
+
+class COMMobileEntities(Component):
+
+    def __init__(self, device):
+        self._device = device
+        self._com_target = {}
+        self._com_target["COM-Home-Target"] = COMTarget(device, StateEnum.ERROR, ModeEnum.Continuous, "SMR", "")
+        # TODO implement
+
+    def get_jobs(self, topic: str) -> List[Job]:
+        job_list = []
+        for uuid in self._com_target:
+            job_list += self._com_target[uuid].get_jobs(f"{topic}/{uuid}")
+        return job_list
+
+    def add(self, uuid: str, child: COMTarget):
+        self._com_target[uuid] = child
+
+    def remove(self, uuid: str):
+        del self._com_target[uuid]
diff --git a/test/devices/experimental_lasertracker/device/com_target.py b/test/devices/experimental_lasertracker/device/com_target.py
new file mode 100644
index 0000000000000000000000000000000000000000..6198c4ad33a8ae18b7693fb9defd7d496a63a14b
--- /dev/null
+++ b/test/devices/experimental_lasertracker/device/com_target.py
@@ -0,0 +1,72 @@
+from typing import Dict
+
+from typing import List
+from device.enums import StateEnum, ModeEnum
+
+from soil.event import Event, EventSeverity, EventTrigger
+from soil.stream import Job, FixedJob, UpdateJob, EventJob
+from device.component import Component
+
+
+class COMTarget(Component):
+
+    def __init__(self, device, par_state: StateEnum = StateEnum.ERROR, par_mode: ModeEnum = ModeEnum.Continuous,
+                 par_type: str = "SMR", par_calibration: str = "", par_locked: bool = False):
+        self._device = device
+        self._mea_position = [0, 0, 0]
+        self._mea_quaternion = [0, 0, 0, 0]
+        self._par_state = par_state
+        self._par_mode = par_mode
+        self._par_type = par_type
+        self._par_calibration = par_calibration
+        self._par_locked = par_locked
+        # TODO implement
+
+    def get_jobs(self, topic: str) -> List[Job]:
+        job_list = []
+        job_list += [FixedJob(f"{topic}/MEA-Position", 1, self.get_mea_position)]
+        job_list += [UpdateJob(f"{topic}/MEA-Quaternion", self.get_mea_quaternion)]
+        job_list += [EventJob(f"{topic}/PAR-Locked", 10, self.get_par_locked,
+                              Event(EventSeverity.WARNING, EventTrigger.EQUALS, "bool", False,
+                                    "Target is not locked!"))]
+        return job_list
+
+    def get_mea_position(self) -> List[float]:
+        # TODO implement
+        return self._mea_position
+
+    def get_mea_quaternion(self) -> List[float]:
+        # TODO implement
+        return self._mea_quaternion
+
+    def get_par_state(self) -> StateEnum:
+        # TODO implement
+        return self._par_state
+
+    def get_par_mode(self) -> ModeEnum:
+        # TODO implement
+        return self._par_mode
+
+    def get_par_type(self) -> str:
+        # TODO implement
+        return self._par_type
+
+    def get_par_calibration(self) -> str:
+        # TODO implement
+        return self._par_calibration
+
+    def get_par_locked(self) -> bool:
+        # TODO implement
+        return self._par_locked
+
+    def set_par_locked(self, par_locked: bool):
+        # TODO implement
+        self._par_locked = par_locked
+
+    def fun_reset(self):
+        # TODO implement
+        pass
+
+    def fun_trigger(self):
+        # TODO implement
+        pass
diff --git a/test/devices/experimental_lasertracker/device/enums.py b/test/devices/experimental_lasertracker/device/enums.py
new file mode 100644
index 0000000000000000000000000000000000000000..deaf5f3c0885da1197cd00864c773bcca0b0347e
--- /dev/null
+++ b/test/devices/experimental_lasertracker/device/enums.py
@@ -0,0 +1,31 @@
+from enum import Enum
+
+
+class StateEnum(Enum):
+    OK = 0
+    WARNING = 1
+    ERROR = 2
+    MAINTENANCE = 3
+
+    def __str__(self):
+        return ["OK", "WARNING", "ERROR", "MAINTENANCE"][self.value]
+
+    @classmethod
+    def from_string(cls, name):
+        return {"OK": StateEnum.OK, "WARNING": StateEnum.WARNING, "ERROR": StateEnum.ERROR,
+                "MAINTENANCE": StateEnum.MAINTENANCE}[name]
+
+
+class ModeEnum(Enum):
+    Continuous = 0
+    Triggered = 1
+    External = 2
+    Idle = 3
+
+    def __str__(self):
+        return ["Continuous", "Triggered", "External", "Idle"][self.value]
+
+    @classmethod
+    def from_string(cls, name):
+        return {"Continuous": ModeEnum.Continuous, "Triggered": ModeEnum.Triggered, "External": ModeEnum.External,
+                "Idle": ModeEnum.Idle}[name]
diff --git a/test/devices/experimental_lasertracker/device/fun_jog.py b/test/devices/experimental_lasertracker/device/fun_jog.py
new file mode 100644
index 0000000000000000000000000000000000000000..2f2ceafdba520e2d42d1bf9d5c2464f229eb5434
--- /dev/null
+++ b/test/devices/experimental_lasertracker/device/fun_jog.py
@@ -0,0 +1,12 @@
+from soil.device import Device
+from soil.function import Function
+
+
+class FUNJog(Function):
+
+    def __init__(self, device: Device, parent_topic: str):
+        Function.__init__(self, 'FUN-Jog', 'jog',
+                          'Jogs the tracker head by the given angles for the azimuth and elevation.', device=device)
+
+    def execute(self, *args, **kwargs):
+        pass
diff --git a/test/devices/experimental_lasertracker/device/mea_azimuth.py b/test/devices/experimental_lasertracker/device/mea_azimuth.py
new file mode 100644
index 0000000000000000000000000000000000000000..4f5e04adaf1259d78736d8a303840f73d1204af9
--- /dev/null
+++ b/test/devices/experimental_lasertracker/device/mea_azimuth.py
@@ -0,0 +1,11 @@
+from soil.device import Device
+
+from soil.datatype import Datatype
+from soil.measurement import Measurement
+
+
+class MEAAzimuth(Measurement):
+
+    def __init__(self, device: Device, parent_topic: str):
+        Measurement.__init__(self, "MEA-Azimuth", "Azimuth", "Current position of azimuth rotation encoder in Radian.",
+                             Datatype.FLOAT, [], (0, 3.14), "C81", device=device)
diff --git a/test/devices/experimental_lasertracker/device/mea_distance.py b/test/devices/experimental_lasertracker/device/mea_distance.py
new file mode 100644
index 0000000000000000000000000000000000000000..b194eba82c05e5bdf2acad33972bf0aec0fcbb0b
--- /dev/null
+++ b/test/devices/experimental_lasertracker/device/mea_distance.py
@@ -0,0 +1,16 @@
+from soil.device import Device
+
+from soil.datatype import Datatype
+from soil.event import Event, EventSeverity, EventTrigger
+from soil.measurement import Measurement
+from soil.stream import EventJob
+
+
+class MEADistance(Measurement):
+
+    def __init__(self, device: Device, parent_topic: str):
+        Measurement.__init__(self, "MEA-Distance", "Distance", "Measured distance to the currently activate target.",
+                             Datatype.FLOAT, [], (0, 80), "MTR", device=device)
+        self._jobs += [EventJob(f"{parent_topic}/MEA-Distance", 10, self.get_value,
+                                Event(EventSeverity.WARNING, EventTrigger.LARGER, "double", 50,
+                                      "The target is distance is large. Uncertainty might be high."))]
diff --git a/test/devices/experimental_lasertracker/device/mea_elevation.py b/test/devices/experimental_lasertracker/device/mea_elevation.py
new file mode 100644
index 0000000000000000000000000000000000000000..152687d550d4d6d553341ca540e09686015d41b6
--- /dev/null
+++ b/test/devices/experimental_lasertracker/device/mea_elevation.py
@@ -0,0 +1,12 @@
+from soil.device import Device
+
+from soil.datatype import Datatype
+from soil.measurement import Measurement
+
+
+class MEAElevation(Measurement):
+
+    def __init__(self, device: Device, parent_topic: str):
+        Measurement.__init__(self, "MEA-Elevation", "Elevation",
+                             "Current position of elevation rotation encoder in Radian.", Datatype.FLOAT, [], (0, 3.14),
+                             "C81", device=device)
diff --git a/test/devices/experimental_lasertracker/device/mea_orientation.py b/test/devices/experimental_lasertracker/device/mea_orientation.py
new file mode 100644
index 0000000000000000000000000000000000000000..186fc1c94a93bd7a410ca1088bda366889daa292
--- /dev/null
+++ b/test/devices/experimental_lasertracker/device/mea_orientation.py
@@ -0,0 +1,10 @@
+from soil.device import Device
+from soil.datatype import Datatype
+from soil.measurement import Measurement
+
+
+class MEAOrientation(Measurement):
+
+    def __init__(self, device: Device, parent_topic: str):
+        Measurement.__init__(self, "MEA-Orientation", "Orientation", "Most recently dispatched measured orientation as quaternion, if available.",
+                             Datatype.FLOAT, [3], (0, 1), "None", device=device)
diff --git a/test/devices/experimental_lasertracker/device/mea_position.py b/test/devices/experimental_lasertracker/device/mea_position.py
new file mode 100644
index 0000000000000000000000000000000000000000..d4648532325b3b6dfa9b176db0c887d1a95f2785
--- /dev/null
+++ b/test/devices/experimental_lasertracker/device/mea_position.py
@@ -0,0 +1,14 @@
+from soil.datatype import Datatype
+from soil.device import Device
+from soil.measurement import Measurement
+from soil.stream import FixedJob
+
+
+class MEAPosition(Measurement):
+
+    def __init__(self, device: Device, parent_topic: str):
+        Measurement.__init__(self, "MEA-Position", "Position", "Most recently dispatched measured position.",
+                             Datatype.FLOAT, [3], (0, 80), "MTR", device=device)
+
+        # create jobs
+        self._jobs += [FixedJob(f"{parent_topic}/{self.uuid}", 1, self.get_value)]
diff --git a/test/devices/experimental_lasertracker/device/par_calibration.py b/test/devices/experimental_lasertracker/device/par_calibration.py
new file mode 100644
index 0000000000000000000000000000000000000000..f8dbaca0a2f13b248199ffdc26bad0e742421049
--- /dev/null
+++ b/test/devices/experimental_lasertracker/device/par_calibration.py
@@ -0,0 +1,11 @@
+from soil.datatype import Datatype
+from soil.device import Device
+from soil.parameter import Parameter
+
+
+class PARCalibration(Parameter):
+
+    def __init__(self, device: Device, parent_topic: str, value: str = ""):
+        Parameter.__init__(self, "PAR-Calibration", "Calibration",
+                           "Unique identifier that can be used to retrieve a corresponding calibration certificate.",
+                           Datatype.STRING, [], (0, None), constant=True, value=value, device=device)
diff --git a/test/devices/experimental_lasertracker/device/par_interval.py b/test/devices/experimental_lasertracker/device/par_interval.py
new file mode 100644
index 0000000000000000000000000000000000000000..c539a6deeba847eb9d27d128e9cfae04ebccb84e
--- /dev/null
+++ b/test/devices/experimental_lasertracker/device/par_interval.py
@@ -0,0 +1,13 @@
+from device.enums import StateEnum
+from soil.datatype import Datatype
+from soil.device import Device
+from soil.event import Event, EventSeverity, EventTrigger
+from soil.parameter import Parameter
+from soil.stream import EventJob
+
+
+class PARInterval(Parameter):
+
+    def __init__(self, device: Device, parent_topic: str, value: int = 10):
+        Parameter.__init__(self, "PAR-Interval", "Interval", "An interval.",
+                           Datatype.ENUM, [], StateEnum, constant=False, value=value, device=device)
diff --git a/test/devices/experimental_lasertracker/device/par_state.py b/test/devices/experimental_lasertracker/device/par_state.py
new file mode 100644
index 0000000000000000000000000000000000000000..8f0eb3963937590633bbc25987f5567dc35a4d84
--- /dev/null
+++ b/test/devices/experimental_lasertracker/device/par_state.py
@@ -0,0 +1,16 @@
+from device.enums import StateEnum
+from soil.datatype import Datatype
+from soil.device import Device
+from soil.event import Event, EventSeverity, EventTrigger
+from soil.parameter import Parameter
+from soil.stream import EventJob
+
+
+class PARState(Parameter):
+
+    def __init__(self, device: Device, parent_topic: str, value: StateEnum = StateEnum.ERROR):
+        Parameter.__init__(self, "PAR-State", "State", "Status of the base station.",
+                           Datatype.ENUM, [], StateEnum, constant=False, value=value, device=device)
+        self._jobs += [EventJob(f"{parent_topic}/PAR-State", 10, self.get_value,
+                                Event(EventSeverity.ERROR, EventTrigger.EQUALS, "enum", StateEnum.ERROR,
+                                      "An error occured!"))]
\ No newline at end of file
diff --git a/test/devices/experimental_lasertracker/device/start.py b/test/devices/experimental_lasertracker/device/start.py
new file mode 100644
index 0000000000000000000000000000000000000000..b7b26db55f241d933bac0c99e69dd49f2ae5354d
--- /dev/null
+++ b/test/devices/experimental_lasertracker/device/start.py
@@ -0,0 +1,97 @@
+# -*- coding: utf-8 -*-
+import asyncio
+import datetime
+import sys
+from concurrent.futures import ThreadPoolExecutor
+
+from devices.lasertracker.const import MQTT_USERNAME, MQTT_PASSWORD, MQTT_BROKER, MQTT_VHOST, MQTT_PORT
+from wzl.utilities import root_logger
+from wzl.mqtt import MQTTPublisher
+from src.soil.component import Component
+from src.http.server_old import HTTPServer
+from src.soil.event import Event, EventSeverity, EventTrigger
+from src.soil.stream import FixedJob, DynamicJob, EventJob, UpdateJob, StreamScheduler
+
+from devices.lasertracker.device.enums import StateEnum, ModeEnum
+from devices.lasertracker.device.com_lasertracker import COMLasertracker
+
+sys.setswitchinterval(0.0005)
+
+
+def start(com_lasertracker: COMLasertracker):
+    # server settings
+    address = '127.0.0.1'
+    port = '8000'
+
+    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)
+    mqtt.connect(MQTT_BROKER, MQTT_PORT, MQTT_VHOST + ":" + MQTT_USERNAME, MQTT_PASSWORD)
+
+    # configure messages
+    scheduler = StreamScheduler(loop, com_lasertracker.get_jobs("COM-Lasertracker"), [mqtt])
+
+    # 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': None}
+    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['MEA-Online'] = com_lasertracker._com_basestations._com_base.get_mea_online
+    submapping['PAR-State'] = {'getter': com_lasertracker._com_basestations._com_base.get_par_state, 'setter': None}
+    submapping['PAR-Calibration'] = {'getter': com_lasertracker._com_basestations._com_base.get_par_calibration, '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-Calibration'] = {'getter': com_lasertracker._com_mobileentities._com_target[child0_uuid].get_par_calibration, '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': []}}
+    model = Component.load('./Lasertracker.json', 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()
diff --git a/test/devices/experimental_lasertracker/lasertracker.py b/test/devices/experimental_lasertracker/lasertracker.py
new file mode 100644
index 0000000000000000000000000000000000000000..b6bd99caafac17159422f6d96fb42d17ba191e7c
--- /dev/null
+++ b/test/devices/experimental_lasertracker/lasertracker.py
@@ -0,0 +1,30 @@
+import datetime
+import random
+
+from device.enums import StateEnum
+from device.com_lasertracker import COMLasertracker
+
+
+class Lasertracker(COMLasertracker):
+
+    def __init__(self, device):
+        COMLasertracker.__init__(self, device)
+
+    def get_par_state(self):
+        if random.random() < 0.5:
+            self._par_state = StateEnum.OK
+        else:
+            self._par_state = StateEnum.WARNING
+        return self._par_state
+
+    def get_par_time(self):
+        # self._par_time = datetime.datetime.now()
+        return self._par_time
+
+    def fun_reset(self):
+        # TODO implement
+        pass
+
+    def fun_shutdown(self):
+        # TODO implement
+        pass
diff --git a/test/devices/experimental_lasertracker/main.py b/test/devices/experimental_lasertracker/main.py
new file mode 100644
index 0000000000000000000000000000000000000000..4a8d7c3ddd47d3f93b97babbc991720fb8c63a49
--- /dev/null
+++ b/test/devices/experimental_lasertracker/main.py
@@ -0,0 +1,12 @@
+# -*- coding: utf-8 -*-
+
+from soil.device import Device
+from device.start import start
+from lasertracker import Lasertracker
+
+if __name__ == "__main__":
+
+    device = Device()
+    lasertracker = Lasertracker(device)
+
+    start(lasertracker)
\ No newline at end of file
diff --git a/test/devices/lasertracker/device/start.py b/test/devices/lasertracker/device/start.py
index f1d7630bf746d3709d7208470228fd7de4bd475c..dcde8780092353e53d9a313ddda5bf2fed46c35a 100644
--- a/test/devices/lasertracker/device/start.py
+++ b/test/devices/lasertracker/device/start.py
@@ -10,7 +10,7 @@ from wzl.mqtt import MQTTPublisher
 from src.soil.component import Component
 from src.http.server import HTTPServer
 from src.soil.event import Event, EventSeverity, EventTrigger
-from src.soil.stream import FixedJob, ConfigurableJob, EventJob, UpdateJob, MessageScheduler, EventScheduler, UpdateScheduler
+from src.soil.stream import FixedJob, DynamicJob, EventJob, UpdateJob, MessageScheduler, EventScheduler, UpdateScheduler
 
 from devices.lasertracker.device.enums import StateEnum, ModeEnum
 from devices.lasertracker.device.com_lasertracker import COMLasertracker