diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..5057f6d595f544eedef1a6524ef8ddc13efec9ed
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,106 @@
+
+# Created by https://www.gitignore.io/api/python
+
+### Python ###
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+
+# PyInstaller
+#  Usually these files are written by a python script from a template
+#  before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.coverage
+.coverage.*
+.cache
+.pytest_cache/
+nosetests.xml
+coverage.xml
+*.cover
+.hypothesis/
+
+# Translations
+*.mo
+*.pot
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# pyenv
+.python-version
+
+# celery beat schedule file
+celerybeat-schedule.*
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+
+
+# End of https://www.gitignore.io/api/python
+
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000000000000000000000000000000000000..26df38bf5e1655464ef1fbf2c6c258a15c2c3752
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+    "python.linting.enabled": false
+}
\ No newline at end of file
diff --git a/animations/bright.py b/animations/bright.py
new file mode 100644
index 0000000000000000000000000000000000000000..fe67533972122e8efc4f054fbf12ec58aaa8faa7
--- /dev/null
+++ b/animations/bright.py
@@ -0,0 +1,22 @@
+from blinkenbase.animation import Animation
+from blinkenbase.blinkenroom import BLINKENROOM_LOUNGE
+import random
+from blinkenbase.Color import Color
+import time
+import _thread
+
+class bright(Animation):
+
+    required_args = {
+        "wait_range": [int, 100],
+        "stay": [float, 0.1]
+    }
+
+    def init_animation(self):
+        self.name = "bright"
+        self.blinkenroom = BLINKENROOM_LOUNGE
+        self.white = Color((255, 255, 255))
+
+    def cycle(self):
+        self.blinkenroom.set_brightness(100)
+        self.blinkenroom.set_full_color(self.white)
\ No newline at end of file
diff --git a/animations/flash.py b/animations/flash.py
index b9f856dc319eca81d5c1820722646ac23da8317d..abd1fa498f643e25197053acc97d6c842593e174 100644
--- a/animations/flash.py
+++ b/animations/flash.py
@@ -3,6 +3,7 @@ from blinkenbase.blinkenroom import BLINKENROOM_LOUNGE
 import random
 from blinkenbase.Color import Color
 import time
+import _thread
 
 class flash(Animation):
 
@@ -18,11 +19,14 @@ class flash(Animation):
         self.wait = random.randrange(self.wait_range)
         self.blinkenroom = BLINKENROOM_LOUNGE
         self.white = Color((255, 255, 255))
+        self.running = True
 
     def cycle(self):
-        if self.wait > 0:
-            self.wait -= 1
-        else:
+        for _ in range(4):
+            _thread.start_new_thread(self._thread_flash, ())
+
+    def _thread_flash(self):
+        while self.running:
             index = random.randrange(self.blinkenroom.total_length)
             self.blinkenroom.set_pixel(index, self.white)
             #flushing and setting every pixel to black
diff --git a/animations/random_color.py b/animations/random_color.py
index cc87985534d98192d4beeb9bfcec3064562b98bd..e94ea5573218877e18c103a069b59d444cc2c6a5 100644
--- a/animations/random_color.py
+++ b/animations/random_color.py
@@ -29,9 +29,15 @@ class Blink(object):
 
 class random_color(Animation):
 
+    required_args = {
+        "fps": [int, 20],
+        "brightness": [int, 60]
+    }
+
     def init_animation(self):
         self.blinkenroom = blinkenroom.BLINKENROOM_LOUNGE
-        self.set_fps(20)
+        self.set_fps(self.args["fps"])
+        self.blinkenroom.set_brightness(self.args["brightness"])
         self.blink_dict = {}
         self.name = "random_color"
 
diff --git a/blinkenbase/Color.py b/blinkenbase/Color.py
index ac2c96ea49143005ce9d3f293112e46983687075..85fb40cbdd9a1b3834c29a65e27fef7404c418cd 100644
--- a/blinkenbase/Color.py
+++ b/blinkenbase/Color.py
@@ -1,16 +1,8 @@
-# -*- coding: utf-8 -*-
-
-# -*- coding: utf-8 -*-
-
 import colorsys
-import numpy
 
