From cd00d37b5c0cf6194b6b2c78d777ade6a5c417a1 Mon Sep 17 00:00:00 2001 From: bav6096 <benedikt.deike@informatik.uni-hamburg.de> Date: Thu, 23 Dec 2021 06:04:53 +0100 Subject: [PATCH] more refactoring --- CMakeLists.txt | 2 +- launch/visualiser.launch | 35 ++++-- ...ild_still_ensemble.py => build_emotion.py} | 13 +- scripts/write.py | 2 +- src/visualising/communication/arduino.py | 51 +++++--- .../communication/channel/connection.py | 39 +++--- .../communication/channel/message.py | 83 ------------ .../channel/message/__init__.py} | 0 .../communication/channel/message/message.py | 9 ++ .../channel/message/msg_frame.py | 37 ++++++ .../channel/message/msg_instr.py | 58 +++++++++ .../communication/channel/message/response.py | 50 ++++++++ .../communication/illustration/animation.py | 40 ++++-- .../illustration/color/__init__.py | 0 .../communication/illustration/color/color.py | 29 +++++ .../communication/illustration/color/rgb.py | 47 +++++++ .../communication/illustration/ensemble.py | 24 +++- .../communication/illustration/frame.py | 50 ++++---- .../communication/illustration/pixel.py | 24 ++-- .../expression/{artificial.py => abstract.py} | 2 +- src/visualising/expression/emotion.py | 106 +++++++++++++--- src/visualising/expression/expression.py | 14 +++ .../expression/library/{ => alphabet}/A.txt | 0 .../expression/library/{ => alphabet}/B.txt | 0 .../expression/library/{ => alphabet}/C.txt | 0 .../expression/library/{ => alphabet}/D.txt | 0 .../expression/library/{ => alphabet}/E.txt | 0 .../expression/library/{ => alphabet}/G.txt | 0 .../expression/library/{ => alphabet}/H.txt | 0 .../expression/library/{ => alphabet}/I.txt | 0 .../expression/library/{ => alphabet}/J.txt | 0 .../expression/library/{ => alphabet}/L.txt | 0 .../expression/library/alphabet/M.txt | 2 + .../expression/library/alphabet/N.txt | 2 + .../expression/library/{ => alphabet}/O.txt | 0 .../expression/library/alphabet/R.txt | 2 + .../expression/library/alphabet/S.txt | 2 + .../expression/library/alphabet/T.txt | 2 + .../expression/library/alphabet/__init__.py | 0 .../expression/library/emotion/__init__.py | 0 .../library/emotion/expression_angry.txt | 2 + .../{ => emotion}/expression_clean.txt | 0 .../library/emotion/expression_happy.txt | 2 + .../library/emotion/expression_ok.txt | 2 + .../expression/library/expression_angry.txt | 2 - .../expression/library/expression_happy.txt | 2 - src/visualising/expression/library/test.txt | 2 + src/visualising/expression/tool.py | 118 +++++------------- src/visualising/monitoring/watchdog.py | 1 + src/visualising/visualiser.py | 6 +- 50 files changed, 568 insertions(+), 294 deletions(-) rename scripts/{build_still_ensemble.py => build_emotion.py} (72%) delete mode 100755 src/visualising/communication/channel/message.py rename src/visualising/{expression/library/expression_ok.txt => communication/channel/message/__init__.py} (100%) create mode 100755 src/visualising/communication/channel/message/message.py create mode 100644 src/visualising/communication/channel/message/msg_frame.py create mode 100644 src/visualising/communication/channel/message/msg_instr.py create mode 100644 src/visualising/communication/channel/message/response.py create mode 100644 src/visualising/communication/illustration/color/__init__.py create mode 100644 src/visualising/communication/illustration/color/color.py create mode 100644 src/visualising/communication/illustration/color/rgb.py rename src/visualising/expression/{artificial.py => abstract.py} (95%) create mode 100644 src/visualising/expression/expression.py rename src/visualising/expression/library/{ => alphabet}/A.txt (100%) rename src/visualising/expression/library/{ => alphabet}/B.txt (100%) rename src/visualising/expression/library/{ => alphabet}/C.txt (100%) rename src/visualising/expression/library/{ => alphabet}/D.txt (100%) rename src/visualising/expression/library/{ => alphabet}/E.txt (100%) rename src/visualising/expression/library/{ => alphabet}/G.txt (100%) rename src/visualising/expression/library/{ => alphabet}/H.txt (100%) rename src/visualising/expression/library/{ => alphabet}/I.txt (100%) rename src/visualising/expression/library/{ => alphabet}/J.txt (100%) rename src/visualising/expression/library/{ => alphabet}/L.txt (100%) create mode 100644 src/visualising/expression/library/alphabet/M.txt create mode 100644 src/visualising/expression/library/alphabet/N.txt rename src/visualising/expression/library/{ => alphabet}/O.txt (100%) create mode 100644 src/visualising/expression/library/alphabet/R.txt create mode 100644 src/visualising/expression/library/alphabet/S.txt create mode 100644 src/visualising/expression/library/alphabet/T.txt create mode 100644 src/visualising/expression/library/alphabet/__init__.py create mode 100644 src/visualising/expression/library/emotion/__init__.py create mode 100644 src/visualising/expression/library/emotion/expression_angry.txt rename src/visualising/expression/library/{ => emotion}/expression_clean.txt (100%) create mode 100644 src/visualising/expression/library/emotion/expression_happy.txt create mode 100644 src/visualising/expression/library/emotion/expression_ok.txt delete mode 100644 src/visualising/expression/library/expression_angry.txt delete mode 100644 src/visualising/expression/library/expression_happy.txt create mode 100644 src/visualising/expression/library/test.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index a9d3016..d486f68 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -162,7 +162,7 @@ catkin_install_python(PROGRAMS nodes/visualiser scripts/play_animation.py scripts/reset_display.py - scripts/build_still_ensemble.py + scripts/build_emotion.py scripts/build_state_cycle.py scripts/write.py DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} diff --git a/launch/visualiser.launch b/launch/visualiser.launch index eed1fb9..d0e9d06 100644 --- a/launch/visualiser.launch +++ b/launch/visualiser.launch @@ -1,22 +1,39 @@ <launch> <node pkg="visualising" type="visualiser" name="visualiser"> - <param name="/wdg_freq" value="2.0" type="double" /> - <param name="/vis_freq" value="2.0" type="double" /> + <!-- frequency at which the watchdog aggregates metrics and domains --> + <param name="/wdg_freq" value="0.2" type="double" /> + <!-- + frequency at which the visualisation of the robot state is updated, + must be lesser than 0.2 and only is relevant for the emotion + visualisation + --> + <param name="/vis_freq" value="0.2" type="double" /> - <param name="/arduino_port" value="/ttyUSB0" type="str" /> + <param name="/arduino_port" value="/dev/ttyUSB0" type="str" /> <param name="/arduino_baud" value="57600" type="int" /> <!-- set visualisation strategy --> + <!-- 1: emotion --> + <!-- 2: abstract --> <param name="/visualisation_strategy" value="1" type="int" /> - <!-- example of how to set an aggregation strategy for a metric --> + <!-- example of how to set an aggregation strategy for a metric --> + <!-- strategy 1 : Take the highest error level of any metric in a domain. --> + <!-- strategy 2 : Take the lowest error level of any metric in a domain. --> + <!-- strategy 0 | default strategy : Take the average error level of every metric in a domain. --> <rosparam param="aggregation_strategy_metrics"> - example_1: 10 - example_2: 10 - test_domain_1: 0 - test_domain_2: 0 + <!-- metric name: metric aggregation strategy --> + + example_1: 0 <!-- default --> + example_2: 0 <!-- default --> + test_domain_1: 0 <!-- default --> + test_domain_2: 0 <!-- default --> + test_domain_3: 0 <!-- default --> </rosparam> <!-- set the aggregation strategy for domains --> - <param name="aggregation_strategy_domains" value="0" type="int" /> + <!-- strategy 1 : Take the highest error level of any domain. --> + <!-- strategy 2 : Take the lowes error level of any domain. --> + <!-- strategy 0 | default strategy : Take the average error level of every domain. --> + <param name="aggregation_strategy_domains" value="2" type="int" /> </node> </launch> diff --git a/scripts/build_still_ensemble.py b/scripts/build_emotion.py similarity index 72% rename from scripts/build_still_ensemble.py rename to scripts/build_emotion.py index 620282a..9c9ead4 100755 --- a/scripts/build_still_ensemble.py +++ b/scripts/build_emotion.py @@ -3,8 +3,10 @@ import argparse from visualising.expression.tool import Tool +from visualising.expression.emotion import Emotion from visualising.communication.arduino import Arduino from visualising.communication.channel.connection import Connection +from visualising.communication.illustration.color.rgb import RGB parser = argparse.ArgumentParser(description="script to play an animation") parser.add_argument("-p", "--port", help="port to which the Arduino is connected", type=str, default="/dev/ttyUSB0") @@ -18,11 +20,16 @@ baud = args["baud"] file = args["file"] time = args["time"] -color = [10, 10, 10] +# 50, 10, 10 + +color = RGB(10, 0, 0) +print(color.r) +print(color.g) +print(color.b) arduino = Arduino(Connection(port, baud)) -ensembles = Tool.create_ensembles(file) +ensembles = Tool.create_ensembles(file, "emotion") -arduino.stream_animation(Tool.build_still_ensemble_parallel(ensembles[0], time, color)) +arduino.stream_animation(Emotion.build_emotion_parallel(ensembles[0], time, color)) diff --git a/scripts/write.py b/scripts/write.py index ec35159..447c9b8 100755 --- a/scripts/write.py +++ b/scripts/write.py @@ -19,6 +19,6 @@ time = args["time"] arduino = Arduino(Connection(port, baud)) -animation = Tool.write("HALLO") +animation = Tool.write("HALLONORMAN") arduino.stream_animation(Animation(animation, time, 1)) diff --git a/src/visualising/communication/arduino.py b/src/visualising/communication/arduino.py index 0bfaf9b..c79a4d3 100755 --- a/src/visualising/communication/arduino.py +++ b/src/visualising/communication/arduino.py @@ -2,55 +2,70 @@ import time -from visualising.communication.channel.connection import FrameMsg -from visualising.communication.channel.connection import InstrMsg -from visualising.communication.channel.connection import ArduinoMsg +from visualising.communication.channel.connection import Connection +from visualising.communication.channel.message.msg_frame import MsgFrame +from visualising.communication.channel.message.msg_instr import MsgInstr +from visualising.communication.channel.message.response import Response from visualising.communication.illustration.animation import Animation class Arduino: def __init__(self, connection): - self.connection = connection + self._connection = connection + + @property + def connection(self): + return self._connection + + @connection.setter + def connection(self, connection): + if not isinstance(connection, Connection): + raise TypeError("The parameter connection must be an object of the Connection class!") + self._connection = connection # Creates instruction message and sends it to the Arduino, # to play a loaded animation. def start_playback(self): - self.connection.confirm_msg(InstrMsg('B'), [ArduinoMsg(b'\x1f')], 5) + self._connection.confirm_msg(MsgInstr('B'), [Response(b'\x1f')], 5) # Creates instruction message and sends it to the Arduino, # to pause a playing animation. def pause_playback(self): - self.connection.confirm_msg(InstrMsg('C'), [ArduinoMsg(b'\x5f')], 5) + self._connection.confirm_msg(MsgInstr('C'), [Response(b'\x5f')], 5) # Creates instruction message and sends it to the Arduino, # to resume playback of a loaded animation. def resume_playback(self): - self.connection.confirm_msg(InstrMsg('D'), [ArduinoMsg(b'\x6f')], 5) + self._connection.confirm_msg(MsgInstr('D'), [Response(b'\x6f')], 5) # Creates instruction message and sends it to the Arduino, # to clear the NeoPixel rings. def reset_display(self): - self.connection.confirm_msg(InstrMsg('E'), [ArduinoMsg(b'\x7f')], 5) + self._connection.confirm_msg(MsgInstr('E'), [Response(b'\x7f')], 5) # Creates one instruction messages and multiple frame messages to load an animation # onto the Arduino. def load_animation(self, animation): - if len(animation.ensembles) > 16: - raise ValueError("Animation has to many ensembles!") + if not isinstance(animation, Animation) or len(animation.ensembles) > 16: + raise ValueError("The animation parameter must be an object of the Animation class, which may contain a " + "maximum of 16 ensembles!") - self.connection.confirm_msg(InstrMsg('A', animation), [ArduinoMsg(b'\x0f')], 5) + self._connection.confirm_msg(MsgInstr('A', animation), [Response(b'\x0f')], 5) for ensemble in animation.ensembles: l_frame = ensemble.l_frame r_frame = ensemble.r_frame - self.connection.confirm_msg(FrameMsg(l_frame), [ArduinoMsg(b'\x2f'), ArduinoMsg(b'\x3f')], 10) - self.connection.confirm_msg(FrameMsg(r_frame), [ArduinoMsg(b'\x2f'), ArduinoMsg(b'\x3f')], 10) + self._connection.confirm_msg(MsgFrame(l_frame), [Response(b'\x2f'), Response(b'\x3f')], 10) + self._connection.confirm_msg(MsgFrame(r_frame), [Response(b'\x2f'), Response(b'\x3f')], 10) # 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): + if not isinstance(animation, Animation): + raise TypeError("The parameter animation must be an object of the Animation class!") + self.load_animation(animation) self.start_playback() @@ -59,7 +74,7 @@ class Arduino: # Current time in seconds. curr_time = time.time() - while not ArduinoMsg(b'\x4f').compare(self.connection.receive_msg()): + while not Response(b'\x4f').compare(self._connection.receive_msg()): # Wait 10 times the estimated animation playback time for a response. if time.time() > curr_time + playback_time * 10: raise ArduinoException("Successful playback of animation can't be confirmed!") @@ -69,11 +84,9 @@ class Arduino: # the received ensemble and then confirms the playback, whereupon a new # ensemble is sent. def stream_animation(self, animation): + if not isinstance(animation, Animation): + raise TypeError("The parameter animation must be an object of the Animation class!") + for _ in range(animation.num_iter): for ensemble in animation.ensembles: self.play_animation(Animation([ensemble], animation.ensemble_time, 1)) - - # Streams consecutively animations to the Arduino. - def stream_multiple_animations(self, animation_list): - for animation in animation_list: - self.stream_animation(animation) diff --git a/src/visualising/communication/channel/connection.py b/src/visualising/communication/channel/connection.py index 0f76509..c6fb34c 100755 --- a/src/visualising/communication/channel/connection.py +++ b/src/visualising/communication/channel/connection.py @@ -3,9 +3,10 @@ import time import serial -from visualising.communication.channel.message import FrameMsg -from visualising.communication.channel.message import InstrMsg -from visualising.communication.channel.message import ArduinoMsg +from visualising.communication.channel.message.message import Message +from visualising.communication.channel.message.response import Response +from visualising.communication.channel.message.msg_frame import MsgFrame +from visualising.communication.channel.message.msg_instr import MsgInstr class ArduinoException(Exception): @@ -26,7 +27,10 @@ class Connection: # Send a message to the Arduino. def send_msg(self, msg): - for byte in msg.content: + if not isinstance(msg, Message): + raise TypeError("The parameter msg must be an object of the Message class!") + + for byte in msg.export_message(): self.connection.write(byte) self.connection.reset_output_buffer() # Clear serial output buffer. @@ -36,35 +40,40 @@ class Connection: 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. + # is read in this time period, an exception is thrown. byte = self.connection.read(1) except SerialException: # Arduino has not responded in time. byte = b'\xff' self.connection.reset_input_buffer() # Clear serial input buffer. - return ArduinoMsg(byte) + return Response(byte) # 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, responses, resends): + def confirm_msg(self, msg, expectations, resends): + if not isinstance(msg, Message): + raise TypeError("The parameter msg must be an object of the Message class!") + if not isinstance(expectations, list) or not all(isinstance(expectation, Response) for expectation in + expectations): + raise TypeError("The parameter responses must be a list of objects of the Response class!") + if not isinstance(resends, int) or not resends > 0: + raise ValueError("The parameter resends must be an integer greater 0!") + def evaluate(): bool = resends > 0 - for exp_resp in responses: - bool = bool and not resp.compare(exp_resp) + for expectation in expectations: + bool = bool and not response.compare(expectation) return bool - if not resends > 0: - raise ValueError("Number of resends must be greater than zero!") - while True: self.send_msg(msg) - resp = self.receive_msg() + response = self.receive_msg() if not evaluate(): break - resends -= 1 + resends = resends - 1 if not resends > 0: - raise ArduinoException(resp.desc) + raise ArduinoException(response.desc) diff --git a/src/visualising/communication/channel/message.py b/src/visualising/communication/channel/message.py deleted file mode 100755 index 5150bef..0000000 --- a/src/visualising/communication/channel/message.py +++ /dev/null @@ -1,83 +0,0 @@ -#!/usr/bin/env python - -class FrameMsg: - # Creates a 50-bit frame message. The first and last bit are intended for authentication - # as a frame message. The remaining 48 bits store exactly one frame. - def __init__(self, frame): - self.content = [bytes('{', 'ascii')] - - for pixel in frame.pixels: - r = pixel.r - g = pixel.g - b = pixel.b - - self.content.append(r.to_bytes(1, byteorder='big')) - self.content.append(g.to_bytes(1, byteorder='big')) - self.content.append(b.to_bytes(1, byteorder='big')) - - self.content.append(bytes('}', 'ascii')) - - -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 __init__(self, instr, animation=None): - self.content = [bytes('[', 'ascii'), bytes(instr, 'ascii')] - - if animation is not None: - num_frames = len(animation.ensembles) * 2 - self.content.append(num_frames.to_bytes(1, byteorder='big')) - - ensemble_time_bytes = animation.ensemble_time.to_bytes(4, byteorder='big') - self.content.append((ensemble_time_bytes[0]).to_bytes(1, byteorder='big')) - self.content.append((ensemble_time_bytes[1]).to_bytes(1, byteorder='big')) - self.content.append((ensemble_time_bytes[2]).to_bytes(1, byteorder='big')) - self.content.append((ensemble_time_bytes[3]).to_bytes(1, byteorder='big')) - - self.content.append(animation.num_iter.to_bytes(1, byteorder='big')) - - for _ in range(41): - self.content.append((0).to_bytes(1, byteorder='big')) - else: - for _ in range(47): - self.content.append((0).to_bytes(1, byteorder='big')) - - self.content.append(bytes(']', 'ascii')) - - -class ArduinoMsg: - # Generates an Arduino message, which is composed of a message byte from the Arduino - # and a translation of the message byte. - def __init__(self, byte): - self.content = byte - - translation = { - 240: "Animation has to many frame ensembles!", # 0xF0 - 241: "No entities is loaded!", # 0xF1 - 242: "Instruction was not recognized!", # 0xF2 - 243: "No entities 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_library 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_library 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 - 127: "Displays have been cleared!", # 0x7F - } - - try: - self.desc = translation[int.from_bytes(byte, byteorder='big')] - except KeyError: - self.desc = "Unknown response!" - - def compare(self, msg): - return bytes(self.content) == bytes(msg.content) diff --git a/src/visualising/expression/library/expression_ok.txt b/src/visualising/communication/channel/message/__init__.py similarity index 100% rename from src/visualising/expression/library/expression_ok.txt rename to src/visualising/communication/channel/message/__init__.py diff --git a/src/visualising/communication/channel/message/message.py b/src/visualising/communication/channel/message/message.py new file mode 100755 index 0000000..06617b8 --- /dev/null +++ b/src/visualising/communication/channel/message/message.py @@ -0,0 +1,9 @@ +#!/usr/bin/env python + +from abc import ABC, abstractmethod + + +class Message(ABC): + @abstractmethod + def export_message(self): + pass diff --git a/src/visualising/communication/channel/message/msg_frame.py b/src/visualising/communication/channel/message/msg_frame.py new file mode 100644 index 0000000..6403697 --- /dev/null +++ b/src/visualising/communication/channel/message/msg_frame.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python + +from visualising.communication.channel.message.message import Message +from visualising.communication.illustration.frame import Frame + + +class MsgFrame(Message): + def __init__(self, frame): + self._frame = frame + + @property + def frame(self): + return self._frame + + @frame.setter + def frame(self, frame): + if not isinstance(frame, Frame): + raise TypeError("The parameter frame must be an object of the Frame class!") + self._frame = frame + + # Creates a 50-bit frame message. The first and last bit are intended for authentication + # as a frame message. The remaining 48 bits store exactly one frame. + def export_message(self): + message = [bytes('{', 'ascii')] + + for pixel in self._frame.pixels: + r = pixel.color.r + g = pixel.color.g + b = pixel.color.b + + message.append(r.to_bytes(1, byteorder='big')) + message.append(g.to_bytes(1, byteorder='big')) + message.append(b.to_bytes(1, byteorder='big')) + + message.append(bytes('}', 'ascii')) + + return message diff --git a/src/visualising/communication/channel/message/msg_instr.py b/src/visualising/communication/channel/message/msg_instr.py new file mode 100644 index 0000000..9b927ed --- /dev/null +++ b/src/visualising/communication/channel/message/msg_instr.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python + +from visualising.communication.channel.message.message import Message +from visualising.communication.illustration.animation import Animation + + +class MsgInstr(Message): + def __init__(self, instr, animation=None): + self._instr = instr + self._animation = animation + + @property + def instr(self): + return self._instr + + @instr.setter + def instr(self, instr): + if instr != "A" and instr != "B" and instr != "C" and instr != "D" and instr != "E": + raise ValueError("Unknown instruction!") + self._instr = instr + + @property + def animation(self): + return self._animation + + @animation.setter + def animation(self, animation): + if not isinstance(animation, Animation): + raise TypeError("The parameter animation must be an object of the Animation class!") + self._animation = animation + + # 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 export_message(self): + message = [bytes('[', 'ascii'), bytes(self._instr, 'ascii')] + + if self._animation is not None: + num_frames = len(self._animation.ensembles) * 2 + message.append(num_frames.to_bytes(1, byteorder='big')) + + ensemble_time_bytes = self._animation.ensemble_time.to_bytes(4, byteorder='big') + message.append((ensemble_time_bytes[0]).to_bytes(1, byteorder='big')) + message.append((ensemble_time_bytes[1]).to_bytes(1, byteorder='big')) + message.append((ensemble_time_bytes[2]).to_bytes(1, byteorder='big')) + message.append((ensemble_time_bytes[3]).to_bytes(1, byteorder='big')) + + message.append(self._animation.num_iter.to_bytes(1, byteorder='big')) + + for _ in range(41): + message.append((0).to_bytes(1, byteorder='big')) + else: + for _ in range(47): + message.append((0).to_bytes(1, byteorder='big')) + + message.append(bytes(']', 'ascii')) + + return message diff --git a/src/visualising/communication/channel/message/response.py b/src/visualising/communication/channel/message/response.py new file mode 100644 index 0000000..30c439a --- /dev/null +++ b/src/visualising/communication/channel/message/response.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python + +class Response: + # Generates an Arduino message, which is composed of a message byte from the Arduino + # and a translation of the message byte. + def __init__(self, byte): + self._byte = byte + + @property + def byte(self): + return self._byte + + @byte.setter + def byte(self, byte): + if not isinstance(byte, bytes): + raise TypeError("The parameter byte must be an object of the byte class!") + self._byte = byte + + def translate(self): + translation = { + 240: "Animation has to many frame ensembles!", # 0xF0 + 241: "No entities is loaded!", # 0xF1 + 242: "Instruction was not recognized!", # 0xF2 + 243: "No entities 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_library 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_library 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 + 127: "Displays have been cleared!", # 0x7F + } + + try: + return translation[int.from_bytes(self._byte, byteorder='big')] + except KeyError: + return "Unknown response!" + + def compare(self, response): + if not isinstance(response, Response): + raise TypeError("The parameter response must be an object of the Response class!") + return bytes(self._byte) == bytes(response.byte) diff --git a/src/visualising/communication/illustration/animation.py b/src/visualising/communication/illustration/animation.py index f6d4ed5..e622d90 100755 --- a/src/visualising/communication/illustration/animation.py +++ b/src/visualising/communication/illustration/animation.py @@ -5,19 +5,37 @@ from visualising.communication.illustration.ensemble import Ensemble class Animation: def __init__(self, ensembles, ensemble_time=0, num_iter=1): - self.ensembles = ensembles + self._ensembles = ensembles + self._ensemble_time = ensemble_time + self._num_iter = num_iter - if ensemble_time < 0: - raise ValueError("Ensemble time must be positive!") - if ensemble_time > 4294967295: - raise ValueError("Ensemble time is to high!") + @property + def ensembles(self): + return self._ensembles + @ensembles.setter + def ensembles(self, ensembles): + if not isinstance(ensembles, Ensemble): + raise TypeError("The parameter ensembles must be an object of the Ensemble class!") + self._ensembles = ensembles + + @property + def ensemble_time(self): + return self._ensemble_time + + @ensemble_time.setter + def ensemble_time(self, ensemble_time): + if not isinstance(ensemble_time, int) or not 0 <= ensemble_time <= 4294967295: + raise ValueError("The parameter ensemble_time must be an integer between 0 an 4294967295!") # The ensemble time is given in milliseconds. - self.ensemble_time = ensemble_time + self._ensemble_time = ensemble_time - if num_iter > 255: - raise ValueError("Animation has to many iterations!") - if num_iter <= 0: - raise ValueError("Animation must have at least one iteration!") + @property + def num_iter(self): + return self._num_iter - self.num_iter = num_iter + @num_iter.setter + def num_iter(self, num_iter): + if not isinstance(num_iter, int) or not 1 <= num_iter <= 255: + raise ValueError("The parameter num_iter must be an integer between 1 an 255!") + self._num_iter = num_iter diff --git a/src/visualising/communication/illustration/color/__init__.py b/src/visualising/communication/illustration/color/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/visualising/communication/illustration/color/color.py b/src/visualising/communication/illustration/color/color.py new file mode 100644 index 0000000..de6edd4 --- /dev/null +++ b/src/visualising/communication/illustration/color/color.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python + +from abc import ABC, abstractmethod + + +class Color(ABC): + @property + @abstractmethod + def r(self): + pass + + @property + @abstractmethod + def g(self): + pass + + @property + @abstractmethod + def b(self): + pass + + @property + @abstractmethod + def illuminated(self): + pass + + @abstractmethod + def export_color(self): + pass diff --git a/src/visualising/communication/illustration/color/rgb.py b/src/visualising/communication/illustration/color/rgb.py new file mode 100644 index 0000000..c874cd1 --- /dev/null +++ b/src/visualising/communication/illustration/color/rgb.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python + +from visualising.communication.illustration.color.color import Color + + +class RGB(Color): + def __init__(self, r, g, b): + self._r = r + self._g = g + self._b = b + + @property + def r(self): + return self._r + + @r.setter + def r(self, r): + if not isinstance(r, int) or not 0 <= r <= 255: + raise ValueError("The parameter r must be an integer between 0 and 255!") + self._r = r + + @property + def g(self): + return self._g + + @g.setter + def g(self, g): + if not isinstance(g, int) or not 0 <= g <= 255: + raise ValueError("The parameter g must be an integer between 0 and 255!") + self._g = g + + @property + def b(self): + return self._b + + @b.setter + def b(self, b): + if not isinstance(b, int) or not 0 <= b <= 255: + raise ValueError("The parameter b must be an integer between 0 and 255!") + self._b = b + + @property + def illuminated(self): + return self._r + self._g + self._b != 0 + + def export_color(self): + return [self._r, self._g, self._b] \ No newline at end of file diff --git a/src/visualising/communication/illustration/ensemble.py b/src/visualising/communication/illustration/ensemble.py index 6a6ca87..3e17571 100644 --- a/src/visualising/communication/illustration/ensemble.py +++ b/src/visualising/communication/illustration/ensemble.py @@ -5,5 +5,25 @@ from visualising.communication.illustration.frame import Frame class Ensemble: def __init__(self, l_frame, r_frame): - self.l_frame = l_frame - self.r_frame = r_frame + self._l_frame = l_frame + self._r_frame = r_frame + + @property + def l_frame(self): + return self._l_frame + + @l_frame.setter + def l_frame(self, l_frame): + if not isinstance(l_frame, Frame): + raise TypeError("The parameter left must be an object of the Frame class!") + self._l_frame = l_frame + + @property + def r_frame(self): + return self._r_frame + + @r_frame.setter + def r_frame(self, r_frame): + if not isinstance(r_frame, Frame): + raise TypeError("The parameter right must be an object of the Frame class!") + self._r_frame = r_frame diff --git a/src/visualising/communication/illustration/frame.py b/src/visualising/communication/illustration/frame.py index 16f36f1..bab4750 100644 --- a/src/visualising/communication/illustration/frame.py +++ b/src/visualising/communication/illustration/frame.py @@ -1,33 +1,31 @@ #!/usr/bin/env python from visualising.communication.illustration.pixel import Pixel +from visualising.communication.illustration.color.color import Color class Frame: def __init__(self, pixels): - if len(pixels) != 16: - raise ValueError("Frames must have exactly 16 pixels!") - - self.pixels = pixels - - def is_colored(self, pixel): - if not 0 <= pixel <= 15: - raise ValueError("Pixel index must be an integer between 0 and 15!") - - return self.pixels[pixel].r + self.pixels[pixel].g + self.pixels[pixel].b != 0 - - def adjust_brightness(self, pixel, factor): - if not 0 <= pixel <= 15: - raise ValueError("Pixel index must be an integer between 0 and 15!") - - r = min(max(self.frame[pixel].r * factor, 0), 255) - g = min(max(self.frame[pixel].g * factor, 0), 255) - b = min(max(self.frame[pixel].b * factor, 0), 255) - - self.pixels[pixel] = Pixel(r, g, b) - - def adjust_color(self, pixel, r, g, b): - if not 0 <= pixel <= 15: - raise ValueError("Pixel index must be an integer between 0 and 15!") - - self.pixels[pixel] = Pixel(r, g, b) + self._pixels = pixels + + @property + def pixels(self): + return self._pixels + + @pixels.setter + def pixels(self, pixels): + if not isinstance(pixels, list) or not all(isinstance(pixel, Pixel) for pixel in pixels) or len(pixels) != 16: + raise ValueError("The parameter pixels must be a list of exactly 16 objects of the Pixel class!") + self._pixels = pixels + + def illuminated(self, index): + if not isinstance(index, int) or not 0 <= index <= 15: + raise ValueError("The parameter index must be an integer between 0 and 15!") + return self._pixels[index].color.illuminated + + def replace_pixel(self, index, pixel): + if not isinstance(index, int) or not 0 <= index <= 15: + raise ValueError("The parameter index must be an integer between 0 and 15!") + if not isinstance(pixel, Pixel): + raise TypeError("The parameter pixel must be an object of the Pixel class!") + self._pixels[index] = pixel diff --git a/src/visualising/communication/illustration/pixel.py b/src/visualising/communication/illustration/pixel.py index 8bf68ce..2e2c975 100644 --- a/src/visualising/communication/illustration/pixel.py +++ b/src/visualising/communication/illustration/pixel.py @@ -1,13 +1,19 @@ #!/usr/bin/env python +from visualising.communication.illustration.color.color import Color + + class Pixel: - def __init__(self, r, g, b): - if r < 0 or g < 0 or b < 0: - raise ValueError("All pixel color values must be positive!") - if r > 255 or g > 255 or b > 255: - raise ValueError("All pixel color values must be smaller than 256!") - - self.r = int(r) - self.g = int(g) - self.b = int(b) + def __init__(self, color): + self._color = color + + @property + def color(self): + return self._color + + @color.setter + def color(self, color): + if not isinstance(color, Color): + raise TypeError("The parameter color must be an object of the Color class!") + self._color = color diff --git a/src/visualising/expression/artificial.py b/src/visualising/expression/abstract.py similarity index 95% rename from src/visualising/expression/artificial.py rename to src/visualising/expression/abstract.py index bc73772..a352fa1 100644 --- a/src/visualising/expression/artificial.py +++ b/src/visualising/expression/abstract.py @@ -4,7 +4,7 @@ import numpy as np from visualising.expression.tool import Tool -class State: +class Abstract: def __init__(self, arduino): self.arduino = arduino self.playback_start = 0 diff --git a/src/visualising/expression/emotion.py b/src/visualising/expression/emotion.py index bb37410..b514670 100644 --- a/src/visualising/expression/emotion.py +++ b/src/visualising/expression/emotion.py @@ -1,37 +1,105 @@ import time +from visualising.communication.arduino import Arduino +from visualising.communication.illustration.animation import Animation +from visualising.communication.illustration.color.rgb import RGB +from visualising.expression.expression import Expression from visualising.expression.tool import Tool -class Emotion: +class Emotion(Expression): def __init__(self, arduino): - self.arduino = arduino + self._arduino = arduino self.playback_start = 0 + self.refresh = 5 - def react(self, state): - if time().time - playback_start > 5: + self.happy = "expression_happy.txt" + self.ok = "expression_ok.txt" + self.angry = "expression_angry.txt" + + @property + def arduino(self): + return self._arduino - color = Tool.state_to_color(state[0], [25, 25, 25], [25, 0, 0]) + @arduino.setter + def arduino(self, arduino): + if not isinstance(arduino, Arduino): + raise TypeError("The parameter arduino must be an object of the Arduino class!") + self._arduino = arduino + def react(self, state): + if time.time() - self.playback_start > self.refresh: + color = Tool.state_to_color(state[0], RGB(25, 25, 25), RGB(25, 0, 0)) if state[0] > 0.7: - self.happy(color) + self.visualise(self.angry, color) else: if state[0] > 0.3: - self.ok(color) + self.visualise(self.ok, color) else: - self.angry(color) + self.visualise(self.happy, color) - def happy(self, color): - ensembles = Tool.create_ensembles("expression_happy.txt") - self.arduino.stream_animation(Tool.build_still_ensemble_parallel(ensembles[0], 50, color)) + def visualise(self, filepath, color): + ensembles = Tool.create_ensembles(filepath, "emotion") + self.arduino.stream_animation(Emotion.build_emotion_parallel(ensembles[0], 50, color)) self.playback_start = time.time() - def ok(self, color): - ensembles = Tool.create_ensembles("expression_ok.txt") - self.arduino.stream_animation(Tool.build_still_ensemble_parallel(ensembles[0], 50, color)) - self.playback_start = time.time() + @staticmethod + def build_emotion(blueprint, time, color): + ensembles = [] + for i in range(32): + ensemble = Tool.create_empty_ensemble() + for j in range(i + 1): + if j < 16: + illuminated = blueprint.l_frame.illuminated(j) + else: + illuminated = bluepring.r_frame.illuminated(j % 16) + + if illuminated or j == i: + ensemble = Tool.set_pixel_color(ensemble, j, color) + + ensembles.append(ensemble) + + ensemble = Tool.create_empty_ensemble() + for k in range(32): + if k < 16: + illuminated = blueprint.l_frame.illuminated(k) + else: + illuminated = blueprint.r_frame.illuminated(k % 16) + + if illuminated: + ensemble = Tool.set_pixel_color(ensemble, k, color) + + ensembles.append(ensemble) + + return Animation(ensembles, time, 1) + + @staticmethod + def build_emotion_parallel(blueprint, time, color): + ensembles = [] + for i in range(16): + ensemble = Tool.create_empty_ensemble() + for j in range(i + 1): + if blueprint.l_frame.illuminated(j): + ensemble = Tool.set_pixel_color(ensemble, j, color) + if blueprint.r_frame.illuminated(j): + ensemble = Tool.set_pixel_color(ensemble, 16 + j, color) + if j == i: + ensemble = Tool.set_pixel_color(ensemble, j, color) + ensemble = Tool.set_pixel_color(ensemble, 16 + j, color) + + ensembles.append(ensemble) + + ensemble = Tool.create_empty_ensemble() + for k in range(32): + if k < 16: + illuminated = blueprint.l_frame.illuminated(k) + else: + illuminated = blueprint.r_frame.illuminated(k % 16) + + if illuminated: + ensemble = Tool.set_pixel_color(ensemble, k, color) + + ensembles.append(ensemble) + + return Animation(ensembles, time, 1) - def angry(self, color): - ensembles = Tool.create_ensembles("expression_angry.txt") - self.arduino.stream_animation(Tool.build_still_ensemble_parallel(ensembles[0], 50, color)) - self.playback_start = time.time() diff --git a/src/visualising/expression/expression.py b/src/visualising/expression/expression.py new file mode 100644 index 0000000..2aa033f --- /dev/null +++ b/src/visualising/expression/expression.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python + +from abc import ABC, abstractmethod + + +class Expression(ABC): + @property + @abstractmethod + def arduino(self): + pass + + @abstractmethod + def react(self, state): + pass diff --git a/src/visualising/expression/library/A.txt b/src/visualising/expression/library/alphabet/A.txt similarity index 100% rename from src/visualising/expression/library/A.txt rename to src/visualising/expression/library/alphabet/A.txt diff --git a/src/visualising/expression/library/B.txt b/src/visualising/expression/library/alphabet/B.txt similarity index 100% rename from src/visualising/expression/library/B.txt rename to src/visualising/expression/library/alphabet/B.txt diff --git a/src/visualising/expression/library/C.txt b/src/visualising/expression/library/alphabet/C.txt similarity index 100% rename from src/visualising/expression/library/C.txt rename to src/visualising/expression/library/alphabet/C.txt diff --git a/src/visualising/expression/library/D.txt b/src/visualising/expression/library/alphabet/D.txt similarity index 100% rename from src/visualising/expression/library/D.txt rename to src/visualising/expression/library/alphabet/D.txt diff --git a/src/visualising/expression/library/E.txt b/src/visualising/expression/library/alphabet/E.txt similarity index 100% rename from src/visualising/expression/library/E.txt rename to src/visualising/expression/library/alphabet/E.txt diff --git a/src/visualising/expression/library/G.txt b/src/visualising/expression/library/alphabet/G.txt similarity index 100% rename from src/visualising/expression/library/G.txt rename to src/visualising/expression/library/alphabet/G.txt diff --git a/src/visualising/expression/library/H.txt b/src/visualising/expression/library/alphabet/H.txt similarity index 100% rename from src/visualising/expression/library/H.txt rename to src/visualising/expression/library/alphabet/H.txt diff --git a/src/visualising/expression/library/I.txt b/src/visualising/expression/library/alphabet/I.txt similarity index 100% rename from src/visualising/expression/library/I.txt rename to src/visualising/expression/library/alphabet/I.txt diff --git a/src/visualising/expression/library/J.txt b/src/visualising/expression/library/alphabet/J.txt similarity index 100% rename from src/visualising/expression/library/J.txt rename to src/visualising/expression/library/alphabet/J.txt diff --git a/src/visualising/expression/library/L.txt b/src/visualising/expression/library/alphabet/L.txt similarity index 100% rename from src/visualising/expression/library/L.txt rename to src/visualising/expression/library/alphabet/L.txt diff --git a/src/visualising/expression/library/alphabet/M.txt b/src/visualising/expression/library/alphabet/M.txt new file mode 100644 index 0000000..614e924 --- /dev/null +++ b/src/visualising/expression/library/alphabet/M.txt @@ -0,0 +1,2 @@ +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,10,0,0,10,10,10,10,10,10,10,10,10,10,10,10,0,0,0,0,0,0,0,0,0,0,0,0,10,10,10,10,10,10,10,10,10,10,10,10,10,0,0 \ No newline at end of file diff --git a/src/visualising/expression/library/alphabet/N.txt b/src/visualising/expression/library/alphabet/N.txt new file mode 100644 index 0000000..c222d6c --- /dev/null +++ b/src/visualising/expression/library/alphabet/N.txt @@ -0,0 +1,2 @@ +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,0,0,0,0,0,10,0,0,10,10,10,10,10,10,10,10,10,10,10,10,0,0,0,0,0,0,0,0,0,10,0,0,10,10,10,10,10,10,10,10,10,10,10,10,0,0,0 \ No newline at end of file diff --git a/src/visualising/expression/library/O.txt b/src/visualising/expression/library/alphabet/O.txt similarity index 100% rename from src/visualising/expression/library/O.txt rename to src/visualising/expression/library/alphabet/O.txt diff --git a/src/visualising/expression/library/alphabet/R.txt b/src/visualising/expression/library/alphabet/R.txt new file mode 100644 index 0000000..6d8ccd6 --- /dev/null +++ b/src/visualising/expression/library/alphabet/R.txt @@ -0,0 +1,2 @@ +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,0,0,0,0,0,0,0,0,0,10,0,0,0,0,0,0,0,0,10,0,0,10,10,10,10,10,10 \ No newline at end of file diff --git a/src/visualising/expression/library/alphabet/S.txt b/src/visualising/expression/library/alphabet/S.txt new file mode 100644 index 0000000..22ce48b --- /dev/null +++ b/src/visualising/expression/library/alphabet/S.txt @@ -0,0 +1,2 @@ +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +10,10,10,10,10,10,10,10,10,10,10,10,10,0,0,0,0,0,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,0,0,0,0,0,10,10,10,10,10,10,10,10,10 \ No newline at end of file diff --git a/src/visualising/expression/library/alphabet/T.txt b/src/visualising/expression/library/alphabet/T.txt new file mode 100644 index 0000000..79be5a6 --- /dev/null +++ b/src/visualising/expression/library/alphabet/T.txt @@ -0,0 +1,2 @@ +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +10,0,0,10,10,10,10,10,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10,10,10,10,10,10 \ No newline at end of file diff --git a/src/visualising/expression/library/alphabet/__init__.py b/src/visualising/expression/library/alphabet/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/visualising/expression/library/emotion/__init__.py b/src/visualising/expression/library/emotion/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/visualising/expression/library/emotion/expression_angry.txt b/src/visualising/expression/library/emotion/expression_angry.txt new file mode 100644 index 0000000..48e2b7f --- /dev/null +++ b/src/visualising/expression/library/emotion/expression_angry.txt @@ -0,0 +1,2 @@ +50,0,0,50,0,0,0,0,0,0,0,0,50,0,0,50,0,0,0,0,0,0,0,0,50,0,0,50,0,0,0,0,0,0,0,0,50,0,0,50,0,0,0,0,0,0,0,0 +50,0,0,50,0,0,0,0,0,0,0,0,50,0,0,50,0,0,0,0,0,0,0,0,50,0,0,50,0,0,0,0,0,0,0,0,50,0,0,50,0,0,0,0,0,0,0,0 \ No newline at end of file diff --git a/src/visualising/expression/library/expression_clean.txt b/src/visualising/expression/library/emotion/expression_clean.txt similarity index 100% rename from src/visualising/expression/library/expression_clean.txt rename to src/visualising/expression/library/emotion/expression_clean.txt diff --git a/src/visualising/expression/library/emotion/expression_happy.txt b/src/visualising/expression/library/emotion/expression_happy.txt new file mode 100644 index 0000000..213cbe6 --- /dev/null +++ b/src/visualising/expression/library/emotion/expression_happy.txt @@ -0,0 +1,2 @@ +50,0,0,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,50,50,50 +50,0,0,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,50,50,50 diff --git a/src/visualising/expression/library/emotion/expression_ok.txt b/src/visualising/expression/library/emotion/expression_ok.txt new file mode 100644 index 0000000..a989e0d --- /dev/null +++ b/src/visualising/expression/library/emotion/expression_ok.txt @@ -0,0 +1,2 @@ +50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50 +50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50 diff --git a/src/visualising/expression/library/expression_angry.txt b/src/visualising/expression/library/expression_angry.txt deleted file mode 100644 index bfb8ee5..0000000 --- a/src/visualising/expression/library/expression_angry.txt +++ /dev/null @@ -1,2 +0,0 @@ -50,50,50,50,50,50,0,0,0,0,0,0,50,50,50,50,50,50,0,0,0,0,0,0,50,50,50,50,50,50,0,0,0,0,0,0,50,50,50,50,50,50,0,0,0,0,0,0 -50,50,50,50,50,50,0,0,0,0,0,0,50,50,50,50,50,50,0,0,0,0,0,0,50,50,50,50,50,50,0,0,0,0,0,0,50,50,50,50,50,50,0,0,0,0,0,0 \ No newline at end of file diff --git a/src/visualising/expression/library/expression_happy.txt b/src/visualising/expression/library/expression_happy.txt deleted file mode 100644 index 12b6349..0000000 --- a/src/visualising/expression/library/expression_happy.txt +++ /dev/null @@ -1,2 +0,0 @@ -50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,50,50,50 -50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,50,50,50 diff --git a/src/visualising/expression/library/test.txt b/src/visualising/expression/library/test.txt new file mode 100644 index 0000000..a2d8125 --- /dev/null +++ b/src/visualising/expression/library/test.txt @@ -0,0 +1,2 @@ +0,0,0,0,0,0,0,0,0,0,0,0,10,0,0,10,0,0,10,0,0,10,0,0,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +10,0,0,10,0,0,10,0,0,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10,0,0 diff --git a/src/visualising/expression/tool.py b/src/visualising/expression/tool.py index b635235..de188c9 100644 --- a/src/visualising/expression/tool.py +++ b/src/visualising/expression/tool.py @@ -8,7 +8,9 @@ from visualising.communication.illustration.animation import Animation from visualising.communication.illustration.ensemble import Ensemble from visualising.communication.illustration.frame import Frame from visualising.communication.illustration.pixel import Pixel -from visualising.expression import library +from visualising.communication.illustration.color.rgb import RGB +from visualising.expression.library import alphabet +from visualising.expression.library import emotion class Tool: @@ -16,7 +18,7 @@ class Tool: pass @staticmethod - def read_raw_ensembles(filename): + def read_raw_ensembles(filename, directory): def read_raw_frame(line): raw_frame = [] for value in line.split(","): @@ -25,7 +27,12 @@ class Tool: ) return raw_frame - file = importlib.resources.open_text(library, filename) + if directory == "emotion": + file = importlib.resources.open_text(emotion, filename) + elif directory == "alphabet": + file = importlib.resources.open_text(alphabet, filename) + else: + raise ValueError("The parameter directory describes a unknown directory!") raw_ensembles = [] while True: @@ -45,7 +52,7 @@ class Tool: return raw_ensembles @staticmethod - def create_ensembles(filename): + def create_ensembles(filename, directory): def create_frame(raw_pixels): if len(raw_pixels) != 48: raise ValueError("Frame must have exactly 48 values!") @@ -58,12 +65,12 @@ class Tool: g = raw_pixels[i + 1] b = raw_pixels[i + 2] - pixels.append(Pixel(r, g, b)) + pixels.append(Pixel(RGB(r, g, b))) i = i + 3 return Frame(pixels) - raw_ensembles = Tool.read_raw_ensembles(filename) + raw_ensembles = Tool.read_raw_ensembles(filename, directory) ensembles = [] for raw_ensemble in raw_ensembles: @@ -78,8 +85,8 @@ class Tool: return ensembles @staticmethod - def create_animation(filename, ensemble_time, iterations): - ensembles = Tool.create_ensembles(filename) + def create_animation(filename, directory, ensemble_time, iterations): + ensembles = Tool.create_ensembles(filename, directory) return Animation(ensembles, ensemble_time, iterations) @staticmethod @@ -87,105 +94,40 @@ class Tool: l_pixels = [] r_pixels = [] for _ in range(16): - l_pixels.append(Pixel(0, 0, 0)) - r_pixels.append(Pixel(0, 0, 0)) + l_pixels.append(Pixel(RGB(0, 0, 0))) + r_pixels.append(Pixel(RGB(0, 0, 0))) return Ensemble(Frame(l_pixels), Frame(r_pixels)) @staticmethod - def set_pixel_color(ensemble, pixel, r, g, b): - if not 0 <= pixel <= 31: - raise ValueError("Pixel index must be an integer between 0 and 31!") + def set_pixel_color(ensemble, index, color): + if not isinstance(index, int) or not 0 <= index <= 31: + raise ValueError("The parameter index must be an integer between 0 and 31!") - if pixel < 16: - ensemble.l_frame.adjust_color(pixel, r, g, b) + if index < 16: + ensemble.l_frame.replace_pixel(index, Pixel(color)) else: - pixel = pixel - 16 - ensemble.r_frame.adjust_color(pixel, r, g, b) + index = index - 16 + ensemble.r_frame.replace_pixel(index, Pixel(color)) return ensemble @staticmethod def state_to_color(state, start_color, end_color): if not 0 <= state <= 1: - raise ValueError("State must be a value between 0 and 1!") - - return (1 - state) * np.array(start_color) + state * np.array(end_color) - - @staticmethod - def build_still_ensemble(still_ensemble, ensemble_time, color): - r = color[0] - g = color[1] - b = color[2] - - ensembles = [] - for i in range(32): - ensemble = Tool.create_empty_ensemble() - for j in range(i + 1): - if j < 16: - is_colored = still_ensemble.l_frame.is_colored(j) - else: - is_colored = still_ensemble.r_frame.is_colored(j % 16) - - if is_colored or j == i: - ensemble = Tool.set_pixel_color(ensemble, j, r, g, b) - - ensembles.append(ensemble) - - ensemble = Tool.create_empty_ensemble() - for k in range(32): - if k < 16: - is_colored = still_ensemble.l_frame.is_colored(k) - else: - is_colored = still_ensemble.r_frame.is_colored(k % 16) - - if is_colored: - ensemble = Tool.set_pixel_color(ensemble, k, r, g, b) - - ensembles.append(ensemble) - - return Animation(ensembles, ensemble_time, 1) - - @staticmethod - def build_still_ensemble_parallel(still_ensemble, ensemble_time, color): - r = color[0] - g = color[1] - b = color[2] - - ensembles = [] - for i in range(16): - ensemble = Tool.create_empty_ensemble() - for j in range(i + 1): - if still_ensemble.l_frame.is_colored(j): - ensemble = Tool.set_pixel_color(ensemble, j, r, g, b) - if still_ensemble.r_frame.is_colored(j): - ensemble = Tool.set_pixel_color(ensemble, 16 + j, r, g, b) - if j == i: - ensemble = Tool.set_pixel_color(ensemble, j, r, g, b) - ensemble = Tool.set_pixel_color(ensemble, 16 + j, r, g, b) - - ensembles.append(ensemble) - - ensemble = Tool.create_empty_ensemble() - for k in range(32): - if k < 16: - is_colored = still_ensemble.l_frame.is_colored(k) - else: - is_colored = still_ensemble.r_frame.is_colored(k % 16) - - if is_colored: - ensemble = Tool.set_pixel_color(ensemble, k, r, g, b) + raise ValueError("The parameter state must be a floating point number between 0 and 1!") - ensembles.append(ensemble) + color = (1 - state) * np.array(start_color.export_color()) + state * np.array(end_color.export_color()) + color = color.tolist() - return Animation(ensembles, ensemble_time, 1) + return RGB(int(color[0]), int(color[1]), int(color[2])) @staticmethod def build_state_cycle(state_vector): ensemble = Tool.create_empty_ensemble() pixel = 0 for state in state_vector: - color = Tool.state_to_color(state, [0, 50, 0], [50, 0, 0]) + color = Tool.state_to_color(state, [0, 50, 0], [50, 0, 0]) ensemble = Tool.set_pixel_color(ensemble, pixel, color[0], color[1], color[2]) pixel = pixel + 1 @@ -219,7 +161,7 @@ class Tool: ensembles.append(Tool.create_empty_ensemble()) - return Animation(ensembles, 500, 1) + return Animation(ensembles, 1000, 1) @staticmethod def write(text): diff --git a/src/visualising/monitoring/watchdog.py b/src/visualising/monitoring/watchdog.py index 0b9428d..2c1eb8c 100755 --- a/src/visualising/monitoring/watchdog.py +++ b/src/visualising/monitoring/watchdog.py @@ -59,6 +59,7 @@ class Watchdog: def update_condition(self, event): self.aggregate_metrics() self.aggregate_domains() + rospy.logwarn(self.aggregated_domains) # TODO: Add more modes! def aggregate_metrics(self): diff --git a/src/visualising/visualiser.py b/src/visualising/visualiser.py index fcffc42..f1db8e6 100644 --- a/src/visualising/visualiser.py +++ b/src/visualising/visualiser.py @@ -3,7 +3,7 @@ import rospy from visualising.monitoring.watchdog import Watchdog -from visualising.expression.expression import Emotion +from visualising.expression.emotion import Emotion from visualising.communication.arduino import Arduino from visualising.communication.channel.connection import Connection @@ -14,11 +14,11 @@ class Visualiser: baud = rospy.get_param("/visualiser/arduino_baud") freq = rospy.get_param("/visualiser/vis_freq") - self.arduino = Arduino(Connection(port, baud)) + arduino = Arduino(Connection(port, baud)) self.watchdog = Watchdog() if rospy.get_param("/visualiser/visualisation_strategy") == 1: - self.expression = Emotion(self.arduino) + self.expression = Emotion(arduino) else: self.expression = None -- GitLab