diff --git a/msg/State.msg b/msg/State.msg deleted file mode 100644 index 0d2c235a8af317a531ffa9be244984e49e194967..0000000000000000000000000000000000000000 --- a/msg/State.msg +++ /dev/null @@ -1,3 +0,0 @@ -string component # Name of the component, whose - # critical level is monitored. -float32 critical # Critical level of the family. \ No newline at end of file diff --git a/msg/States.msg b/msg/States.msg deleted file mode 100644 index d6705831806ee8d3908995353186638fe8aa46c5..0000000000000000000000000000000000000000 --- a/msg/States.msg +++ /dev/null @@ -1 +0,0 @@ -State[] states # Array of states. diff --git a/src/visualising/animation.py b/src/visualising/animation.py index a639dfd8e14a3aa679d3948899d4733fea68d776..6354bf3948734e971c623cad02371eedbaf817e2 100755 --- a/src/visualising/animation.py +++ b/src/visualising/animation.py @@ -2,29 +2,33 @@ import re import importlib.resources + from . import animations -from visualising.exception import AnimationException +from visualising.exception import VisualiseException class Animation: + + source = "animation.py" # Name of the file that contains the Animation class definition. + 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 AnimationException("P: -- ERROR -- The animation is empty!") + raise VisualiseException(source, "The animation is empty!") if animation_len % 2 != 0: - raise AnimationException("P: -- ERROR -- Animation must contain an even number of frames!") + raise VisualiseException(source, "Animation must contain an even number of frames!") else: for frame in frames: try: frame_len = len(frame) except TypeError: - raise AnimationException("P: -- ERROR -- At least one frame is empty!") + raise VisualiseException(source, "At least one frame is empty!") if frame_len != 48: - raise AnimationException("P: -- ERROR -- At least one frame doesn't have exactly 48 values!") + raise VisualiseException(source, "At least one frame doesn't have exactly 48 values!") else: is_numeric = True is_positive = True @@ -36,11 +40,11 @@ class Animation: is_small = is_small and value < 256 if not is_numeric: - raise AnimationException("P: -- ERROR -- Not all values in the animation are integers!") + raise VisualiseException(source, "Not all values in the animation are integers!") elif not is_positive: - raise AnimationException("P: -- ERROR -- Not all values in the animation are positive!") + raise VisualiseException(source, "Not all values in the animation are positive!") elif not is_small: - raise AnimationException("P: -- ERROR -- One or more values in the animation are to large!") + raise VisualiseException(source, "One or more values in the animation are to large!") else: self.frames = frames self.num_frames = animation_len @@ -48,21 +52,21 @@ class Animation: # Set frame time. if not isinstance(frame_time, int): - raise AnimationException("P: -- ERROR -- Frame time must be an integer!") + raise VisualiseException(source, "Frame time must be an integer!") elif frame_time < 0: - raise AnimationException("P: -- ERROR -- Frame time must be positive!") + raise VisualiseException(source, "Frame time must be positive!") elif frame_time > 4294967295: - raise AnimationException("P: -- ERROR -- Animation delay is to high!") + raise VisualiseException(source, "Animation delay is to high!") else: self.frame_time = frame_time # Set number of iterations. if not isinstance(num_iter, int): - raise AnimationException("P: -- ERROR -- Number of iterations must be an integer!") + raise VisualiseException(source, "Number of iterations must be an integer!") elif num_iter > 255: - raise AnimationException("P: -- ERROR -- Animation has to many iterations!") + raise VisualiseException(source, "Animation has to many iterations!") elif num_iter <= 0: - raise AnimationException("P: -- ERROR -- Animation must have at least one iteration!") + raise VisualiseException(source, "Animation must have at least one iteration!") else: self.num_iter = num_iter @@ -71,11 +75,11 @@ class Animation: try: file = importlib.resources.open_text(animations, filename) except AttributeError: - raise AnimationException("P: -- ERROR -- The file is incorrectly formatted!") + raise VisualiseException(source, "The file is incorrectly formatted!") except FileNotFoundError: - raise AnimationException("P: -- ERROR -- The specified file does not exist!") + raise VisualiseException(source, "The specified file does not exist!") except TypeError: - raise AnimationException("P: -- ERROR -- No animation file was specified!") + raise VisualiseException(source, "No animation file was specified!") animation = [] for line in file: @@ -99,19 +103,19 @@ class Animation: try: ensemble_len = len(ensemble) except TypeError: - raise AnimationException("P: -- ERROR -- Ensemble is empty!") + raise VisualiseException(source, "Ensemble is empty!") if ensemble_len != 2: - raise AnimationException("P: -- ERROR -- Ensemble doesn't contain exactly two frames!") + raise VisualiseException(source, "Ensemble doesn't contain exactly two frames!") else: for frame in ensemble: try: frame_len = len(frame) except TypeError: - raise AnimationException("P: -- ERROR -- At least one frame is empty!") + raise VisualiseException(source, "At least one frame is empty!") if frame_len != 48: - raise AnimationException("P: -- ERROR -- At least one frame doesn't have exactly 48 values!") + raise VisualiseException(source, "At least one frame doesn't have exactly 48 values!") else: is_numeric = True is_positive = True @@ -123,22 +127,22 @@ class Animation: is_small = is_small and value < 256 if not is_numeric: - raise AnimationException("P: -- ERROR -- Not all values in the ensemble are integers!") + raise VisualiseException(source, "Not all values in the ensemble are integers!") elif not is_positive: - raise AnimationException("P: -- ERROR -- Not all values in the ensemble are positive!") + raise VisualiseException(source, "Not all values in the ensemble are positive!") elif not is_small: - raise AnimationException("P: -- ERROR -- One or more values in the ensemble are to large!") + raise VisualiseException(source, "One or more values in the ensemble are to large!") else: if not (0 < pixel < 33): - raise AnimationException("P: -- ERROR -- Pixel has to be in the range of 1 to 32!") + raise VisualiseException(source, "Pixel has to be in the range of 1 to 32!") elif not (0 < r < 256): - raise AnimationException("P: -- ERROR -- Red channel value has to be in the range of 1 to " - "255!") + raise VisualiseException(source, "Red channel value has to be in the range of 1 " + "to 255!") elif not (0 < g < 256): - raise AnimationException("P: -- ERROR -- Green channel value has to be in the range of 1 " + raise VisualiseException(source, "Green channel value has to be in the range of 1 " "to 255!") elif not (0 < b < 256): - raise AnimationException("P: -- ERROR -- Blue channel value has to be in the range of 1 " + raise VisualiseException(source, "Blue channel value has to be in the range of 1 " "to 255!") else: if pixel < 16: diff --git a/src/visualising/arduino.py b/src/visualising/arduino.py index 896deadf5c665091ca087257be35ce25798318e2..6815d6080909f17eced482afb464a44108494f1e 100755 --- a/src/visualising/arduino.py +++ b/src/visualising/arduino.py @@ -1,87 +1,60 @@ #!/usr/bin/env python import time + from visualising.message import FrameMsg, InstrMsg from visualising.animation import Animation -from visualising.exception import ArduinoException +from visualising.exception import VisualiseException class Arduino: - def __init__(self, connection): - self.connection = connection - # Translates a 8-bit message from Arduino and prints the translation. - @staticmethod - def print_response(msg): - arduino_msg = { - 240: "A: -- ERROR -- Animation has to many frame ensembles!", - 241: "A: -- ERROR -- No animation is loaded!", - 242: "A: -- ERROR -- Instruction was not recognized!", - 243: "A: -- ERROR -- No data message expected!", - 244: "A: -- ERROR -- Message has wrong format!", - 245: "A: -- ERROR -- Animation is playing!", - 246: "A: -- ERROR -- Animation has no iteration!", - 247: "A: -- ERROR -- Unequal number of frames for the left and right NeoPixel ring!", - 248: "A: -- ERROR -- Animation has no frame ensemble!", - 0: "A: No Response!", - 15: "A: -- SUCCESS -- Waiting for frames to be send!", - 31: "A: -- SUCCESS -- Animation playback has been started!", - 47: "A: -- SUCCESS -- Frame successfully received!", - 63: "A: -- SUCCESS -- Last frame successfully received!", - 79: "A: -- SUCCESS -- Animation successfully played!", - 95: "A: -- SUCCESS -- Animation playback has been paused!", - 111: "A: -- SUCCESS -- Animation playback has been resumed", - } + 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. - try: - print(arduino_msg[int.from_bytes(msg, byteorder='big')]) - except KeyError: - raise ArduinoException("P: -- ERROR -- Unknown response from 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 ArduinoException("P: -- ERROR -- Animation hast to many frames!") + raise VisualiseException(source, "Animation hast to many frames!") else: - self.connection.send_msg(InstrMsg('A', animation).create_arduino_msg()) - msg = self.connection.receive_msg() + try: + self.connection.confirm_msg(InstrMsg('A', animation).create_msg(), [b'\x0f'], resend) + except VisualiseException: + raise - Arduino.print_response(msg) - if bytes(msg) == bytes(b'\x0f'): - for frame in animation.frames: - self.connection.send_msg(FrameMsg(frame).create_arduino_msg()) - msg = self.connection.receive_msg() - - d = 10 - while bytes(msg) != bytes(b'\x2f') and bytes(msg) != bytes(b'\x3f') and d > 0: - d = d - 1 - Arduino.print_response(msg) - self.connection.send_msg(FrameMsg(frame).create_arduino_msg()) - msg = self.connection.receive_msg() - - if d > 0: - Arduino.print_response(msg) - else: - raise ArduinoException("P: -- ERROR -- Unable to send frame message!") + 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): - self.connection.send_msg(InstrMsg('B').create_arduino_msg()) - Arduino.print_response(self.connection.receive_msg()) + try: + self.connection.confirm_msg(InstrMsg('B').create_msg(), [b'\x1f'], resend) + except VisualiseException: + raise # Creates instruction message and sends it to the Arduino, # to pause a playing animation. def pause_playback(self): - self.connection.send_msg(InstrMsg('C').create_arduino_msg()) - Arduino.print_response(self.connection.receive_msg()) + try: + self.connection.confirm_msg(InstrMsg('C').create_msg(), [b'\x5f'], resend) + except VisualiseException: + raise # Creates instruction message and sends it to the Arduino, # to resume playback of a loaded animation. def resume_playback(self): - self.connection.send_msg(InstrMsg('D').create_arduino_msg()) - Arduino.print_response(self.connection.receive_msg()) + try: + self.connection.confirm_msg(InstrMsg('D').create_msg(), [b'\x6f'], resend) + except VisualiseException: + raise # Streams an animation to the Arduino. For this purpose, two frames of # the animation are repeatedly sent to the Arduino. The Arduino plays diff --git a/src/visualising/connection.py b/src/visualising/connection.py index 745c536dacb5430ccd50bb344aaa0d458b68ba49..f3573903fe673c1da6dc2dfb03334156a83d30a9 100755 --- a/src/visualising/connection.py +++ b/src/visualising/connection.py @@ -3,33 +3,104 @@ import time import serial -from visualising.exception import ConnectionException +from visualising.exception import VisualiseException from serial.serialutil import SerialException class Connection: + + source = "connection.py" # Name of the file that contains the Connection class definition. + 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 ConnectionException("P: -- ERROR -- No serial connection established!") + raise VisualiseException(source, "No serial connection established!") except FileNotFoundError: - raise ConnectionException("P: -- ERROR -- The specified port does not exist!") + raise VisualiseException(source, "The specified port does not exist!") time.sleep(2) # Prevents errors regarding pyserial. - # Send a message. + # Send a message to the Arduino. def send_msg(self, msg): for byte in msg: self.connection.write(byte) self.connection.reset_output_buffer() # Clear serial output buffer. - # Receive a message. + # Receive a message from the Arduino. def receive_msg(self): - msg = self.connection.read(1) + try: + # Since the timeout is set to one second, there is a waiting period of + # exactly one second to read a byte from the serial buffer. If no byte + # is read in this one second period, an exception is thrown. + msg = self.connection.read(1) + except SerialException: # Arduino has not responded in time. + msg = b'\xff' + self.connection.reset_input_buffer() # Clear serial input buffer. return msg + + # Translates a 8-bit message from the Arduino. + @staticmethod + def translate_msg(msg): + translation = { + 240: "Animation has to many frame ensembles!", # 0xF0 + 241: "No animation is loaded!", # 0xF1 + 242: "Instruction was not recognized!", # 0xF2 + 243: "No data message expected!", # 0xF3 + 244: "Message has wrong format!", # 0xF4 + 245: "Animation is playing!", # 0xF5 + 246: "Animation has no iteration!", # 0xF6 + 247: "Unequal number of frames for the left and right NeoPixel ring!", # 0xF7 + 248: "Animation has no frame ensemble!", # 0xF8 + 255: "Timeout!", # 0xFF + 0: "No response!", # 0x00 + 15: "Waiting for frames to be send!", # 0x0F + 31: "Animation playback has been started!", # 0x1F + 47: "Frame successfully received!", # 0x2F + 63: "Last frame successfully received!", # 0x3F + 79: "Animation successfully played!", # 0x4F + 95: "Animation playback has been paused!", # 0x5F + 111: "Animation playback has been resumed!", # 0x6F + } + + try: + return translation[int.from_bytes(msg, byteorder='big')] + except KeyError: + return "Unknown response!" + + # Send a message and confirm that the response is as expected. + # 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 evaluate(): + bool = 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: + 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)) diff --git a/src/visualising/exception.py b/src/visualising/exception.py index 0671296631cea33ac1ea6726685f5eb0d7d77bdb..fad8fca718c024db6128d8a3c93f6c44c94e42f5 100755 --- a/src/visualising/exception.py +++ b/src/visualising/exception.py @@ -4,16 +4,7 @@ class Error(Exception): pass -class AnimationException(Error): - def __init__(self, message): - self.message = message - - -class ConnectionException(Error): - def __init__(self, message): - self.message = message - - -class ArduinoException(Error): - def __init__(self, message): - self.message = message +class VisualiseException(Error): + def __init__(self, source, description): + self.source = source + self.description = description diff --git a/src/visualising/message.py b/src/visualising/message.py index 353426756bef4e748d8578685f52f1da7a29d86c..06ad43177a5636182344c65796caddd0ae433049 100755 --- a/src/visualising/message.py +++ b/src/visualising/message.py @@ -6,7 +6,7 @@ class FrameMsg: # Creates a 50-bit data Message. The first and last bit are intended for authentication # as a data message. The remaining 48 bits store exactly one animation frame. - def create_arduino_msg(self): + def create_msg(self): msg = [bytes('{', 'ascii')] for color in self.frame: @@ -25,7 +25,7 @@ class InstrMsg: # Creates a 50-bit instruction message. Since only 7 bits at most are required for the # actual message, most of the message consists only of zeros. The first and last bit # are intended for authentication as an instruction message. - def create_arduino_msg(self): + def create_msg(self): msg = [bytes('[', 'ascii'), bytes(self.instr, 'ascii')] if self.animation is not None: diff --git a/src/visualising/visualiser.py b/src/visualising/visualiser.py index d5123ca9b82fccb3a21de42790376812988814f5..d7ef32cf18d5505cf4e9a7d9daa3c95c7f3cf84d 100644 --- a/src/visualising/visualiser.py +++ b/src/visualising/visualiser.py @@ -1,49 +1,50 @@ #!/usr/bin/env python import rospy -from visualising.msg import State -from visualising.msg import States +from monitoring.watchdog import Watchdog +from visualising.exception import * from visualising.arduino import Arduino from visualising.connection import Connection from visualising.animation import Animation -from visualising.exception import * class Visualiser: - def __init__(self, port="/dev/ttyUSB0", baud=57600): + def __init__(self): + port = rospy.get_param("/arduino_port", "/dev/ttyUSB0") + baud = rospy.get_param("/arduino_baud", 57600) + self.visualisation_strategy = rospy.get_param("/visualisation_strategy", 1) + self.arduino = Arduino(Connection(port, baud)) - self.robot_state = 0 # Robot starts in a happy state. + self.watchdog = Watchdog() - self.happy = Animation(Animation.read_frames_from_file("animation_happy.txt"), 100, 1) - self.ok = Animation(Animation.read_frames_from_file("animation_ok.txt"), 100, 1) - self.sad = Animation(Animation.read_frames_from_file("animation_sad.txt"), 100, 1) + def visualise(self): + if self.visualisation_strategy == 1: + self.mood_mode() + elif self.visualisation_strategy == 2: + self.info_mode() + else: + rospy.logwarn("Visualiser: The specified strategy is not recognised by the visualiser!") - rospy.Subscriber("states", States, self.evaluate) + # TODO: Add all basic emotions! + def mood_mode(self): + happy = Animation(Animation.read_frames_from_file("animation_happy.txt"), 100, 1) + ok = Animation(Animation.read_frames_from_file("animation_ok.txt"), 100, 1) + sad = Animation(Animation.read_frames_from_file("animation_sad.txt"), 100, 1) - animation_duration = 1 - self.timer = rospy.Timer(rospy.Duration(animation_duration), self.update_mood) + while not rospy.is_shutdown(): + if self.watchdog.aggregated_domains > 0.7: + self.stream_animation(sad) + else: + if self.watchdog.aggregated_domains > 0.4: + self.stream_animation(ok) + else: + self.stream_animation(happy) - # TODO: Add different modes! - def evaluate(self, event): - number = 0 - value = 0 - for state in event.states: - value = value + state.critical - number = number + 1 - self.robot_state = value / number - # TODO: Add different modes! - def update_mood(self, event): - print(self.robot_state) - if self.robot_state > 0.7: - self.stream_animation(self.sad) - else: - if self.robot_state > 0.4: - self.stream_animation(self.ok) - else: - self.stream_animation(self.happy) + def info_mode(self): + print("Hallo!") def stream_animation(self, animation): - self.arduino.stream_animation(animation) + self.arduino.stream_animation(animation) \ No newline at end of file