-class Color(object):
-    @staticmethod
-    def hex_to_rgb(hex):
-        """Turns a hex value #123456 into rgb (18, 52, 86)"""
-        return tuple(int(hex[i:i+2], 16) for i in (0, 2 ,4))
 
+class Color(object):
+    
     red = 0
     green = 0
     blue = 0
@@ -21,6 +13,14 @@ class Color(object):
 
     def __init__(self, rgb=None, hsv=None, hex=None):
 
+        self.red = 0
+        self.green = 0
+        self.blue = 0
+
+        self.hue = 0
+        self.saturation = 0
+        self.value = 0
+
         rgb_passed = type(rgb) is tuple
         hsv_passed = type(hsv) is tuple
         hex_passed = type(hex) is str
@@ -120,3 +120,10 @@ class Color(object):
             return (int(self.red), int(self.green), int(self.blue))
         else:
             return (self.red, self.green, self.blue)
+
+    @staticmethod
+    def hex_to_rgb(hex):
+        """Turns a hex value #123456 into rgb (18, 52, 86)"""
+        return tuple(int(hex[i:i+2], 16) for i in (0, 2 ,4))
+
+            
diff --git a/blinkenbase/animation.py b/blinkenbase/animation.py
index e61dbc7c675585b7d210d746ea94e452a24d06cb..91c051ee8c02d05dcbdfc8d10baf6becc79ab01e 100644
--- a/blinkenbase/animation.py
+++ b/blinkenbase/animation.py
@@ -87,7 +87,7 @@ class Animation(Thread):
             try:
                 self.cycle()
             except:
-                continue
+                return
             if self._fps:
                 time_spend = time.time() - self._last_cycle
                 sleep_time = self._time_should - time_spend
diff --git a/blinkenbase/animation_handler.py b/blinkenbase/animation_handler.py
index d01e900b3a4655e0cd7d42338285f73f7269f815..337a2d963cf2f6d577336f20fa4130c846c0bf48 100644
--- a/blinkenbase/animation_handler.py
+++ b/blinkenbase/animation_handler.py
@@ -3,16 +3,35 @@ from blinkenbase.blinkenroom import Blinkenroom
 import blinkenbase.blinkenroom
 import blinkenbase.animation
 import time
+import os
 from blinkenbase.blinkenroom import BLINKENROOM_LOUNGE
 import _thread
+import threading
 
 class AnimationHandler(object):
 
     def __init__(self):
         self.running_animations = {}
         self.loaded_animation_instances = {}
+        self.available_animations = []
         self.last_frame = None
 
+        self._fetch_available_aninmations()
+
+    def _fetch_available_aninmations(self):
+        """
+        Scans the animation directory and stores all animations
+        """
+        for root, dirs, files in os.walk("animations/"):
+            for name in files:
+                if name.endswith("py"):
+                    name = name.split(".")[0]
+                    self.available_animations.append(name)
+
+    def get_available_animations(self):
+        # TODO make fancier -> fstring
+        return str(self.available_animations)
+
     #######################
     ###Animation control###
     ######################
@@ -55,7 +74,7 @@ class AnimationHandler(object):
         """
         Imports the given anmation and returns an instance of that class
         """
-        print(exec("from animations.%s import %s" % (p_name, p_name)))
+        exec("from animations.%s import %s" % (p_name, p_name))
         animation_class = eval(p_name)
         return animation_class()
 
@@ -100,42 +119,57 @@ class AnimationHandler(object):
         to_stop = []
         for running in self.running_animations.values():
             if animation.blinkenroom.is_overlapping(running.blinkenroom):
-                to_stop.append(running.name)
+                to_stop.append(running)
         for running in to_stop:
-            self.stop_animation(running)
+            #print("<<", type(running))
+           # blinkenroom = running.blinkenroom
+            #threading.Thread(target=self._thread_handler_darken, kwargs={"p_blinkenroom": blinkenroom, "p_sleep": 0.005}).start()
+            self.stop_animation(running.name)
 
         #Finaly starting animation
         self.running_animations[p_name] = animation
         animation.start()
+        
 
     #################
     ###Light-Level###
     #################
 
-    def _thread_handler_darken(self):
+    def _thread_handler_darken(self, p_start=100, p_end=0, p_blinkenroom=BLINKENROOM_LOUNGE, p_sleep=0.05):
         """
         Method to be called by thread to darken all blinkendevices
         """
