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