diff --git a/src/visualising/animation.py b/src/visualising/animation.py index 6354bf3948734e971c623cad02371eedbaf817e2..1898b1e3f7d5e0526946967c9f2266c404a0364b 100755 --- a/src/visualising/animation.py +++ b/src/visualising/animation.py @@ -4,82 +4,63 @@ import re import importlib.resources from . import animations -from visualising.exception import VisualiseException class Animation: + def __init__(self, frames, frame_time, num_iter): + num_frames = len(frames) - source = "animation.py" # Name of the file that contains the Animation class definition. + if num_frames % 2 != 0: + raise ValueError("Number of frames must be even!") + + for frame in frames: + num_values = len(frame) + + if num_values != 48: + raise ValueError("All frames must have exactly 48 values!") + + is_numeric = True + is_positive = True + is_small = True + + for value in frame: + is_numeric = is_numeric and isinstance(value, int) + is_positive = is_positive and not (value < 0) + is_small = is_small and value < 256 + + if not is_numeric: + raise TypeError("All frame values must be integers!") + if not is_positive: + raise ValueError("All frame values must be positive!") + if not is_small: + raise ValueError("All frame values must be smaller than 256!") + + self.frames = frames + self.num_frames = num_frames + self.num_ensembles = int(num_frames / 2) - def __init__(self, frames, frame_time, num_iter): - # Set frames, number of frames and number of ensembles. - try: - animation_len = len(frames) - except TypeError: - raise VisualiseException(source, "The animation is empty!") - - if animation_len % 2 != 0: - raise VisualiseException(source, "Animation must contain an even number of frames!") - else: - for frame in frames: - try: - frame_len = len(frame) - except TypeError: - raise VisualiseException(source, "At least one frame is empty!") - - if frame_len != 48: - raise VisualiseException(source, "At least one frame doesn't have exactly 48 values!") - else: - is_numeric = True - is_positive = True - is_small = True - - for value in frame: - is_numeric = is_numeric and isinstance(value, int) - is_positive = is_positive and not (value < 0) - is_small = is_small and value < 256 - - if not is_numeric: - raise VisualiseException(source, "Not all values in the animation are integers!") - elif not is_positive: - raise VisualiseException(source, "Not all values in the animation are positive!") - elif not is_small: - raise VisualiseException(source, "One or more values in the animation are to large!") - else: - self.frames = frames - self.num_frames = animation_len - self.num_ensembles = int(animation_len / 2) - - # Set frame time. if not isinstance(frame_time, int): - raise VisualiseException(source, "Frame time must be an integer!") - elif frame_time < 0: - raise VisualiseException(source, "Frame time must be positive!") - elif frame_time > 4294967295: - raise VisualiseException(source, "Animation delay is to high!") - else: - self.frame_time = frame_time + raise TypeError("Frame time must be an integer!") + if not frame_time < 0: + raise ValueError("Frame time must be positive!") + if not frame_time > 4294967295: + raise ValueError("Frame time is to high!") + + # The frame time is given in milliseconds. + self.frame_time = frame_time - # Set number of iterations. if not isinstance(num_iter, int): - raise VisualiseException(source, "Number of iterations must be an integer!") - elif num_iter > 255: - raise VisualiseException(source, "Animation has to many iterations!") - elif num_iter <= 0: - raise VisualiseException(source, "Animation must have at least one iteration!") - else: - self.num_iter = num_iter + raise TypeError("Number of iterations must be an integer!") + if num_iter > 255: + raise ValueError("Animation has to many iterations!") + if num_iter <= 0: + raise ValueError("Animation must have at least one iteration!") + + self.num_iter = num_iter @staticmethod def read_frames_from_file(filename): - try: - file = importlib.resources.open_text(animations, filename) - except AttributeError: - raise VisualiseException(source, "The file is incorrectly formatted!") - except FileNotFoundError: - raise VisualiseException(source, "The specified file does not exist!") - except TypeError: - raise VisualiseException(source, "No animation file was specified!") + file = importlib.resources.open_text(animations, filename) animation = [] for line in file: @@ -100,59 +81,50 @@ class Animation: @staticmethod def set_pixel_color(ensemble, pixel, r, g, b): - try: - ensemble_len = len(ensemble) - except TypeError: - raise VisualiseException(source, "Ensemble is empty!") + ensemble_len = len(ensemble) if ensemble_len != 2: - raise VisualiseException(source, "Ensemble doesn't contain exactly two frames!") + raise ValueError("Ensemble must contain exactly two frames!") + + for frame in ensemble: + num_values = len(frame) + + if num_values != 48: + raise ValueError("All frames must have exactly 48 values!") + + is_numeric = True + is_positive = True + is_small = True + + for value in frame: + is_numeric = is_numeric and isinstance(value, int) + is_positive = is_positive and not (value < 0) + is_small = is_small and value < 256 + + if not is_numeric: + raise TypeError("All frame values must be integers!") + if not is_positive: + raise ValueError("All frame values must be positive!") + if not is_small: + raise ValueError("All frame values must be smaller than 256!") + + if not isinstance(pixel, int): + raise TypeError("Pixel must be an integer!") + if not (0 < pixel < 33): + raise ValueError("Pixel must be in the range of 1 to 32!") + if not (isinstance(r, int) and isinstance(g, int) and isinstance(b, int)): + raise TypeError("Color values must be integers!") + if not ((0 < r < 256) and (0 < g < 256) and (0 < b < 256)): + raise ValueError("Color values must be in the range of 1 to 255!") + + if pixel < 16: + index = 0 else: - for frame in ensemble: - try: - frame_len = len(frame) - except TypeError: - raise VisualiseException(source, "At least one frame is empty!") - - if frame_len != 48: - raise VisualiseException(source, "At least one frame doesn't have exactly 48 values!") - else: - is_numeric = True - is_positive = True - is_small = True - - for value in frame: - is_numeric = is_numeric and isinstance(value, int) - is_positive = is_positive and not (value < 0) - is_small = is_small and value < 256 - - if not is_numeric: - raise VisualiseException(source, "Not all values in the ensemble are integers!") - elif not is_positive: - raise VisualiseException(source, "Not all values in the ensemble are positive!") - elif not is_small: - raise VisualiseException(source, "One or more values in the ensemble are to large!") - else: - if not (0 < pixel < 33): - raise VisualiseException(source, "Pixel has to be in the range of 1 to 32!") - elif not (0 < r < 256): - raise VisualiseException(source, "Red channel value has to be in the range of 1 " - "to 255!") - elif not (0 < g < 256): - raise VisualiseException(source, "Green channel value has to be in the range of 1 " - "to 255!") - elif not (0 < b < 256): - raise VisualiseException(source, "Blue channel value has to be in the range of 1 " - "to 255!") - else: - if pixel < 16: - index = 0 - else: - index = 1 - pixel = pixel - 16 - - ensemble[index][pixel + 0] = r - ensemble[index][pixel + 1] = g - ensemble[index][pixel + 2] = b - - return ensemble + index = 1 + pixel = pixel - 16 + + ensemble[index][pixel + 0] = r + ensemble[index][pixel + 1] = g + ensemble[index][pixel + 2] = b + + return ensemble diff --git a/src/visualising/arduino.py b/src/visualising/arduino.py index 6815d6080909f17eced482afb464a44108494f1e..85c21dc37df52380d9722c4797f7dea7ba798f28 100755 --- a/src/visualising/arduino.py +++ b/src/visualising/arduino.py @@ -4,57 +4,58 @@ import time from visualising.message import FrameMsg, InstrMsg from visualising.animation import Animation -from visualising.exception import VisualiseException +from visualising.connection import ArduinoException class Arduino: - source = "arduino.py" # Name of the file that contains the Arduino class definition. - resend = 5 # Number of times a message is resend to the Arduino. + resend = 5 # Number of times a message is resend to the Arduino. def __init__(self, connection): self.connection = connection - # Creates one instruction messages and multiple frame messages to load an animation - # onto the Arduino. - def load_animation(self, animation): - if animation.num_ensembles > 16: - raise VisualiseException(source, "Animation hast to many frames!") - else: - try: - self.connection.confirm_msg(InstrMsg('A', animation).create_msg(), [b'\x0f'], resend) - except VisualiseException: - raise - - for frame in animation.frames: - try: - self.connection.confirm_msg(FrameMsg(frame).create_msg(), [b'\x2f', b'\x3f'], resend) - except VisualiseException: - raise - # Creates instruction message and sends it to the Arduino, # to play a loaded animation. def start_playback(self): - try: - self.connection.confirm_msg(InstrMsg('B').create_msg(), [b'\x1f'], resend) - except VisualiseException: - raise + self.connection.confirm_msg(InstrMsg('B').create_msg(), [b'\x1f'], resend) # Creates instruction message and sends it to the Arduino, # to pause a playing animation. def pause_playback(self): - try: - self.connection.confirm_msg(InstrMsg('C').create_msg(), [b'\x5f'], resend) - except VisualiseException: - raise + self.connection.confirm_msg(InstrMsg('C').create_msg(), [b'\x5f'], resend) # Creates instruction message and sends it to the Arduino, # to resume playback of a loaded animation. def resume_playback(self): - try: - self.connection.confirm_msg(InstrMsg('D').create_msg(), [b'\x6f'], resend) - except VisualiseException: - raise + self.connection.confirm_msg(InstrMsg('D').create_msg(), [b'\x6f'], resend) + + # Creates one instruction messages and multiple frame messages to load an animation + # onto the Arduino. + def load_animation(self, animation): + if animation.num_ensembles > 16: + raise ValueError("Animation has to many frames!") + else: + self.connection.confirm_msg(InstrMsg('A', animation).create_msg(), [b'\x0f'], resend) + + for frame in animation.frames: + self.connection.confirm_msg(FrameMsg(frame).create_msg(), [b'\x2f', b'\x3f'], resend) + + # First, an animation is loaded onto the Arduino. The playback of this + # animation is then started. In addition, it is checked whether the + # animation was played successfully. + def play_animation(self, animation): + self.load_animation(animation) + self.start_playback() + + # Estimated playback time in seconds. + playback_time = animation.frame_time * animation.num_ensembles * 0.001 + # Current time in seconds. + current_time = time.time() + + while bytes(self.connection.receive_msg()) != bytes(b'\x4f'): + # Wait 10 times the estimated animation playback time for a response. + if time.time() > current_time + playback_time * 10: + raise ArduinoException("Successful playback of animation can't be confirmed!") # Streams an animation to the Arduino. For this purpose, two frames of # the animation are repeatedly sent to the Arduino. The Arduino plays @@ -64,11 +65,4 @@ class Arduino: for _ in range(animation.num_iter): for i in range(animation.num_ensembles): ensemble = [animation.frames[2 * i + 0], animation.frames[2 * i + 1]] - self.load_animation(Animation(ensemble, animation.frame_time, 1)) - self.start_playback() - - curr_time = time.time() # Current time in milliseconds. - # Waits 10 times the animation delay for a response. - while bytes(self.connection.receive_msg()) != bytes(b'\x4F'): - if time.time() > curr_time + animation.frame_time / 100: - raise ArduinoException("P: -- ERROR -- Arduino is not responding!") + self.play_animation(Animation(ensemble, animation.frame_time, 1)) diff --git a/src/visualising/connection.py b/src/visualising/connection.py index f3573903fe673c1da6dc2dfb03334156a83d30a9..43b2353a029a6dbdf28e3b980268b32078bdc75d 100755 --- a/src/visualising/connection.py +++ b/src/visualising/connection.py @@ -7,22 +7,19 @@ from visualising.exception import VisualiseException from serial.serialutil import SerialException -class Connection: +class ArduinoException(Exception): + def __init__(self, desc): + self.desc = desc - source = "connection.py" # Name of the file that contains the Connection class definition. +class Connection: def __init__(self, port, baud): self.connection = serial.Serial() self.connection.port = port self.connection.baudrate = baud self.connection.timeout = 1 - try: - self.connection.open() # Establishes a serial connection. - except SerialException: - raise VisualiseException(source, "No serial connection established!") - except FileNotFoundError: - raise VisualiseException(source, "The specified port does not exist!") + self.connection.open() # Establishes a serial connection. time.sleep(2) # Prevents errors regarding pyserial. @@ -78,29 +75,31 @@ class Connection: # If the response is not as expected, try again as often as # specified. In case any response wasn't even once one of the # expected responses raise an exception. - def confirm_msg(self, msg, expected_responses, resend): + def confirm_msg(self, msg, expected_responses, num_resend): def evaluate(): - bool = resend > 0 + bool = num_resend > 0 for element in expected_responses: bool = bool and (bytes(element) != bytes(response)) return bool if not isinstance(expected_responses, list): - raise VisualiseException(source, "Expected responses must be given as a list!") - elif not expected_responses: - raise VisualiseException(source, "There must be at least one expected response!") - elif not resend > 0: - raise VisualiseException(source, "Number of resends must be greater than zero!") - else: + raise TypeError("Expected responses must be given as a list!") + if not expected_responses: + raise ValueError("There must be at least one expected response!") + if not isinstance(num_resend, int): + raise TypeError("Number of resends must be an integer!") + if not num_resend > 0: + raise ValueError("Number of resends must be greater than zero!") + + self.connection.send_msg(msg) + response = self.connection.receive_msg() + + condition = evaluate() + while condition: + num_resend = num_resend - 1 self.connection.send_msg(msg) response = self.connection.receive_msg() - condition = evaluate() - while condition: - resend = resend - 1 - self.connection.send_msg(msg) - response = self.connection.receive_msg() - condition = evaluate() - - if not resend > 0: - raise VisualiseException("Arduino", Connection.translate_msg(response)) + + if not num_resend > 0: + raise ArduinoException(Connection.translate_msg(response)) diff --git a/src/visualising/exception.py b/src/visualising/exception.py deleted file mode 100755 index fad8fca718c024db6128d8a3c93f6c44c94e42f5..0000000000000000000000000000000000000000 --- a/src/visualising/exception.py +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env python - -class Error(Exception): - pass - - -class VisualiseException(Error): - def __init__(self, source, description): - self.source = source - self.description = description diff --git a/src/visualising/visualiser.py b/src/visualising/visualiser.py index d7ef32cf18d5505cf4e9a7d9daa3c95c7f3cf84d..0b262229b5b78761b0871f8f63ba12c80230a396 100644 --- a/src/visualising/visualiser.py +++ b/src/visualising/visualiser.py @@ -1,10 +1,10 @@ #!/usr/bin/env python import rospy + from monitoring.watchdog import Watchdog -from visualising.exception import * from visualising.arduino import Arduino -from visualising.connection import Connection +from visualising.connection import Connection, ArduinoException from visualising.animation import Animation @@ -40,11 +40,8 @@ class Visualiser: else: self.stream_animation(happy) - - - def info_mode(self): print("Hallo!") def stream_animation(self, animation): - self.arduino.stream_animation(animation) \ No newline at end of file + self.arduino.stream_animation(animation)