-        f = 100
-        while f >= 0:
-            BLINKENROOM_LOUNGE.set_brightness(f)
-            time.sleep(0.05)
+        print(p_start)
+        print(p_end)
+        f = p_start
+        while f >= p_end:
+            p_blinkenroom.set_brightness(f)
+            time.sleep(p_sleep)
             f -= 1
         self.pause_all_animations()
 
-    def _thread_handler_lighten(self):
+    def _thread_handler_lighten(self, p_start=0, p_end=100, p_blinkenroom=BLINKENROOM_LOUNGE, p_sleep=0.05):
         """
         Method to be called by thread to light up all blinkendevices
         """
-        f = 1
-        self.resume_all_animations()
-        while f <= 100:
-            BLINKENROOM_LOUNGE.set_brightness(f)
-            time.sleep(0.05)
+        f = p_start
+        while f <= p_end:
+            p_blinkenroom.set_brightness(f)
+            time.sleep(p_sleep)
             f += 1
 
-    def darken(self, p_args):
-        _thread.start_new_thread(self._thread_handler_darken, ())
+    def darken(self, p_command, p_args):
+        start = 100
+        end = 0
 
-    def lighten(self, p_args):
-        _thread.start_new_thread(self._thread_handler_lighten, ())
+        if len(p_args) > 1:
+            start = int(p_args[1])
+        if len(p_args) > 2:
+            end = int(p_args[2])
+        #_thread.start_new_thread(self._thread_handler_darken, ())
+        threading.Thread(target=self._thread_handler_darken).start()
+
+    def lighten(self, p_command, p_args):
+        #_thread.start_new_thread(self._thread_handler_lighten, ())
+        self.resume_all_animations()
+        threading.Thread(target=self._thread_handler_lighten).start()
diff --git a/blinkenbase/blinken_receiver.py b/blinkenbase/blinken_receiver.py
new file mode 100644
index 0000000000000000000000000000000000000000..cdb3c11c0efe7dd51d5a3aa4356e9d8a991da741
--- /dev/null
+++ b/blinkenbase/blinken_receiver.py
@@ -0,0 +1,51 @@
+import socket
+import sys
+
+class BlinkenReceiver(object):
+    """
+    This class is the counterpart of blinkenbase.Blinkefoo.
+    Blinkenfoo generates a frame and sends it as UDP, while as
+    this class receives a udp package, turns it into a frame.
+    Subclasses can implement a specific blinkefoo-device and display
+    the frame in the desired fashion.
+    """
+
+    def __init__(self, p_name, p_hostname, p_port=2390):
+        self.name = p_name
+        self.hostname = p_hostname
+        self.port = p_port
+
+        self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+        self.socket.bind((self.hostname, self.port))
+
+        self._running = False
+    
+    def start_listening(self, p_buffer_size=1024):
+        self._running = True
+        while self._running:
+            data, address = self.socket.recvfrom(p_buffer_size)
+            frame = self._decode_frame(data)
+            print(frame)
+
+    def stop_listening(self):
+        self._running = False
+
+    def _decode_frame(self, p_data):
+        b = bytearray(p_data)
+        length = len(b)
+        start_index = 256 * b[0] + b[1]
+        end_index = 256 * b[2] + b[3]
+        rgb_list = []
+        for i in range(4, length, 3):
+            red = b[i]
+            green = b[i+1]
+            blue = b[i+2]
+            rgb_list.append((red, green, blue))
+        return ((start_index, end_index), rgb_list)
+
+
+    def diplay_frame(self, p_frame):
+        """
+        Displays a given frame on the blinkenfoo device
+        """
+        pass
diff --git a/blinkenbase/blinkenfoo.py b/blinkenbase/blinkenfoo.py
index dd7f7c363f41eeab4a57d23d73d9c81cb0377a6d..eb92f979687ade14d288b7fac0af942dfc2dd329 100644
--- a/blinkenbase/blinkenfoo.py
+++ b/blinkenbase/blinkenfoo.py
@@ -27,7 +27,7 @@ class Blinkenfoo(object):
         self._reset_buffer()
 
         self.current_fps = None
-        self.brightness_factor = 70
+        self.brightness_factor = p_brightness
 
     def __setattr__(self, p_key, p_value):
         if p_key == "brightness_factor":
@@ -58,7 +58,6 @@ class Blinkenfoo(object):
                 pack.extend([r, g, b])
             return pack
 
-
     def _send_package(self, p_package):
         """Sends the given udp_package to this's host:port"""
         try:
@@ -140,6 +139,7 @@ class Blinkenfoo(object):
     def clear(self):
         self._reset_buffer()
         self.flush()
+
 ##################
 ###End of class###
 ##################
@@ -152,7 +152,7 @@ DMX = Blinkenfoo("DMX", "10.0.3.27", 5, 10, p_brightness = 70)
 CUBES = Blinkenfoo("Cubes", "cubes.warpzone", 8, 5)
 TISCH = Blinkenfoo("Tisch", "tisch.warpzone", 700)
 
-DEVICE_LIST_LOUNGE = [DMX, WARP_SIGN, SPHERES, PANEL, CUBES]
+DEVICE_LIST_LOUNGE = [WARP_SIGN, SPHERES, PANEL, CUBES, DMX]
 #DEVICE_LIST_LOUNGE = [PANEL]
 #DEVICE_LIST_LOUNGE = [TISCH]
-#DEVICE_LIST_LOUNGE = [SPHERES]
+#DEVICE_LIST_LOUNGE = [SPHERES]
\ No newline at end of file
diff --git a/blinkenbase/blinkenroom.py b/blinkenbase/blinkenroom.py
index 3415fb8bac3ab005dd011e89c24f98d41cfa6687..675635b3dd8fcad31ad2ada5ca59120cf3bcc03d 100644
--- a/blinkenbase/blinkenroom.py
+++ b/blinkenbase/blinkenroom.py
@@ -4,12 +4,12 @@ from blinkenbase.Color import Color
 class Blinkenroom:
 
     def __init__(self, p_device_list, p_fps = 42):
-        self.fps = p_fps
         self.device_list = p_device_list
+        self._remove_unreachable_devices()
+        self.fps = p_fps
         self._calc_total_length()
         self._generate_index_map()
         self.previous_device = None
-        self._remove_unreachable_devices()
 
     #####################
     ###private methods###
diff --git a/blinkenshell.py b/blinkenshell.py
new file mode 100644
index 0000000000000000000000000000000000000000..7f6fda726b3f3b5341f909459e3e4f173dbc69df
--- /dev/null
+++ b/blinkenshell.py
@@ -0,0 +1,92 @@
+from shell.mqtt_shell import MQTTShell
+from shell.shellcore import Command
+from blinkenbase.animation_handler import AnimationHandler
+
+handler = AnimationHandler()
+shell = MQTTShell("81!nk3n5h311", p_server="warpsrvint.warpzone")
+
+def handler_start_animation(p_command, p_args):
+    animation_name = p_args[1]
+    animation_args_dict = {}
+    for i in range(2, len(p_args), 2):
+        key = str(p_args[i])
+        value = p_args[i+1]
+        animation_args_dict[key] = value
+
+    try:
+        handler.start_animation(animation_name, animation_args_dict)
+    except ValueError as e:
+        shell.print_error(str(e))
+
+def handler_list_animations(p_command, p_args):
+    shell.print_message(handler.get_available_animations())
+
+
+def handler_pause(p_command, p_args):
+    length = len(p_args)
+    if length <= 1:
+        handler.pause_all_animations()
+    else:
+        args = p_args[1:]
+        for name in args:
+            handler.pause_animation(name)
+
+def handler_resume(p_command, p_args):
+    length = len(p_args)
+    if length <= 1:
+        handler.resume_all_animations()
+    else:
+        args = p_args[1:]
+        for name in args:
+            handler.resume_animation(name)
+
+command_pause = Command(
+    p_name="pause",
+    p_handler=handler_pause,
+    p_usage="pause [<animation>]",
+    p_help="Pauses a specific or all animations"
+)
+
+command_resume = Command(
+    p_name="resume",
+    p_handler=handler_resume,
+    p_usage="resume [<animation>]",
+    p_help="Resumes a specific or all animations"
+)
+
+command_play = Command(
+    p_name="Play",
+    p_handler=handler_start_animation,
+    p_usage="play <animation> {<key> <value>}",
+    p_help="Plays an animation. Eg. rainbow speed 2"
+)
+
+command_list = Command(
+    p_name="List",
+    p_handler=handler_list_animations,
+    p_usage="list",
+    p_help="Lists all available animations"
+)
+
+command_darken = Command(
+    p_name="darken",
+    p_handler=handler.darken,
+    p_usage="darken",
+    p_help="darkens all blinkenfoo to 0 and pauses all animations"
+)
+
+command_lighten = Command(
+    p_name="lighten",
+    p_handler=handler.lighten,
+    p_usage="lighten",
+    p_help="resumes all animations and lightens the room t0 100%"
+)
+
+shell.register_command(command_play)
+shell.register_command(command_list)
+shell.register_command(command_darken)
+shell.register_command(command_lighten)
+shell.register_command(command_pause)
+shell.register_command(command_resume)
+
+shell.start()
diff --git a/mqtt.py b/mqtt.py
index bc2f4defe33934553e7a70ecf1ca90e10be12835..d9665f42014edd8b2af59d19684be29af1a4195c 100644
--- a/mqtt.py
+++ b/mqtt.py
@@ -55,12 +55,16 @@ def handler_resume(p_args):
         for name in args:
             handler.resume_animation(name)
 
+def handler_list_animations(p_args):
+    print(handler.get_available_animations())
+
 command_handler_dict["play"] = handler_start_animation
 command_handler_dict["exit"] = handler.stop_all_animations
 command_handler_dict["resume"] = handler_resume
 command_handler_dict["pause"] = handler_pause
 command_handler_dict["darken"] = handler.darken
 command_handler_dict["lighten"] = handler.lighten
+command_handler_dict["list"] = handler_list_animations
 
 client.connect("warpsrvint.warpzone", 1883, 60)
 client.loop_forever()
diff --git a/shell/mqtt_handler.poy b/shell/mqtt_handler.poy
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/shell/mqtt_handler.py b/shell/mqtt_handler.py
new file mode 100644
index 0000000000000000000000000000000000000000..5dc5c22c82422e2e9056c04bd3638360c5742106
--- /dev/null
+++ b/shell/mqtt_handler.py
@@ -0,0 +1,16 @@
+import paho.mqtt.client as mqtt
+
+class MQTTHandler(object):
+
+    def __init__(self, p_read_topic="blinkenfoo/read", p_write_topic="blinkenfoo/write", p_server, p_port=1883):
+        client = mqtt.Client()
+
+        client.connect(p_server, p_port, 60)
+        client.subscribe(p_read_topic)
+        client.loop_forever()
+
+
+    def read():
+
+
+    def write(self, p_message):
diff --git a/shell/mqtt_shell.py b/shell/mqtt_shell.py
new file mode 100644
index 0000000000000000000000000000000000000000..e9e80ffe2a530222fb087e66104d3c2a6a6bdcd7
--- /dev/null
+++ b/shell/mqtt_shell.py
@@ -0,0 +1,43 @@
+from shell.shellcore import Shell
+import paho.mqtt.client as mqtt
+
+class MQTTShell(Shell):
+
+    def __init__(self, p_name, p_server, p_port=1883, p_read_topic="blinkenfoo/read", p_write_topic="blinkenfoo/write"):
+        super(MQTTShell, self).__init__(p_name)
+        client = mqtt.Client()
+
+        client.connect(p_server, p_port, 60)
+        client.subscribe(p_read_topic)
+
+        client.on_message = self._built_on_message()
+        self.client = client
+        self.write_topic = p_write_topic
+
+    def _built_on_message(self):
+        return lambda client, userdata, p_message: self.evaluate_input(p_message.payload.decode())
+
+    def _write(self, p_message):
+        self.client.publish(self.write_topic, p_message.encode())
+
+    ##########################
+    ### overriding methods ###
+    ##########################
+
+    def print_message(self, p_string):
+        self._write(p_string)
+
+    def print_notification(self, p_string):
+        self.print_message(p_string)
+
+    def print_error(self, p_string):
+        self.print_message("[ Error ]" + p_string)
+
+    def print_warning(self, p_string):
+        self.print_message("[Warning]" + p_string)
+
+    ###
+
+    def start(self):
+        super(MQTTShell, self).start(False)
+        self.client.loop_forever()
diff --git a/shell/shellcore.py b/shell/shellcore.py
new file mode 100644
index 0000000000000000000000000000000000000000..8be79f05a0e34f9caeb92781fa6dbfd9f86ea132
--- /dev/null
+++ b/shell/shellcore.py
@@ -0,0 +1,284 @@
+class Command(object):
+
+    # TODO aliases
+
+    def __init__(self, p_name=None, p_handler=None, p_usage="", p_help=""):
+        if not p_name:
+            raise ValueError("Command-name cannot be None")
+
+        self.name = p_name.lower()
+        self.handler = p_handler
+        self.help = p_help
+        self.usage = p_usage
+        self._shell = None
+
+    def print_usage(self):
+        """Prints the usage of this command in the currently assigned shell"""
+        if self._shell is not None:
+            self._shell.print_notification(self.usage)
+        else:
+            raise AttributeError(
+                "Command ''%s' is not assigned to a shell" % self.name
+            )
+
+    def assign_command_to_shell(self, p_shell):
+        """
+        Assigns this commmand to a shell. An command instance can only
+        be assigned to one shell at a time
+        """
+        if not self._shell:
+            self._shell = p_shell
+        else:
+            raise AttributeError(
+                "Command '%s' is alread assigned to a shell" % self.name
+            )
+
+    def deassign_command_from_shell(self):
+        """Signature says it all"""
+        self._shell = None
+
+    def get_shell(self):
+        return self._shell
+
+
+class Shell(object):
+
+    def __init__(self, p_name="Programmer 2 lazy to name this shell"):
+        self.SHELL_NAME = p_name
+        self.SHELL_PREFIX = self.SHELL_NAME.lower() + "~: "
+
+        self._RUNNING = False
+        self._command_dict = {}
+
+        command_exit = Command(
+            p_name="exit",
+            p_handler=self._exit_handler,
+            p_usage="exit",
+            p_help="Quits this shell"
+        )
+        command_help = Command(
+            p_name="help",
+            p_handler=self._help_handler,
+            p_usage="help",
+            p_help="Prints this help"
+        )
+
+        self.register_command(command_exit)
+        self.register_command(command_help)
+
+        # If no matching command was found, the default_handler is called
+        self.default_handler = command_help.handler
+
+    def evaluate_input(self, p_input):
+        """
+        Handles the given input as command.
+        The deal is that a shell may not be always busy waiting, therefor
+        a loop is not always necassery
+        """
+        # Splitting input string an removing whitespaces
+        args = p_input.lower().split(" ")
+        args = [a for a in args if a not in [" ", "", "\t"] ]
+
+        # If no command was given
+        if not len(args):
+            self.default_handler(None, None)
+            return
+
+        command = self._get_command_by_name(args[0])
+
+        # If given command was not found
+        if command is None:
+            self.print_error("Unknown command!")
+            return
+        #try:
+        command.handler(command, args)
+       # except Exception as e:
+        #    self.print_error("\nThere was an error executing this command. Contact your admin/dev.\n" + str(e))
+
+    # Shell main-control
+    # ##################
+    def start(self, p_busy_waiting=True):
+        """
+        Starting shell
+        """
+        self._RUNNING = True
+        self.print_notification(
+            "Welcome to " + self.SHELL_NAME + ". Type help for help."
+        )
+
+        if not p_busy_waiting:
+            return
+
+        while self._RUNNING:
+            # Reading command
+            input = self.read_command()
+            self.evaluate_input(input)
+
+    def stop(self):
+        """
+        Stopping shell
+        """
+        self.print_notification("Quitting " + self.SHELL_NAME + "...")
+        self._RUNNING = False
+
+    # Reading stuff from UI
+    #######################
+    def read_command(self):
+        pass
+
+    def read_string(self, p_message, p_default=None, p_retries=1):
+        """
+        Prints given message and reads user input as string.
+        Prints error if input is not string/not decodeable and returns None.
+        """
+        return self._read_custom_value(
+            p_message=p_message,
+            p_cast_function=lambda x: x,
+            p_retries=p_retries,
+            p_error_message="Input must be text!",
+            p_default=p_default
+        )
+
+    def read_float(self, p_message, p_default=0, p_retries=1):
+        """
+        Prints given message and reads user input as float.
+        Prints error if input is no float and returns None.
+        """
+        return self._read_custom_value(
+            p_message=p_message,
+            p_cast_function=lambda x: float(x),
+            p_retries=p_retries,
+            p_error_message="Input must be float!",
+            p_default=p_default
+        )
+
+    def read_int(self, p_message, p_default=0, p_retries=1):
+        """
+        Prints given message and reads user input as int.
+        Prints error if input is no int and returns None.
+        """
+        return self._read_custom_value(
+            p_message=p_message,
+            p_cast_function=lambda x: int(x),
+            p_retries=p_retries,
+            p_error_message="Input must be Integer!",
+            p_default=p_default
+        )
+
+    def _read_custom_value(self, p_message, p_cast_function, p_retries, p_error_message, p_default):
+        """
+        Reads a custom value and tries to cast it. If casting fails, the given error-message is printed.
+        if retries > 0, then the user is prompted to enter the value again till all retries were used.
+        """
+        read = self.read_custom_input(p_message)
+
+        if not len(read):
+            return p_default
+
+        try:
+            read = p_cast_function(read)
+        except ValueError:
+            self.print_error(p_error_message)
+            if p_retries > 0:
+                return self._read_custom_value(p_message, p_cast_function, p_retries - 1, p_error_message, p_default)
+            return p_default
+        return read
+
+    def read_custom_input(self, p_message):
+        """
+        Reads input with custom
+        """
+        pass
+
+    # Handler for integrated commands
+    #################################
+    def _exit_handler(self, p_command, p_args):
+        self.stop()
+
+    def _help_handler(self, p_command, p_args):
+        self.print_help()
+
+    # Printmethods
+    # ############
+    def print_message(self, p_string):
+        pass
+
+    def print_notification(self, p_string):
+        pass
+
+    def print_error(self, p_string):
+        pass
+
+    def print_warning(self, p_string):
+        pass
+
+    def print_help(self):
+        """
+        Remove since this shell works with mqtt input.
+        Help is handled by client
+        """
+        pass
+
+     # Command registering
+    # ###################
+    def register_command(self, p_command):
+        """Registers an command to this shell. A command is identified by its name."""
+        name = p_command.name
+        if name in self._command_dict:
+            raise ValueError("Command %s is already registered! Unregister first to replace" % str(p_command.name))
+        self._command_dict[name] = p_command
+        p_command.assign_command_to_shell(self)
+
+    def unregister_command(self, p_name):
+        """Unregisters an command. If there is no such name, nothing will happen"""
+        if p_name in self._command_dict:
+            command = self._command_dict[p_name]
+            command.deassign_command_from_shell()
+            del self._command_dict[p_name]
+        else:
+            raise ValueError("No command by the alias %s registered" % str(p_name))
+
+    def _get_command_by_name(self, p_name):
+        """Maps an alias to the command key. Returns None if there is no such alias"""
+        if p_name is None or p_name not in self._command_dict:
+            return None
+        return self._command_dict[p_name]
+
+    # I dont think this is needed here but I'll let it live...just in case
+    # More lines -> more money...i guess
+    # (This project is opensource)
+    # SHUT UP!
+    def merge_values(self, p_value_old, p_value_new):
+        """
+        Merges two values. If one of both is None, the other one is returned.
+        If both values are not the same, the user is prompted to choose
+        """
+        # If both values are the same
+        if p_value_old == p_value_new:
+            return p_value_old
+
+        # If no value is set
+        if not p_value_old and not p_value_new:
+            return None
+
+        # If both values are set, user is prompted to decide which one to use
+        if p_value_old is not None and p_value_new is not None:
+            choice = self.read_custom_input(
+                "Entry is already known. Do want to replace '%s' with '%s'? [Y]es/[N]o/[A]bort: " %
+                (str(p_value_old), str(p_value_new))
+            )
+            choice = choice.lower()
+            if choice in ["yes", "y"]:
+                return p_value_new
+            if choice in ["no", "n"]:
+                return p_value_old
+            self.print_message("Aborting...")
+            return None
+
+        # If only new value is set
+        if p_value_old is None:
+            return p_value_new
+
+        # If only old value is set
+        if p_value_new is None:
+            return p_value_old