From 22e9ecc9d8a1663cc896495e21f19ce83797853f Mon Sep 17 00:00:00 2001 From: RUFFY-369 Date: Tue, 9 Aug 2022 18:21:18 +0530 Subject: [PATCH 01/42] multiprocessing for drone based exercise --- .../follow_turtlebot/web-template/brain.py | 166 +++++++++++ .../follow_turtlebot/web-template/exercise.py | 270 ++++++------------ .../follow_turtlebot/web-template/gui.py | 221 ++++++++------ .../follow_turtlebot/web-template/hal.py | 113 ++++++-- .../web-template/shared/__init__.py | 0 .../web-template/shared/image.py | 109 +++++++ .../web-template/shared/structure_img.py | 9 + .../web-template/shared/value.py | 74 +++++ .../web-template/user_functions.py | 117 ++++++++ scripts/instructions.json | 7 +- 10 files changed, 802 insertions(+), 284 deletions(-) create mode 100644 exercises/static/exercises/follow_turtlebot/web-template/brain.py create mode 100644 exercises/static/exercises/follow_turtlebot/web-template/shared/__init__.py create mode 100644 exercises/static/exercises/follow_turtlebot/web-template/shared/image.py create mode 100644 exercises/static/exercises/follow_turtlebot/web-template/shared/structure_img.py create mode 100644 exercises/static/exercises/follow_turtlebot/web-template/shared/value.py create mode 100644 exercises/static/exercises/follow_turtlebot/web-template/user_functions.py diff --git a/exercises/static/exercises/follow_turtlebot/web-template/brain.py b/exercises/static/exercises/follow_turtlebot/web-template/brain.py new file mode 100644 index 000000000..2330c04ba --- /dev/null +++ b/exercises/static/exercises/follow_turtlebot/web-template/brain.py @@ -0,0 +1,166 @@ +import time +import threading +import multiprocessing +import sys +from datetime import datetime +import re +import json +import importlib + +import rospy +from std_srvs.srv import Empty +import cv2 + +from user_functions import GUIFunctions, HALFunctions +from console import start_console, close_console + +from shared.value import SharedValue + +# The brain process class +class BrainProcess(multiprocessing.Process): + def __init__(self, code, exit_signal): + super(BrainProcess, self).__init__() + + # Initialize exit signal + self.exit_signal = exit_signal + + # Function definitions for users to use + self.hal = HALFunctions() + self.gui = GUIFunctions() + + # Time variables + self.time_cycle = SharedValue('brain_time_cycle') + self.ideal_cycle = SharedValue('brain_ideal_cycle') + self.iteration_counter = 0 + + # Get the sequential and iterative code + # Something wrong over here! The code is reversing + # Found a solution but could not find the reason for this (parse_code function's return line of exercise.py is the reason) + self.sequential_code = code[1] + self.iterative_code = code[0] + + # Function to run to start the process + def run(self): + # Two threads for running and measuring + self.measure_thread = threading.Thread(target=self.measure_frequency) + self.thread = threading.Thread(target=self.process_code) + + self.measure_thread.start() + self.thread.start() + + print("Brain Process Started!") + + self.exit_signal.wait() + + # The process function + def process_code(self): + # Redirect information to console + start_console() + + # Reference Environment for the exec() function + iterative_code, sequential_code = self.iterative_code, self.sequential_code + + # The Python exec function + # Run the sequential part + gui_module, hal_module = self.generate_modules() + if sequential_code != "": + reference_environment = {"GUI": gui_module, "HAL": hal_module} + exec(sequential_code, reference_environment) + + # Run the iterative part inside template + # and keep the check for flag + while not self.exit_signal.is_set(): + start_time = datetime.now() + + # Execute the iterative portion + if iterative_code != "": + exec(iterative_code, reference_environment) + + # Template specifics to run! + finish_time = datetime.now() + dt = finish_time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + + # Keep updating the iteration counter + if(iterative_code == ""): + self.iteration_counter = 0 + else: + self.iteration_counter = self.iteration_counter + 1 + + # The code should be run for atleast the target time step + # If it's less put to sleep + # If it's more no problem as such, but we can change it! + time_cycle = self.time_cycle.get() + + if(ms < time_cycle): + time.sleep((time_cycle - ms) / 1000.0) + + close_console() + print("Current Thread Joined!") + + + # Function to generate the modules for use in ACE Editor + def generate_modules(self): + # Define HAL module + hal_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) + hal_module.HAL = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) + hal_module.HAL.motors = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("motors", None)) + + # Add HAL functions + hal_module.HAL.get_frontal_image = self.hal.get_frontal_image + hal_module.HAL.get_ventral_image = self.hal.get_ventral_image + hal_module.HAL.takeoff = self.hal.takeoff + hal_module.HAL.land = self.hal.land + hal_module.HAL.set_cmd_pos = self.hal.set_cmd_pos + hal_module.HAL.set_cmd_vel = self.hal.set_cmd_vel + hal_module.HAL.set_cmd_mix = self.hal.set_cmd_mix + hal_module.HAL.get_position = self.hal.get_position + hal_module.HAL.get_velocity = self.hal.get_velocity + hal_module.HAL.get_orientation = self.hal.get_orientation + hal_module.HAL.get_roll = self.hal.get_roll + hal_module.HAL.get_pitch = self.hal.get_pitch + hal_module.HAL.get_yaw = self.hal.get_yaw + hal_module.HAL.get_landed_state = self.hal.get_landed_state + hal_module.HAL.get_yaw_rate = self.hal.get_yaw_rate + + + + # Define GUI module + gui_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) + gui_module.GUI = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) + + # Add GUI functions + gui_module.GUI.showImage = self.gui.showImage + gui_module.GUI.showLeftImage = self.gui.showLeftImage + + # Adding modules to system + # Protip: The names should be different from + # other modules, otherwise some errors + sys.modules["HAL"] = hal_module + sys.modules["GUI"] = gui_module + + return gui_module, hal_module + + # Function to measure the frequency of iterations + def measure_frequency(self): + previous_time = datetime.now() + # An infinite loop + while not self.exit_signal.is_set(): + # Sleep for 2 seconds + time.sleep(2) + + # Measure the current time and subtract from the previous time to get real time interval + current_time = datetime.now() + dt = current_time - previous_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + previous_time = current_time + + # Get the time period + try: + # Division by zero + self.ideal_cycle.add(ms / self.iteration_counter) + except: + self.ideal_cycle.add(0) + + # Reset the counter + self.iteration_counter = 0 \ No newline at end of file diff --git a/exercises/static/exercises/follow_turtlebot/web-template/exercise.py b/exercises/static/exercises/follow_turtlebot/web-template/exercise.py index 02709744d..5c4e49578 100644 --- a/exercises/static/exercises/follow_turtlebot/web-template/exercise.py +++ b/exercises/static/exercises/follow_turtlebot/web-template/exercise.py @@ -1,3 +1,5 @@ + + #!/usr/bin/env python from __future__ import print_function @@ -5,6 +7,7 @@ from websocket_server import WebsocketServer import time import threading +import multiprocessing import subprocess import sys from datetime import datetime @@ -16,6 +19,11 @@ from std_srvs.srv import Empty import cv2 +from shared.value import SharedValue +from hal import HAL +from brain import BrainProcess +import queue + from gui import GUI, ThreadGUI from hal import HAL from turtlebot import Turtlebot @@ -27,19 +35,21 @@ class Template: # self.ideal_cycle to run an execution for at least 1 second # self.process for the current running process def __init__(self): - self.measure_thread = None - self.thread = None - self.reload = False - self.stop_brain = True - self.user_code = "" + + self.brain_process = None + self.reload = multiprocessing.Event() # Time variables - self.ideal_cycle = 80 - self.measured_cycle = 80 - self.iteration_counter = 0 + self.brain_time_cycle = SharedValue('brain_time_cycle') + self.brain_ideal_cycle = SharedValue('brain_ideal_cycle') self.real_time_factor = 0 + self.frequency_message = {'brain': '', 'gui': '', 'rtf': ''} + # GUI variables + self.gui_time_cycle = SharedValue('gui_time_cycle') + self.gui_ideal_cycle = SharedValue('gui_ideal_cycle') + self.server = None self.client = None self.host = sys.argv[1] @@ -48,16 +58,46 @@ def __init__(self): self.hal = HAL() self.turtlebot = Turtlebot() self.gui = GUI(self.host, self.turtlebot) + self.paused = False + # Function to parse the code # A few assumptions: # 1. The user always passes sequential and iterative codes # 2. Only a single infinite loop def parse_code(self, source_code): - sequential_code, iterative_code = self.seperate_seq_iter(source_code) - return iterative_code, sequential_code + # Check for save/load + if(source_code[:5] == "#save"): + source_code = source_code[5:] + self.save_code(source_code) + + return "", "" + + elif(source_code[:5] == "#load"): + source_code = source_code + self.load_code() + self.server.send_message(self.client, source_code) - # Function to separate the iterative and sequential code + return "", "" + + else: + sequential_code, iterative_code = self.seperate_seq_iter(source_code[6:]) + return iterative_code, sequential_code + + + + # Function for saving + def save_code(self, source_code): + with open('code/academy.py', 'w') as code_file: + code_file.write(source_code) + + # Function for loading + def load_code(self): + with open('code/academy.py', 'r') as code_file: + source_code = code_file.read() + + return source_code + + # Function to seperate the iterative and sequential code def seperate_seq_iter(self, source_code): if source_code == "": return "", "" @@ -65,8 +105,8 @@ def seperate_seq_iter(self, source_code): # Search for an instance of while True infinite_loop = re.search(r'[^ ]while\s*\(\s*True\s*\)\s*:|[^ ]while\s*True\s*:|[^ ]while\s*1\s*:|[^ ]while\s*\(\s*1\s*\)\s*:', source_code) - # Separate the content inside while True and the other - # (Separating the sequential and iterative part!) + # Seperate the content inside while True and the other + # (Seperating the sequential and iterative part!) try: start_index = infinite_loop.start() iterative_code = source_code[start_index:] @@ -88,138 +128,17 @@ def seperate_seq_iter(self, source_code): return sequential_code, iterative_code - # The process function - def process_code(self, source_code): - # Redirect the information to console - start_console() - - iterative_code, sequential_code = self.parse_code(source_code) - - # print(sequential_code) - # print(iterative_code) - - # The Python exec function - # Run the sequential part - gui_module, hal_module = self.generate_modules() - reference_environment = {"GUI": gui_module, "HAL": hal_module} - while (self.stop_brain == True): - if (self.reload == True): - return - time.sleep(0.1) - exec(sequential_code, reference_environment) - - # Run the iterative part inside template - # and keep the check for flag - while self.reload == False: - while (self.stop_brain == True): - if (self.reload == True): - return - time.sleep(0.1) - - start_time = datetime.now() - - # Execute the iterative portion - exec(iterative_code, reference_environment) - - # Template specifics to run! - finish_time = datetime.now() - dt = finish_time - start_time - ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 - - # Keep updating the iteration counter - if (iterative_code == ""): - self.iteration_counter = 0 - else: - self.iteration_counter = self.iteration_counter + 1 - - # The code should be run for atleast the target time step - # If it's less put to sleep - if (ms < self.ideal_cycle): - time.sleep((self.ideal_cycle - ms) / 1000.0) - - close_console() - print("Current Thread Joined!") - - # Function to generate the modules for use in ACE Editor - def generate_modules(self): - # Define HAL module - hal_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) - hal_module.HAL = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) - # hal_module.drone = imp.new_module("drone") - # motors# hal_module.HAL.motors = imp.new_module("motors") - - # Add HAL functions - hal_module.HAL.get_frontal_image = self.hal.get_frontal_image - hal_module.HAL.get_ventral_image = self.hal.get_ventral_image - hal_module.HAL.get_position = self.hal.get_position - hal_module.HAL.get_velocity = self.hal.get_velocity - hal_module.HAL.get_yaw_rate = self.hal.get_yaw_rate - hal_module.HAL.get_orientation = self.hal.get_orientation - hal_module.HAL.get_roll = self.hal.get_roll - hal_module.HAL.get_pitch = self.hal.get_pitch - hal_module.HAL.get_yaw = self.hal.get_yaw - hal_module.HAL.get_landed_state = self.hal.get_landed_state - hal_module.HAL.set_cmd_pos = self.hal.set_cmd_pos - hal_module.HAL.set_cmd_vel = self.hal.set_cmd_vel - hal_module.HAL.set_cmd_mix = self.hal.set_cmd_mix - hal_module.HAL.takeoff = self.hal.takeoff - hal_module.HAL.land = self.hal.land - - # Define GUI module - gui_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) - gui_module.GUI = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) - - # Add GUI functions - gui_module.GUI.showImage = self.gui.showImage - gui_module.GUI.showLeftImage = self.gui.showLeftImage - - # Adding modules to system - # Protip: The names should be different from - # other modules, otherwise some errors - sys.modules["HAL"] = hal_module - sys.modules["GUI"] = gui_module - - return gui_module, hal_module - - # Function to measure the frequency of iterations - def measure_frequency(self): - previous_time = datetime.now() - # An infinite loop - while True: - # Sleep for 2 seconds - time.sleep(2) - - # Measure the current time and subtract from the previous time to get real time interval - current_time = datetime.now() - dt = current_time - previous_time - ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 - previous_time = current_time - - # Get the time period - try: - # Division by zero - self.measured_cycle = ms / self.iteration_counter - except: - self.measured_cycle = 0 - - # Reset the counter - self.iteration_counter = 0 - - # Send to client - self.send_frequency_message() - - # Function to generate and send frequency messages def send_frequency_message(self): # This function generates and sends frequency measures of the brain and gui - brain_frequency = 0; gui_frequency = 0 + brain_frequency = 0;gui_frequency = 0 try: - brain_frequency = round(1000 / self.measured_cycle, 1) - except: + brain_frequency = round(1000 / self.brain_ideal_cycle.get(), 1) + except ZeroDivisionError: brain_frequency = 0 try: - gui_frequency = round(1000 / self.thread_gui.measured_cicle, 1) - except: + gui_frequency = round(1000 / self.gui_ideal_cycle.get(), 1) + except ZeroDivisionError: gui_frequency = 0 self.frequency_message["brain"] = brain_frequency @@ -243,29 +162,32 @@ def track_stats(self): args = ["gz", "stats", "-p"] # Prints gz statistics. "-p": Output comma-separated values containing- # real-time factor (percent), simtime (sec), realtime (sec), paused (T or F) - stats_process = subprocess.Popen(args, stdout=subprocess.PIPE, bufsize=1, universal_newlines=True) + stats_process = subprocess.Popen(args, stdout=subprocess.PIPE, bufsize=0) # bufsize=1 enables line-bufferred mode (the input buffer is flushed # automatically on newlines if you would write to process.stdin ) with stats_process.stdout: - for line in iter(stats_process.stdout.readline, ''): - stats_list = [x.strip() for x in line.split(',')] - self.real_time_factor = stats_list[0] + for line in iter(stats_process.stdout.readline, b''): + stats_list = [x.strip() for x in line.split(b',')] + self.real_time_factor = stats_list[0].decode("utf-8") # Function to maintain thread execution def execute_thread(self, source_code): # Keep checking until the thread is alive # The thread will die when the coming iteration reads the flag - if self.thread is not None: - while self.thread.is_alive(): - time.sleep(0.2) + if(self.brain_process != None): + while self.brain_process.is_alive(): + pass # Turn the flag down, the iteration has successfully stopped! - self.reload = False + self.reload.clear() # New thread execution - self.thread = threading.Thread(target=self.process_code, args=[source_code]) - self.thread.start() + code = self.parse_code(source_code) + if code[0] == "" and code[1] == "": + return + + self.brain_process = BrainProcess(code, self.reload) + self.brain_process.start() self.send_code_message() - print("New Thread Started!") # Function to read and set frequency from incoming message def read_frequency_message(self, message): @@ -273,66 +195,62 @@ def read_frequency_message(self, message): # Set brain frequency frequency = float(frequency_message["brain"]) - self.ideal_cycle = 1000.0 / frequency + self.brain_time_cycle.add(1000.0 / frequency) # Set gui frequency frequency = float(frequency_message["gui"]) - self.thread_gui.ideal_cicle = 1000.0 / frequency + self.gui_time_cycle.add(1000.0 / frequency) return # The websocket function # Gets called when there is an incoming message from the client def handle(self, client, server, message): - if message[:5] == "#freq": + if(message[:5] == "#freq"): frequency_message = message[5:] self.read_frequency_message(frequency_message) time.sleep(1) + self.send_frequency_message() return - elif(message[:5] == "#ping"): time.sleep(1) self.send_ping_message() return - - elif (message[:5] == "#code"): + elif (message[:5] == "#code"): try: # Once received turn the reload flag up and send it to execute_thread function - self.user_code = message[6:] + code = message # print(repr(code)) - self.reload = True - self.execute_thread(self.user_code) + self.reload.set() + self.execute_thread(code) except: pass - elif (message[:5] == "#rest"): + elif (message[:5] == "#stop"): try: - self.reload = True - self.stop_brain = True - self.execute_thread(self.user_code) + self.reload.set() + self.execute_thread("""from GUI import GUI +from HAL import HAL +# Enter sequential code! + +while True: + # Enter iterative code!""") except: pass - - elif (message[:5] == "#stop"): - self.stop_brain = True - - elif (message[:5] == "#play"): - self.stop_brain = False + self.server.send_message(self.client, "#stpd") # Function that gets called when the server is connected def connected(self, client, server): self.client = client - # Start the GUI update thread - self.thread_gui = ThreadGUI(self.gui) - self.thread_gui.start() + # Start the HAL update thread + self.hal.start_thread() - # Start the real time factor tracker thread + # Start real time factor tracker thread self.stats_thread = threading.Thread(target=self.track_stats) self.stats_thread.start() - # Start measure frequency - self.measure_thread = threading.Thread(target=self.measure_frequency) - self.measure_thread.start() + # Initialize the ping message + self.send_frequency_message() print(client, 'connected') diff --git a/exercises/static/exercises/follow_turtlebot/web-template/gui.py b/exercises/static/exercises/follow_turtlebot/web-template/gui.py index 5427ea531..02df0a438 100644 --- a/exercises/static/exercises/follow_turtlebot/web-template/gui.py +++ b/exercises/static/exercises/follow_turtlebot/web-template/gui.py @@ -6,15 +6,25 @@ from datetime import datetime from websocket_server import WebsocketServer import logging +import rospy +import cv2 +import sys +import numpy as np +import multiprocessing + +from interfaces.pose3d import ListenerPose3d +from shared.image import SharedImage +from shared.value import SharedValue +from .turtlebot import Turtlebot # Graphical User Interface Class class GUI: # Initialization function # The actual initialization def __init__(self, host, turtlebot): - t = threading.Thread(target=self.run_server) + rospy.init_node("GUI") self.payload = {'image': ''} self.left_payload = {'image': ''} self.server = None @@ -22,84 +32,74 @@ def __init__(self, host, turtlebot): self.host = host + # Image variable host + self.shared_image = SharedImage("guifrontalimage") + self.shared_left_image = SharedImage("guiventralimage") + + # Event objects for multiprocessing + self.ack_event = multiprocessing.Event() + self.cli_event = multiprocessing.Event() + # Image variables - self.image_to_be_shown = None - self.image_to_be_shown_updated = False - self.image_show_lock = threading.Lock() + # self.image_to_be_shown = None + # self.image_to_be_shown_updated = False + # self.image_show_lock = threading.Lock() - self.left_image_to_be_shown = None - self.left_image_to_be_shown_updated = False - self.left_image_show_lock = threading.Lock() + # self.left_image_to_be_shown = None + # self.left_image_to_be_shown_updated = False + # self.left_image_show_lock = threading.Lock() - self.acknowledge = False - self.acknowledge_lock = threading.Lock() + # self.acknowledge = False + # self.acknowledge_lock = threading.Lock() # Take the console object to set the same websocket and client self.turtlebot = turtlebot + + # Start server thread + t = threading.Thread(target=self.run_server) t.start() + # Explicit initialization function # Class method, so user can call it without instantiation - @classmethod - def initGUI(cls, host): - # self.payload = {'image': '', 'shape': []} - new_instance = cls(host) - return new_instance + # @classmethod + # def initGUI(cls, host): + # # self.payload = {'image': '', 'shape': []} + # new_instance = cls(host) + # return new_instance # Function to prepare image payload # Encodes the image as a JSON string and sends through the WS def payloadImage(self): - self.image_show_lock.acquire() - image_to_be_shown_updated = self.image_to_be_shown_updated - image_to_be_shown = self.image_to_be_shown - self.image_show_lock.release() - - image = image_to_be_shown + image = self.shared_image.get() payload = {'image': '', 'shape': ''} - - if not image_to_be_shown_updated: - return payload - + shape = image.shape frame = cv2.imencode('.JPEG', image)[1] encoded_image = base64.b64encode(frame) - + payload['image'] = encoded_image.decode('utf-8') payload['shape'] = shape - - self.image_show_lock.acquire() - self.image_to_be_shown_updated = False - self.image_show_lock.release() - + return payload + # Function to prepare image payload # Encodes the image as a JSON string and sends through the WS def payloadLeftImage(self): - self.left_image_show_lock.acquire() - left_image_to_be_shown_updated = self.left_image_to_be_shown_updated - left_image_to_be_shown = self.left_image_to_be_shown - self.left_image_show_lock.release() - - image = left_image_to_be_shown + image = self.shared_left_image.get() payload = {'image': '', 'shape': ''} - - if not left_image_to_be_shown_updated: - return payload - + shape = image.shape frame = cv2.imencode('.JPEG', image)[1] encoded_image = base64.b64encode(frame) - + payload['image'] = encoded_image.decode('utf-8') payload['shape'] = shape - - self.left_image_show_lock.acquire() - self.left_image_to_be_shown_updated = False - self.left_image_show_lock.release() - + return payload + # Function for student to call def showImage(self, image): self.image_show_lock.acquire() @@ -118,20 +118,23 @@ def showLeftImage(self, image): # Called when a new client is received def get_client(self, client, server): self.client = client + self.cli_event.set() + + print(client, 'connected') - # Function to get value of Acknowledge - def get_acknowledge(self): - self.acknowledge_lock.acquire() - acknowledge = self.acknowledge - self.acknowledge_lock.release() + # # Function to get value of Acknowledge + # def get_acknowledge(self): + # self.acknowledge_lock.acquire() + # acknowledge = self.acknowledge + # self.acknowledge_lock.release() - return acknowledge + # return acknowledge - # Function to get value of Acknowledge - def set_acknowledge(self, value): - self.acknowledge_lock.acquire() - self.acknowledge = value - self.acknowledge_lock.release() + # # Function to get value of Acknowledge + # def set_acknowledge(self, value): + # self.acknowledge_lock.acquire() + # self.acknowledge = value + # self.acknowledge_lock.release() # Update the gui def update_gui(self): @@ -142,6 +145,7 @@ def update_gui(self): message = "#gui" + json.dumps(self.payload) self.server.send_message(self.client, message) + # Payload Left Image Message left_payload = self.payloadLeftImage() self.left_payload["image"] = json.dumps(left_payload) @@ -153,14 +157,25 @@ def update_gui(self): # Gets called when there is an incoming message from the client def get_message(self, client, server, message): # Acknowledge Message for GUI Thread - if message[:4] == "#ack": - self.set_acknowledge(True) + + + if(message[:4] == "#ack"): + # Set acknowledgement flag + self.ack_event.set() elif message[:4] == "#tur": self.turtlebot.start_turtlebot() elif message[:4] == "#stp": self.turtlebot.stop_turtlebot() + # Reset message elif message[:4] == "#rst": self.turtlebot.reset_turtlebot() + + + + # Function that gets called when the connected closes + def handle_close(self, client, server): + print(client, 'closed') + # Activate the server @@ -168,6 +183,7 @@ def run_server(self): self.server = WebsocketServer(port=2303, host=self.host) self.server.set_fn_new_client(self.get_client) self.server.set_fn_message_received(self.get_message) + self.server.set_fn_client_left(self.handle_close) logged = False while not logged: @@ -188,32 +204,57 @@ def reset_gui(self): # This class decouples the user thread # and the GUI update thread -class ThreadGUI: - def __init__(self, gui): - self.gui = gui +class ProcessGUI(multiprocessing.Process): + def __init__(self): + super(ProcessGUI, self).__init__() + self.host = sys.argv[1] + self.turtlebot = Turtlebot() # Time variables - self.ideal_cycle = 80 - self.measured_cycle = 80 + self.time_cycle = SharedValue("gui_time_cycle") + self.ideal_cycle = SharedValue("gui_ideal_cycle") self.iteration_counter = 0 + # Function to initialize events + def initialize_events(self): + # Events + self.ack_event = self.gui.ack_event + self.cli_event = self.gui.cli_event + self.exit_signal = multiprocessing.Event() + # Function to start the execution of threads - def start(self): + def run(self): + # Initialize GUI + self.gui = GUI(self.host, self.turtlebot) + self.initialize_events() + + # Wait for client before starting + self.cli_event.wait() + self.measure_thread = threading.Thread(target=self.measure_thread) - self.thread = threading.Thread(target=self.run) + self.thread = threading.Thread(target=self.run_gui) self.measure_thread.start() self.thread.start() - print("GUI Thread Started!") + print("GUI Process Started!") + + self.exit_signal.wait() + + # Function to start the execution of threads + # def start(self): + # self.measure_thread = threading.Thread(target=self.measure_thread) + # self.thread = threading.Thread(target=self.run) + + # self.measure_thread.start() + # self.thread.start() + + # print("GUI Thread Started!") # The measuring thread to measure frequency def measure_thread(self): - while self.gui.client is None: - pass - previous_time = datetime.now() - while True: + while(True): # Sleep for 2 seconds time.sleep(2) @@ -226,32 +267,42 @@ def measure_thread(self): # Get the time period try: # Division by zero - self.measured_cycle = ms / self.iteration_counter + self.ideal_cycle.add(ms / self.iteration_counter) except: - self.measured_cycle = 0 + self.ideal_cycle.add(0) # Reset the counter self.iteration_counter = 0 # The main thread of execution - def run(self): - while self.gui.client is None: - pass - - while True: + # The main thread of execution + def run_gui(self): + while(True): start_time = datetime.now() + # Send update signal self.gui.update_gui() - acknowledge_message = self.gui.get_acknowledge() - - while not acknowledge_message: - acknowledge_message = self.gui.get_acknowledge() - self.gui.set_acknowledge(False) + # Wait for acknowldege signal + self.ack_event.wait() + self.ack_event.clear() finish_time = datetime.now() self.iteration_counter = self.iteration_counter + 1 dt = finish_time - start_time ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 - if ms < self.ideal_cycle: - time.sleep((self.ideal_cycle-ms) / 1000.0) + + time_cycle = self.time_cycle.get() + + if(ms < time_cycle): + time.sleep((time_cycle-ms) / 1000.0) + + self.exit_signal.set() + + # Functions to handle auxillary GUI functions + def reset_gui(self): + self.gui.reset_gui() + +if __name__ == "__main__": + gui = ProcessGUI() + gui.start() \ No newline at end of file diff --git a/exercises/static/exercises/follow_turtlebot/web-template/hal.py b/exercises/static/exercises/follow_turtlebot/web-template/hal.py index ee0fa1d02..65b70f1a1 100644 --- a/exercises/static/exercises/follow_turtlebot/web-template/hal.py +++ b/exercises/static/exercises/follow_turtlebot/web-template/hal.py @@ -5,7 +5,8 @@ from datetime import datetime from drone_wrapper import DroneWrapper - +from shared.image import SharedImage +from shared.value import SharedValue # Hardware Abstraction Layer class HAL: @@ -14,71 +15,143 @@ class HAL: def __init__(self): rospy.init_node("HAL") - + + self.shared_frontal_image = SharedImage("halfrontalimage") + self.shared_ventral_image = SharedImage("halventralimage") + self.shared_x = SharedValue("x") + self.shared_y = SharedValue("y") + self.shared_z = SharedValue("z") + self.shared_takeoff_z = SharedValue("sharedtakeoffz") + self.shared_az = SharedValue("az") + self.shared_azt = SharedValue("azt") + self.shared_vx = SharedValue("vx") + self.shared_vy = SharedValue("vy") + self.shared_vz = SharedValue("vz") + self.shared_landed_state = SharedValue("landedstate") + self.shared_position = SharedValue("position") + self.shared_velocity = SharedValue("velocity") + self.shared_orientation = SharedValue("orientation") + self.shared_roll = SharedValue("roll") + self.shared_pitch = SharedValue("pitch") + self.shared_yaw = SharedValue("yaw") + self.shared_yaw_rate = SharedValue("yawrate") + self.image = None self.drone = DroneWrapper(name="rqt") + # Update thread + self.thread = ThreadHAL(self.update_hal) + # Explicit initialization functions # Class method, so user can call it without instantiation - @classmethod - def initRobot(cls): - new_instance = cls() - return new_instance + # @classmethod + # def initRobot(cls): + # new_instance = cls() + # return new_instance + + # Function to start the update thread + def start_thread(self): + self.thread.start() # Get Image from ROS Driver Camera def get_frontal_image(self): image = self.drone.get_frontal_image() image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) - return image_rgb + self.shared_frontal_image.add(image_rgb) def get_ventral_image(self): image = self.drone.get_ventral_image() image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) - return image_rgb + self.shared_ventral_image.add(image_rgb) def get_position(self): pos = self.drone.get_position() - return pos + self.shared_position.add(pos) def get_velocity(self): vel = self.drone.get_velocity() - return vel + self.shared_velocity.add(vel) def get_yaw_rate(self): yaw_rate = self.drone.get_yaw_rate() - return yaw_rate + self.shared_yaw_rate.add(yaw_rate) def get_orientation(self): orientation = self.drone.get_orientation() - return orientation + self.shared_orientation.add(orientation) def get_roll(self): roll = self.drone.get_roll() - return roll + self.shared_roll.add(roll) def get_pitch(self): pitch = self.drone.get_pitch() - return pitch + self.shared_pitch.add(pitch) def get_yaw(self): yaw = self.drone.get_yaw() - return yaw + self.shared_yaw.add(yaw) def get_landed_state(self): state = self.drone.get_landed_state() - return state + self.shared_landed_state.add(state) + + def set_cmd_pos(self): + x = self.shared_x.get() + y = self.shared_y.get() + z = self.shared_z.get() + az = self.shared_az.get() - def set_cmd_pos(self, x, y, z, az): self.drone.set_cmd_pos(x, y, z, az) - def set_cmd_vel(self, vx, vy, vz, az): + def set_cmd_vel(self): + vx = self.shared_vx.get() + vy = self.shared_vy.get() + vz = self.shared_vz.get() + az = self.shared_azt.get() self.drone.set_cmd_vel(vx, vy, vz, az) - def set_cmd_mix(self, vx, vy, z, az): + def set_cmd_mix(self): + vx = self.shared_vx.get() + vy = self.shared_vy.get() + z = self.shared_z.get() + az = self.shared_azt.get() self.drone.set_cmd_mix(vx, vy, z, az) - def takeoff(self, h=5): + def takeoff(self): + h = self.shared_takeoff_z.get() self.drone.takeoff(h) def land(self): self.drone.land() + + def update_hal(self): + self.getImage() + self.setV() + self.setW() + + # Destructor function to close all fds + def __del__(self): + self.shared_image.close() + self.shared_v.close() + self.shared_w.close() + +class ThreadHAL(threading.Thread): + def __init__(self, update_function): + super(ThreadHAL, self).__init__() + self.time_cycle = 80 + self.update_function = update_function + + def run(self): + while(True): + start_time = datetime.now() + + self.update_function() + + finish_time = datetime.now() + + dt = finish_time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + + if(ms < self.time_cycle): + time.sleep((self.time_cycle - ms) / 1000.0) diff --git a/exercises/static/exercises/follow_turtlebot/web-template/shared/__init__.py b/exercises/static/exercises/follow_turtlebot/web-template/shared/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/exercises/static/exercises/follow_turtlebot/web-template/shared/image.py b/exercises/static/exercises/follow_turtlebot/web-template/shared/image.py new file mode 100644 index 000000000..de5c9f9d6 --- /dev/null +++ b/exercises/static/exercises/follow_turtlebot/web-template/shared/image.py @@ -0,0 +1,109 @@ +import numpy as np +import mmap +from posix_ipc import Semaphore, O_CREX, ExistentialError, O_CREAT, SharedMemory, unlink_shared_memory +from ctypes import sizeof, memmove, addressof, create_string_buffer +from shared.structure_img import MD + +# Probably, using self variables gives errors with memmove +# Therefore, a global variable for utility +md_buf = create_string_buffer(sizeof(MD)) + +class SharedImage: + def __init__(self, name): + # Initialize variables for memory regions and buffers and Semaphore + self.shm_buf = None; self.shm_region = None + self.md_buf = None; self.md_region = None + self.image_lock = None + + self.shm_name = name; self.md_name = name + "-meta" + self.image_lock_name = name + + # Initialize or retreive metadata memory region + try: + self.md_region = SharedMemory(self.md_name) + self.md_buf = mmap.mmap(self.md_region.fd, sizeof(MD)) + self.md_region.close_fd() + except ExistentialError: + self.md_region = SharedMemory(self.md_name, O_CREAT, size=sizeof(MD)) + self.md_buf = mmap.mmap(self.md_region.fd, self.md_region.size) + self.md_region.close_fd() + + # Initialize or retreive Semaphore + try: + self.image_lock = Semaphore(self.image_lock_name, O_CREX) + except ExistentialError: + image_lock = Semaphore(self.image_lock_name, O_CREAT) + image_lock.unlink() + self.image_lock = Semaphore(self.image_lock_name, O_CREX) + + self.image_lock.release() + + # Get the shared image + def get(self): + # Define metadata + metadata = MD() + + # Get metadata from the shared region + self.image_lock.acquire() + md_buf[:] = self.md_buf + memmove(addressof(metadata), md_buf, sizeof(metadata)) + self.image_lock.release() + + # Try to retreive the image from shm_buffer + # Otherwise return a zero image + try: + self.shm_region = SharedMemory(self.shm_name) + self.shm_buf = mmap.mmap(self.shm_region.fd, metadata.size) + self.shm_region.close_fd() + + self.image_lock.acquire() + image = np.ndarray(shape=(metadata.shape_0, metadata.shape_1, metadata.shape_2), + dtype='uint8', buffer=self.shm_buf) + self.image_lock.release() + + # Check for a None image + if(image.size == 0): + image = np.zeros((3, 3, 3), np.uint8) + + except ExistentialError: + image = np.zeros((3, 3, 3), np.uint8) + + return image + + # Add the shared image + def add(self, image): + try: + # Get byte size of the image + byte_size = image.nbytes + + # Get the shared memory buffer to read from + if not self.shm_region: + self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=byte_size) + self.shm_buf = mmap.mmap(self.shm_region.fd, byte_size) + self.shm_region.close_fd() + + # Generate meta data + metadata = MD(image.shape[0], image.shape[1], image.shape[2], byte_size) + + # Send the meta data and image to shared regions + self.image_lock.acquire() + memmove(md_buf, addressof(metadata), sizeof(metadata)) + self.md_buf[:] = md_buf[:] + self.shm_buf[:] = image.tobytes() + self.image_lock.release() + except: + pass + + # Destructor function to unlink and disconnect + def close(self): + self.image_lock.acquire() + self.md_buf.close() + + try: + unlink_shared_memory(self.md_name) + unlink_shared_memory(self.shm_name) + except ExistentialError: + pass + + self.image_lock.release() + self.image_lock.close() diff --git a/exercises/static/exercises/follow_turtlebot/web-template/shared/structure_img.py b/exercises/static/exercises/follow_turtlebot/web-template/shared/structure_img.py new file mode 100644 index 000000000..ae40b3707 --- /dev/null +++ b/exercises/static/exercises/follow_turtlebot/web-template/shared/structure_img.py @@ -0,0 +1,9 @@ +from ctypes import Structure, c_int32, c_int64 + +class MD(Structure): + _fields_ = [ + ('shape_0', c_int32), + ('shape_1', c_int32), + ('shape_2', c_int32), + ('size', c_int64) + ] \ No newline at end of file diff --git a/exercises/static/exercises/follow_turtlebot/web-template/shared/value.py b/exercises/static/exercises/follow_turtlebot/web-template/shared/value.py new file mode 100644 index 000000000..d367020fc --- /dev/null +++ b/exercises/static/exercises/follow_turtlebot/web-template/shared/value.py @@ -0,0 +1,74 @@ +import numpy as np +import mmap +from posix_ipc import Semaphore, O_CREX, ExistentialError, O_CREAT, SharedMemory, unlink_shared_memory +from ctypes import sizeof, memmove, addressof, create_string_buffer, c_float +import struct + +class SharedValue: + def __init__(self, name): + # Initialize varaibles for memory regions and buffers and Semaphore + self.shm_buf = None; self.shm_region = None + self.value_lock = None + + self.shm_name = name; self.value_lock_name = name + + # Initialize shared memory buffer + try: + self.shm_region = SharedMemory(self.shm_name) + self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) + self.shm_region.close_fd() + except ExistentialError: + self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=sizeof(c_float)) + self.shm_buf = mmap.mmap(self.shm_region.fd, self.shm_region.size) + self.shm_region.close_fd() + + # Initialize or retreive Semaphore + try: + self.value_lock = Semaphore(self.value_lock_name, O_CREX) + except ExistentialError: + value_lock = Semaphore(self.value_lock_name, O_CREAT) + value_lock.unlink() + self.value_lock = Semaphore(self.value_lock_name, O_CREX) + + self.value_lock.release() + + # Get the shared value + def get(self, type_name): + # Retreive the data from buffer + if type_name=="value": + self.value_lock.acquire() + value = struct.unpack('f', self.shm_buf)[0] + self.value_lock.release() + + return value + elif type_name=="list": + self.value_lock.acquire() + array_val = np.ndarray(shape=(3,), + dtype='float32', buffer=self.shm_buf) + self.value_lock.release() + + return array_val + + else: + print("missing argument for return type") + + + # Add the shared value + def add(self, value): + # Send the data to shared regions + self.value_lock.acquire() + self.shm_buf[:] = struct.pack('f', value) + self.value_lock.release() + + # Destructor function to unlink and disconnect + def close(self): + self.value_lock.acquire() + self.shm_buf.close() + + try: + unlink_shared_memory(self.shm_name) + except ExistentialError: + pass + + self.value_lock.release() + self.value_lock.close() diff --git a/exercises/static/exercises/follow_turtlebot/web-template/user_functions.py b/exercises/static/exercises/follow_turtlebot/web-template/user_functions.py new file mode 100644 index 000000000..d741601fc --- /dev/null +++ b/exercises/static/exercises/follow_turtlebot/web-template/user_functions.py @@ -0,0 +1,117 @@ +from shared.image import SharedImage +from shared.value import SharedValue +import numpy as np +import cv2 + +# Define HAL functions +class HALFunctions: + def __init__(self): + # Initialize image variable + self.shared_frontal_image = SharedImage("halfrontalimage") + self.shared_ventral_image = SharedImage("halventralimage") + self.shared_x = SharedValue("x") + self.shared_y = SharedValue("y") + self.shared_z = SharedValue("z") + self.shared_takeoff_z = SharedValue("sharedtakeoffz") + self.shared_az = SharedValue("az") + self.shared_azt = SharedValue("azt") + self.shared_vx = SharedValue("vx") + self.shared_vy = SharedValue("vy") + self.shared_vz = SharedValue("vz") + self.shared_landed_state = SharedValue("landedstate") + self.shared_position = SharedValue("position") + self.shared_velocity = SharedValue("velocity") + self.shared_orientation = SharedValue("orientation") + self.shared_roll = SharedValue("roll") + self.shared_pitch = SharedValue("pitch") + self.shared_yaw = SharedValue("yaw") + self.shared_yaw_rate = SharedValue("yawrate") + + + # Get image function + def get_frontal_image(self): + image = self.shared_frontal_image.get() + return image + + # Get left image function + def get_ventral_image(self): + image = self.shared_ventral_image.get() + return image + + def takeoff(self, height): + self.shared_takeoff_z.add(height) + + def land(self): + pass + + def set_cmd_pos(self, x, y , z, az): + self.shared_x.add(x) + self.shared_y.add(y) + self.shared_z.add(z) + self.shared_az.add(az) + + def set_cmd_vel(self, vx, vy, vz, az): + self.shared_vx.add(vx) + self.shared_vy.add(vy) + self.shared_vz.add(vz) + self.shared_azt.add(az) + + def set_cmd_mix(self, vx, vy, z, az): + self.shared_vx.add(vx) + self.shared_vy.add(vy) + self.shared_vz.add(z) + self.shared_azt.add(az) + + + def get_position(self): + position = self.shared_position.get(type_name = "list") + return position + + def get_velocity(self): + velocity = self.shared_velocity.get(type_name = "list") + return velocity + + def get_yaw_rate(self): + yaw_rate = self.shared_yaw_rate.get(type_name = "value") + return yaw_rate + + def get_orientation(self): + orientation = self.shared_orientation.get(type_name = "list") + return orientation + + def get_roll(self): + roll = self.shared_roll.get(type_name = "value") + return roll + + def get_pitch(self): + pitch = self.shared_pitch.get(type_name = "value") + return pitch + + def get_yaw(self): + yaw = self.shared_yaw.get(type_name = "value") + return yaw + + def get_landed_state(self): + landed_state = self.shared_landed_state.get(type_name = "value") + return landed_state + +# Define GUI functions +class GUIFunctions: + def __init__(self): + # Initialize image variable + self.shared_image = SharedImage("guifrontalimage") + self.shared_left_image = SharedImage("guiventralimage") + + # Show image function + def showImage(self, image): + # Reshape to 3 channel if it has only 1 in order to display it + if (len(image.shape) < 3): + image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB) + self.shared_image.add(image) + + # Show left image function + def showLeftImage(self, image): + # Reshape to 3 channel if it has only 1 in order to display it + if (len(image.shape) < 3): + image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB) + self.shared_left_image.add(image) \ No newline at end of file diff --git a/scripts/instructions.json b/scripts/instructions.json index 1aa963c34..f512c7c4b 100644 --- a/scripts/instructions.json +++ b/scripts/instructions.json @@ -25,7 +25,7 @@ }, "drone_cat_mouse": { "gazebo_path": "/RoboticsAcademy/exercises/drone_cat_mouse/web-template/launch", - "instructions_ros": ["python3 ./RoboticsAcademy/exercises/drone_cat_mouse/web-template/launch/launch.py"], + "instructions_ros": ["/opt/ros/noetic/bin/roslaunch ./RoboticsAcademy/exercises/drone_cat_mouse/web-template/launch/drone_cat_mouse.launch"], "instructions_host": "python3 /RoboticsAcademy/exercises/drone_cat_mouse/web-template/exercise.py 0.0.0.0", "instructions_guest": "python3 /RoboticsAcademy/exercises/drone_cat_mouse/web-template/exercise_guest.py 0.0.0.0" }, @@ -37,8 +37,9 @@ }, "follow_turtlebot": { "gazebo_path": "/RoboticsAcademy/exercises/follow_turtlebot/web-template/launch", - "instructions_ros": ["python3 ./RoboticsAcademy/exercises/follow_turtlebot/web-template/launch/launch.py"], - "instructions_host": "python3 /RoboticsAcademy/exercises/follow_turtlebot/web-template/exercise.py 0.0.0.0" + "instructions_ros": ["/opt/ros/noetic/bin/roslaunch ./RoboticsAcademy/exercises/follow_turtlebot/web-template/launch/follow_turtlebot.launch"], + "instructions_host": "python3 /RoboticsAcademy/exercises/follow_turtlebot/web-template/exercise.py 0.0.0.0", + "instructions_gui": "python3 /RoboticsAcademy/exercises/follow_turtlebot/web-template/gui.py 0.0.0.0 {}" }, "global_navigation": { "gazebo_path": "/RoboticsAcademy/exercises/global_navigation/web-template/launch", From bbd59999d8e96fc99cf574bc4c0292be3b0fe60b Mon Sep 17 00:00:00 2001 From: RUFFY-369 Date: Fri, 2 Sep 2022 17:57:09 +0530 Subject: [PATCH 02/42] changes to solve mp errors and make it work --- .../follow_turtlebot/web-template/exercise.py | 4 +- .../follow_turtlebot/web-template/gui.py | 46 ++--------------- .../follow_turtlebot/web-template/hal.py | 46 +++++++++++++---- .../web-template/{ => shared}/turtlebot.py | 0 .../web-template/shared/value.py | 49 +++++++++++++------ 5 files changed, 75 insertions(+), 70 deletions(-) rename exercises/static/exercises/follow_turtlebot/web-template/{ => shared}/turtlebot.py (100%) diff --git a/exercises/static/exercises/follow_turtlebot/web-template/exercise.py b/exercises/static/exercises/follow_turtlebot/web-template/exercise.py index 5c4e49578..fd3c282b8 100644 --- a/exercises/static/exercises/follow_turtlebot/web-template/exercise.py +++ b/exercises/static/exercises/follow_turtlebot/web-template/exercise.py @@ -24,9 +24,8 @@ from brain import BrainProcess import queue -from gui import GUI, ThreadGUI from hal import HAL -from turtlebot import Turtlebot +from shared.turtlebot import Turtlebot from console import start_console, close_console @@ -57,7 +56,6 @@ def __init__(self): # Initialize the GUI, HAL and Console behind the scenes self.hal = HAL() self.turtlebot = Turtlebot() - self.gui = GUI(self.host, self.turtlebot) self.paused = False diff --git a/exercises/static/exercises/follow_turtlebot/web-template/gui.py b/exercises/static/exercises/follow_turtlebot/web-template/gui.py index 02df0a438..b0171286b 100644 --- a/exercises/static/exercises/follow_turtlebot/web-template/gui.py +++ b/exercises/static/exercises/follow_turtlebot/web-template/gui.py @@ -16,7 +16,7 @@ from shared.image import SharedImage from shared.value import SharedValue -from .turtlebot import Turtlebot +from shared.turtlebot import Turtlebot # Graphical User Interface Class class GUI: @@ -40,17 +40,6 @@ def __init__(self, host, turtlebot): self.ack_event = multiprocessing.Event() self.cli_event = multiprocessing.Event() - # Image variables - # self.image_to_be_shown = None - # self.image_to_be_shown_updated = False - # self.image_show_lock = threading.Lock() - - # self.left_image_to_be_shown = None - # self.left_image_to_be_shown_updated = False - # self.left_image_show_lock = threading.Lock() - - # self.acknowledge = False - # self.acknowledge_lock = threading.Lock() # Take the console object to set the same websocket and client self.turtlebot = turtlebot @@ -60,14 +49,7 @@ def __init__(self, host, turtlebot): t.start() - # Explicit initialization function - # Class method, so user can call it without instantiation - # @classmethod - # def initGUI(cls, host): - # # self.payload = {'image': '', 'shape': []} - # new_instance = cls(host) - # return new_instance - + # Function to prepare image payload # Encodes the image as a JSON string and sends through the WS def payloadImage(self): @@ -122,20 +104,7 @@ def get_client(self, client, server): print(client, 'connected') - # # Function to get value of Acknowledge - # def get_acknowledge(self): - # self.acknowledge_lock.acquire() - # acknowledge = self.acknowledge - # self.acknowledge_lock.release() - - # return acknowledge - - # # Function to get value of Acknowledge - # def set_acknowledge(self, value): - # self.acknowledge_lock.acquire() - # self.acknowledge = value - # self.acknowledge_lock.release() - + # Update the gui def update_gui(self): # Payload Image Message @@ -241,15 +210,6 @@ def run(self): self.exit_signal.wait() - # Function to start the execution of threads - # def start(self): - # self.measure_thread = threading.Thread(target=self.measure_thread) - # self.thread = threading.Thread(target=self.run) - - # self.measure_thread.start() - # self.thread.start() - - # print("GUI Thread Started!") # The measuring thread to measure frequency def measure_thread(self): diff --git a/exercises/static/exercises/follow_turtlebot/web-template/hal.py b/exercises/static/exercises/follow_turtlebot/web-template/hal.py index 65b70f1a1..907b84481 100644 --- a/exercises/static/exercises/follow_turtlebot/web-template/hal.py +++ b/exercises/static/exercises/follow_turtlebot/web-template/hal.py @@ -37,7 +37,7 @@ def __init__(self): self.shared_yaw_rate = SharedValue("yawrate") self.image = None - self.drone = DroneWrapper(name="rqt") + self.drone = DroneWrapper(name="rqt",ns="/firefly/") # Update thread self.thread = ThreadHAL(self.update_hal) @@ -66,11 +66,11 @@ def get_ventral_image(self): def get_position(self): pos = self.drone.get_position() - self.shared_position.add(pos) + self.shared_position.add(pos,type_name="list") def get_velocity(self): vel = self.drone.get_velocity() - self.shared_velocity.add(vel) + self.shared_velocity.add(vel,type_name="list") def get_yaw_rate(self): yaw_rate = self.drone.get_yaw_rate() @@ -78,7 +78,7 @@ def get_yaw_rate(self): def get_orientation(self): orientation = self.drone.get_orientation() - self.shared_orientation.add(orientation) + self.shared_orientation.add(orientation,type_name="list") def get_roll(self): roll = self.drone.get_roll() @@ -126,15 +126,41 @@ def land(self): self.drone.land() def update_hal(self): - self.getImage() - self.setV() - self.setW() + self.get_frontal_image() + self.get_ventral_image() + self.get_position() + self.get_velocity() + self.get_yaw_rate() + self.get_orientation() + self.get_pitch() + self.get_roll() + self.get_yaw() + self.get_landed_state() + self.set_cmd_pos() + self.set_cmd_vel() + self.set_cmd_mix() # Destructor function to close all fds def __del__(self): - self.shared_image.close() - self.shared_v.close() - self.shared_w.close() + self.shared_frontal_image.close() + self.shared_ventral_image.close() + self.shared_x.close() + self.shared_y.close() + self.shared_z.close() + self.shared_takeoff_z.close() + self.shared_az.close() + self.shared_azt.close() + self.shared_vx.close() + self.shared_vy.close() + self.shared_vz.close() + self.shared_landed_state.close() + self.shared_position.close() + self.shared_velocity.close() + self.shared_orientation.close() + self.shared_roll.close() + self.shared_pitch.close() + self.shared_yaw.close() + self.shared_yaw_rate.close() class ThreadHAL(threading.Thread): def __init__(self, update_function): diff --git a/exercises/static/exercises/follow_turtlebot/web-template/turtlebot.py b/exercises/static/exercises/follow_turtlebot/web-template/shared/turtlebot.py similarity index 100% rename from exercises/static/exercises/follow_turtlebot/web-template/turtlebot.py rename to exercises/static/exercises/follow_turtlebot/web-template/shared/turtlebot.py diff --git a/exercises/static/exercises/follow_turtlebot/web-template/shared/value.py b/exercises/static/exercises/follow_turtlebot/web-template/shared/value.py index d367020fc..ffa0bcd26 100644 --- a/exercises/static/exercises/follow_turtlebot/web-template/shared/value.py +++ b/exercises/static/exercises/follow_turtlebot/web-template/shared/value.py @@ -12,16 +12,6 @@ def __init__(self, name): self.shm_name = name; self.value_lock_name = name - # Initialize shared memory buffer - try: - self.shm_region = SharedMemory(self.shm_name) - self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) - self.shm_region.close_fd() - except ExistentialError: - self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=sizeof(c_float)) - self.shm_buf = mmap.mmap(self.shm_region.fd, self.shm_region.size) - self.shm_region.close_fd() - # Initialize or retreive Semaphore try: self.value_lock = Semaphore(self.value_lock_name, O_CREX) @@ -36,12 +26,25 @@ def __init__(self, name): def get(self, type_name): # Retreive the data from buffer if type_name=="value": + # Initialize shared memory buffer + try: + self.shm_region = SharedMemory(self.shm_name) + self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) + self.shm_region.close_fd() + except ExistentialError: + self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=sizeof(c_float)) + self.shm_buf = mmap.mmap(self.shm_region.fd, self.shm_region.size) + self.shm_region.close_fd() self.value_lock.acquire() value = struct.unpack('f', self.shm_buf)[0] self.value_lock.release() return value elif type_name=="list": + self.shm_region = SharedMemory(self.shm_name) + self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) + self.shm_region.close_fd() + self.value_lock.acquire() array_val = np.ndarray(shape=(3,), dtype='float32', buffer=self.shm_buf) @@ -54,11 +57,29 @@ def get(self, type_name): # Add the shared value - def add(self, value): + def add(self, value, type_name): # Send the data to shared regions - self.value_lock.acquire() - self.shm_buf[:] = struct.pack('f', value) - self.value_lock.release() + if type_name=="value": + try: + self.shm_region = SharedMemory(self.shm_name) + self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) + self.shm_region.close_fd() + except ExistentialError: + self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=sizeof(c_float)) + self.shm_buf = mmap.mmap(self.shm_region.fd, self.shm_region.size) + self.shm_region.close_fd() + + self.value_lock.acquire() + self.shm_buf[:] = struct.pack('f', value) + self.value_lock.release() + elif type_name=="list": + byte_size = value.nbytes + self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=byte_size) + self.shm_buf = mmap.mmap(self.shm_region.fd, byte_size) + self.shm_region.close_fd() + self.value_lock.acquire() + self.shm_buf[:] = value.tobytes() + self.value_lock.release() # Destructor function to unlink and disconnect def close(self): From d831f93d2c19cd8e737a95ea97a02ee17a3bb5fe Mon Sep 17 00:00:00 2001 From: RUFFY-369 Date: Fri, 2 Sep 2022 19:12:00 +0530 Subject: [PATCH 03/42] test bug fix --- .../exercises/follow_turtlebot/web-template/brain.py | 9 ++++++--- .../follow_turtlebot/web-template/shared/value.py | 11 +++++------ 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/exercises/static/exercises/follow_turtlebot/web-template/brain.py b/exercises/static/exercises/follow_turtlebot/web-template/brain.py index 2330c04ba..6f336c1c4 100644 --- a/exercises/static/exercises/follow_turtlebot/web-template/brain.py +++ b/exercises/static/exercises/follow_turtlebot/web-template/brain.py @@ -52,6 +52,7 @@ def run(self): self.exit_signal.wait() + # The process function def process_code(self): # Redirect information to console @@ -99,6 +100,7 @@ def process_code(self): print("Current Thread Joined!") + # Function to generate the modules for use in ACE Editor def generate_modules(self): # Define HAL module @@ -122,8 +124,8 @@ def generate_modules(self): hal_module.HAL.get_yaw = self.hal.get_yaw hal_module.HAL.get_landed_state = self.hal.get_landed_state hal_module.HAL.get_yaw_rate = self.hal.get_yaw_rate - - + + # Define GUI module gui_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) @@ -141,6 +143,7 @@ def generate_modules(self): return gui_module, hal_module + # Function to measure the frequency of iterations def measure_frequency(self): previous_time = datetime.now() @@ -163,4 +166,4 @@ def measure_frequency(self): self.ideal_cycle.add(0) # Reset the counter - self.iteration_counter = 0 \ No newline at end of file + self.iteration_counter = 0 diff --git a/exercises/static/exercises/follow_turtlebot/web-template/shared/value.py b/exercises/static/exercises/follow_turtlebot/web-template/shared/value.py index ffa0bcd26..4bd3dcacb 100644 --- a/exercises/static/exercises/follow_turtlebot/web-template/shared/value.py +++ b/exercises/static/exercises/follow_turtlebot/web-template/shared/value.py @@ -23,10 +23,9 @@ def __init__(self, name): self.value_lock.release() # Get the shared value - def get(self, type_name): + def get(self, type_name= "value"): # Retreive the data from buffer if type_name=="value": - # Initialize shared memory buffer try: self.shm_region = SharedMemory(self.shm_name) self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) @@ -44,20 +43,20 @@ def get(self, type_name): self.shm_region = SharedMemory(self.shm_name) self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) self.shm_region.close_fd() - self.value_lock.acquire() array_val = np.ndarray(shape=(3,), dtype='float32', buffer=self.shm_buf) self.value_lock.release() return array_val - + else: print("missing argument for return type") + # Add the shared value - def add(self, value, type_name): + def add(self, value, type_name= "value"): # Send the data to shared regions if type_name=="value": try: @@ -68,7 +67,7 @@ def add(self, value, type_name): self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=sizeof(c_float)) self.shm_buf = mmap.mmap(self.shm_region.fd, self.shm_region.size) self.shm_region.close_fd() - + self.value_lock.acquire() self.shm_buf[:] = struct.pack('f', value) self.value_lock.release() From df32df4dbb07a2c1fa8f3705492cd0dac7173e3f Mon Sep 17 00:00:00 2001 From: RUFFY-369 Date: Fri, 2 Sep 2022 20:23:51 +0530 Subject: [PATCH 04/42] bug fix through matching files with working container --- .../follow_turtlebot/web-template/README.md | 0 .../follow_turtlebot/web-template/brain.py | 9 ++-- .../web-template/code/academy.py | 0 .../follow_turtlebot/web-template/console.py | 0 .../follow_turtlebot/web-template/exercise.py | 22 +++++---- .../web-template/follow_turtlebot.world | 0 .../follow_turtlebot/web-template/gui.py | 45 ++++++++++++++++++- .../follow_turtlebot/web-template/hal.py | 4 +- .../web-template/interfaces/__init__.py | 0 .../web-template/interfaces/camera.py | 0 .../web-template/interfaces/motors.py | 0 .../web-template/interfaces/pose3d.py | 0 .../interfaces/threadPublisher.py | 0 .../interfaces/threadStoppable.py | 0 .../launch/follow_turtlebot.launch | 2 +- .../web-template/shared/__init__.py | 0 .../web-template/shared/image.py | 0 .../web-template/shared/structure_img.py | 0 .../web-template/shared/turtlebot.py | 0 .../web-template/shared/value.py | 13 +++++- .../web-template/user_functions.py | 0 21 files changed, 74 insertions(+), 21 deletions(-) mode change 100644 => 100755 exercises/static/exercises/follow_turtlebot/web-template/README.md mode change 100644 => 100755 exercises/static/exercises/follow_turtlebot/web-template/brain.py mode change 100644 => 100755 exercises/static/exercises/follow_turtlebot/web-template/code/academy.py mode change 100644 => 100755 exercises/static/exercises/follow_turtlebot/web-template/console.py mode change 100644 => 100755 exercises/static/exercises/follow_turtlebot/web-template/exercise.py mode change 100644 => 100755 exercises/static/exercises/follow_turtlebot/web-template/follow_turtlebot.world mode change 100644 => 100755 exercises/static/exercises/follow_turtlebot/web-template/gui.py mode change 100644 => 100755 exercises/static/exercises/follow_turtlebot/web-template/hal.py mode change 100644 => 100755 exercises/static/exercises/follow_turtlebot/web-template/interfaces/__init__.py mode change 100644 => 100755 exercises/static/exercises/follow_turtlebot/web-template/interfaces/camera.py mode change 100644 => 100755 exercises/static/exercises/follow_turtlebot/web-template/interfaces/motors.py mode change 100644 => 100755 exercises/static/exercises/follow_turtlebot/web-template/interfaces/pose3d.py mode change 100644 => 100755 exercises/static/exercises/follow_turtlebot/web-template/interfaces/threadPublisher.py mode change 100644 => 100755 exercises/static/exercises/follow_turtlebot/web-template/interfaces/threadStoppable.py mode change 100644 => 100755 exercises/static/exercises/follow_turtlebot/web-template/launch/follow_turtlebot.launch mode change 100644 => 100755 exercises/static/exercises/follow_turtlebot/web-template/shared/__init__.py mode change 100644 => 100755 exercises/static/exercises/follow_turtlebot/web-template/shared/image.py mode change 100644 => 100755 exercises/static/exercises/follow_turtlebot/web-template/shared/structure_img.py mode change 100644 => 100755 exercises/static/exercises/follow_turtlebot/web-template/shared/turtlebot.py mode change 100644 => 100755 exercises/static/exercises/follow_turtlebot/web-template/shared/value.py mode change 100644 => 100755 exercises/static/exercises/follow_turtlebot/web-template/user_functions.py diff --git a/exercises/static/exercises/follow_turtlebot/web-template/README.md b/exercises/static/exercises/follow_turtlebot/web-template/README.md old mode 100644 new mode 100755 diff --git a/exercises/static/exercises/follow_turtlebot/web-template/brain.py b/exercises/static/exercises/follow_turtlebot/web-template/brain.py old mode 100644 new mode 100755 index 6f336c1c4..2330c04ba --- a/exercises/static/exercises/follow_turtlebot/web-template/brain.py +++ b/exercises/static/exercises/follow_turtlebot/web-template/brain.py @@ -52,7 +52,6 @@ def run(self): self.exit_signal.wait() - # The process function def process_code(self): # Redirect information to console @@ -100,7 +99,6 @@ def process_code(self): print("Current Thread Joined!") - # Function to generate the modules for use in ACE Editor def generate_modules(self): # Define HAL module @@ -124,8 +122,8 @@ def generate_modules(self): hal_module.HAL.get_yaw = self.hal.get_yaw hal_module.HAL.get_landed_state = self.hal.get_landed_state hal_module.HAL.get_yaw_rate = self.hal.get_yaw_rate - - + + # Define GUI module gui_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) @@ -143,7 +141,6 @@ def generate_modules(self): return gui_module, hal_module - # Function to measure the frequency of iterations def measure_frequency(self): previous_time = datetime.now() @@ -166,4 +163,4 @@ def measure_frequency(self): self.ideal_cycle.add(0) # Reset the counter - self.iteration_counter = 0 + self.iteration_counter = 0 \ No newline at end of file diff --git a/exercises/static/exercises/follow_turtlebot/web-template/code/academy.py b/exercises/static/exercises/follow_turtlebot/web-template/code/academy.py old mode 100644 new mode 100755 diff --git a/exercises/static/exercises/follow_turtlebot/web-template/console.py b/exercises/static/exercises/follow_turtlebot/web-template/console.py old mode 100644 new mode 100755 diff --git a/exercises/static/exercises/follow_turtlebot/web-template/exercise.py b/exercises/static/exercises/follow_turtlebot/web-template/exercise.py old mode 100644 new mode 100755 index fd3c282b8..bd140d227 --- a/exercises/static/exercises/follow_turtlebot/web-template/exercise.py +++ b/exercises/static/exercises/follow_turtlebot/web-template/exercise.py @@ -24,6 +24,7 @@ from brain import BrainProcess import queue + from hal import HAL from shared.turtlebot import Turtlebot from console import start_console, close_console @@ -52,10 +53,13 @@ def __init__(self): self.server = None self.client = None self.host = sys.argv[1] - + print("before hal") # Initialize the GUI, HAL and Console behind the scenes self.hal = HAL() + print("after hal") self.turtlebot = Turtlebot() + print("After tbbbbbbbbbbbbb") + # self.gui = GUI(self.host, self.turtlebot) self.paused = False @@ -227,12 +231,7 @@ def handle(self, client, server, message): elif (message[:5] == "#stop"): try: self.reload.set() - self.execute_thread("""from GUI import GUI -from HAL import HAL -# Enter sequential code! - -while True: - # Enter iterative code!""") + self.execute_thread(code) except: pass self.server.send_message(self.client, "#stpd") @@ -240,15 +239,19 @@ def handle(self, client, server, message): # Function that gets called when the server is connected def connected(self, client, server): self.client = client + print("before hal threaddddddddd") # Start the HAL update thread self.hal.start_thread() + print("after hal threadDDDDDDDD") # Start real time factor tracker thread self.stats_thread = threading.Thread(target=self.track_stats) self.stats_thread.start() + print("after stats thereadddddddd") # Initialize the ping message self.send_frequency_message() + print("After sneding freweq msg") print(client, 'connected') @@ -257,6 +260,7 @@ def handle_close(self, client, server): print(client, 'closed') def run_server(self): + print("cycleeeeeeee", self.brain_ideal_cycle.get()) self.server = WebsocketServer(port=1905, host=self.host) self.server.set_fn_new_client(self.connected) self.server.set_fn_client_left(self.handle_close) @@ -277,5 +281,7 @@ def run_server(self): # Execute! if __name__ == "__main__": + print("in aminnnnnnnnnnnnnnnn") server = Template() - server.run_server() \ No newline at end of file + print("afrer remp") + server.run_server() diff --git a/exercises/static/exercises/follow_turtlebot/web-template/follow_turtlebot.world b/exercises/static/exercises/follow_turtlebot/web-template/follow_turtlebot.world old mode 100644 new mode 100755 diff --git a/exercises/static/exercises/follow_turtlebot/web-template/gui.py b/exercises/static/exercises/follow_turtlebot/web-template/gui.py old mode 100644 new mode 100755 index 8ad788b8d..b576f2cca --- a/exercises/static/exercises/follow_turtlebot/web-template/gui.py +++ b/exercises/static/exercises/follow_turtlebot/web-template/gui.py @@ -40,6 +40,17 @@ def __init__(self, host, turtlebot): self.ack_event = multiprocessing.Event() self.cli_event = multiprocessing.Event() + # Image variables + # self.image_to_be_shown = None + # self.image_to_be_shown_updated = False + # self.image_show_lock = threading.Lock() + + # self.left_image_to_be_shown = None + # self.left_image_to_be_shown_updated = False + # self.left_image_show_lock = threading.Lock() + + # self.acknowledge = False + # self.acknowledge_lock = threading.Lock() # Take the console object to set the same websocket and client self.turtlebot = turtlebot @@ -49,7 +60,14 @@ def __init__(self, host, turtlebot): t.start() - + # Explicit initialization function + # Class method, so user can call it without instantiation + # @classmethod + # def initGUI(cls, host): + # # self.payload = {'image': '', 'shape': []} + # new_instance = cls(host) + # return new_instance + # Function to prepare image payload # Encodes the image as a JSON string and sends through the WS def payloadImage(self): @@ -104,7 +122,20 @@ def get_client(self, client, server): print(client, 'connected') - + # # Function to get value of Acknowledge + # def get_acknowledge(self): + # self.acknowledge_lock.acquire() + # acknowledge = self.acknowledge + # self.acknowledge_lock.release() + + # return acknowledge + + # # Function to get value of Acknowledge + # def set_acknowledge(self, value): + # self.acknowledge_lock.acquire() + # self.acknowledge = value + # self.acknowledge_lock.release() + # Update the gui def update_gui(self): # Payload Image Message @@ -156,6 +187,7 @@ def run_server(self): logged = False while not logged: + print("IN WHILEEEEEEEEEEEEEEEEEEEEEEE") try: f = open("/ws_gui.log", "w") f.write("websocket_gui=ready") @@ -210,6 +242,15 @@ def run(self): self.exit_signal.wait() + # Function to start the execution of threads + # def start(self): + # self.measure_thread = threading.Thread(target=self.measure_thread) + # self.thread = threading.Thread(target=self.run) + + # self.measure_thread.start() + # self.thread.start() + + # print("GUI Thread Started!") # The measuring thread to measure frequency def measure_thread(self): diff --git a/exercises/static/exercises/follow_turtlebot/web-template/hal.py b/exercises/static/exercises/follow_turtlebot/web-template/hal.py old mode 100644 new mode 100755 index c8b0dced4..da63a754c --- a/exercises/static/exercises/follow_turtlebot/web-template/hal.py +++ b/exercises/static/exercises/follow_turtlebot/web-template/hal.py @@ -72,7 +72,7 @@ def get_position(self): def get_velocity(self): vel = self.drone.get_velocity() - self.shared_velocity.add(vel,type_name="list") + self.shared_velocity.add(vel ,type_name="list") def get_yaw_rate(self): yaw_rate = self.drone.get_yaw_rate() @@ -80,7 +80,7 @@ def get_yaw_rate(self): def get_orientation(self): orientation = self.drone.get_orientation() - self.shared_orientation.add(orientation,type_name="list") + self.shared_orientation.add(orientation ,type_name="list") def get_roll(self): roll = self.drone.get_roll() diff --git a/exercises/static/exercises/follow_turtlebot/web-template/interfaces/__init__.py b/exercises/static/exercises/follow_turtlebot/web-template/interfaces/__init__.py old mode 100644 new mode 100755 diff --git a/exercises/static/exercises/follow_turtlebot/web-template/interfaces/camera.py b/exercises/static/exercises/follow_turtlebot/web-template/interfaces/camera.py old mode 100644 new mode 100755 diff --git a/exercises/static/exercises/follow_turtlebot/web-template/interfaces/motors.py b/exercises/static/exercises/follow_turtlebot/web-template/interfaces/motors.py old mode 100644 new mode 100755 diff --git a/exercises/static/exercises/follow_turtlebot/web-template/interfaces/pose3d.py b/exercises/static/exercises/follow_turtlebot/web-template/interfaces/pose3d.py old mode 100644 new mode 100755 diff --git a/exercises/static/exercises/follow_turtlebot/web-template/interfaces/threadPublisher.py b/exercises/static/exercises/follow_turtlebot/web-template/interfaces/threadPublisher.py old mode 100644 new mode 100755 diff --git a/exercises/static/exercises/follow_turtlebot/web-template/interfaces/threadStoppable.py b/exercises/static/exercises/follow_turtlebot/web-template/interfaces/threadStoppable.py old mode 100644 new mode 100755 diff --git a/exercises/static/exercises/follow_turtlebot/web-template/launch/follow_turtlebot.launch b/exercises/static/exercises/follow_turtlebot/web-template/launch/follow_turtlebot.launch old mode 100644 new mode 100755 index d8584f1ba..62e0d36e3 --- a/exercises/static/exercises/follow_turtlebot/web-template/launch/follow_turtlebot.launch +++ b/exercises/static/exercises/follow_turtlebot/web-template/launch/follow_turtlebot.launch @@ -52,7 +52,7 @@ - + diff --git a/exercises/static/exercises/follow_turtlebot/web-template/shared/__init__.py b/exercises/static/exercises/follow_turtlebot/web-template/shared/__init__.py old mode 100644 new mode 100755 diff --git a/exercises/static/exercises/follow_turtlebot/web-template/shared/image.py b/exercises/static/exercises/follow_turtlebot/web-template/shared/image.py old mode 100644 new mode 100755 diff --git a/exercises/static/exercises/follow_turtlebot/web-template/shared/structure_img.py b/exercises/static/exercises/follow_turtlebot/web-template/shared/structure_img.py old mode 100644 new mode 100755 diff --git a/exercises/static/exercises/follow_turtlebot/web-template/shared/turtlebot.py b/exercises/static/exercises/follow_turtlebot/web-template/shared/turtlebot.py old mode 100644 new mode 100755 diff --git a/exercises/static/exercises/follow_turtlebot/web-template/shared/value.py b/exercises/static/exercises/follow_turtlebot/web-template/shared/value.py old mode 100644 new mode 100755 index 4bd3dcacb..3ec51ca46 --- a/exercises/static/exercises/follow_turtlebot/web-template/shared/value.py +++ b/exercises/static/exercises/follow_turtlebot/web-template/shared/value.py @@ -12,6 +12,16 @@ def __init__(self, name): self.shm_name = name; self.value_lock_name = name + # Initialize shared memory buffer + # try: + # self.shm_region = SharedMemory(self.shm_name) + # self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) + # self.shm_region.close_fd() + #except ExistentialError: + # self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=sizeof(c_float)) + # self.shm_buf = mmap.mmap(self.shm_region.fd, self.shm_region.size) + # self.shm_region.close_fd() + # Initialize or retreive Semaphore try: self.value_lock = Semaphore(self.value_lock_name, O_CREX) @@ -49,10 +59,9 @@ def get(self, type_name= "value"): self.value_lock.release() return array_val - + else: print("missing argument for return type") - # Add the shared value diff --git a/exercises/static/exercises/follow_turtlebot/web-template/user_functions.py b/exercises/static/exercises/follow_turtlebot/web-template/user_functions.py old mode 100644 new mode 100755 From 889161b711b16a240e0b3555a1ca7a024f3d16ac Mon Sep 17 00:00:00 2001 From: RUFFY-369 Date: Sat, 3 Sep 2022 19:43:10 +0530 Subject: [PATCH 05/42] changed model for better optimization for docker container --- exercises/static/exercises/follow_turtlebot/web-template/hal.py | 2 +- .../web-template/launch/follow_turtlebot.launch | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/static/exercises/follow_turtlebot/web-template/hal.py b/exercises/static/exercises/follow_turtlebot/web-template/hal.py index da63a754c..4bade1683 100755 --- a/exercises/static/exercises/follow_turtlebot/web-template/hal.py +++ b/exercises/static/exercises/follow_turtlebot/web-template/hal.py @@ -39,7 +39,7 @@ def __init__(self): self.shared_yaw_rate = SharedValue("yawrate") self.image = None - self.drone = DroneWrapper(name="rqt",ns="/firefly/") + self.drone = DroneWrapper(name="rqt",ns="/iris/") # Update thread self.thread = ThreadHAL(self.update_hal) diff --git a/exercises/static/exercises/follow_turtlebot/web-template/launch/follow_turtlebot.launch b/exercises/static/exercises/follow_turtlebot/web-template/launch/follow_turtlebot.launch index 62e0d36e3..c87525257 100755 --- a/exercises/static/exercises/follow_turtlebot/web-template/launch/follow_turtlebot.launch +++ b/exercises/static/exercises/follow_turtlebot/web-template/launch/follow_turtlebot.launch @@ -1,6 +1,6 @@ - + From 934d965f8178b0692cc1e07482c2876c2db2cb44 Mon Sep 17 00:00:00 2001 From: RUFFY-369 Date: Mon, 5 Sep 2022 14:01:54 +0530 Subject: [PATCH 06/42] bug fix for drone_cant_mouse ex --- exercises/static/exercises/drone_cat_mouse/web-template/hal.py | 2 +- .../static/exercises/drone_cat_mouse/web-template/hal_guest.py | 2 +- .../static/exercises/follow_turtlebot/web-template/exercise.py | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/hal.py b/exercises/static/exercises/drone_cat_mouse/web-template/hal.py index f039b4c15..657a091c4 100644 --- a/exercises/static/exercises/drone_cat_mouse/web-template/hal.py +++ b/exercises/static/exercises/drone_cat_mouse/web-template/hal.py @@ -13,7 +13,7 @@ def __init__(self): rospy.init_node("HAL_cat") self.image = None - self.cat = DroneWrapper(name="rqt", ns="cat/") + self.cat = DroneWrapper(name="rqt", ns="/firefly/") # Explicit initialization functions # Class method, so user can call it without instantiation diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/hal_guest.py b/exercises/static/exercises/drone_cat_mouse/web-template/hal_guest.py index 0dbe1eaba..7b660068a 100644 --- a/exercises/static/exercises/drone_cat_mouse/web-template/hal_guest.py +++ b/exercises/static/exercises/drone_cat_mouse/web-template/hal_guest.py @@ -13,7 +13,7 @@ def __init__(self): rospy.init_node("HAL_mouse") self.image = None - self.mouse = DroneWrapper(name="rqt", ns="mouse/") + self.mouse = DroneWrapper(name="rqt", ns="/firefly1/") # Explicit initialization functions # Class method, so user can call it without instantiation diff --git a/exercises/static/exercises/follow_turtlebot/web-template/exercise.py b/exercises/static/exercises/follow_turtlebot/web-template/exercise.py index bd140d227..39b1a527c 100755 --- a/exercises/static/exercises/follow_turtlebot/web-template/exercise.py +++ b/exercises/static/exercises/follow_turtlebot/web-template/exercise.py @@ -20,7 +20,6 @@ import cv2 from shared.value import SharedValue -from hal import HAL from brain import BrainProcess import queue From cd4ca5f3216e27c84410d96f54e9569d1ac69b3a Mon Sep 17 00:00:00 2001 From: RUFFY-369 Date: Mon, 5 Sep 2022 14:15:24 +0530 Subject: [PATCH 07/42] lighter drone model for ex optimisation --- exercises/static/exercises/drone_cat_mouse/web-template/hal.py | 2 +- .../static/exercises/drone_cat_mouse/web-template/hal_guest.py | 2 +- .../drone_cat_mouse/web-template/launch/drone_cat_mouse.launch | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/hal.py b/exercises/static/exercises/drone_cat_mouse/web-template/hal.py index 657a091c4..8174aa3a5 100644 --- a/exercises/static/exercises/drone_cat_mouse/web-template/hal.py +++ b/exercises/static/exercises/drone_cat_mouse/web-template/hal.py @@ -13,7 +13,7 @@ def __init__(self): rospy.init_node("HAL_cat") self.image = None - self.cat = DroneWrapper(name="rqt", ns="/firefly/") + self.cat = DroneWrapper(name="rqt", ns="/iris/") # Explicit initialization functions # Class method, so user can call it without instantiation diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/hal_guest.py b/exercises/static/exercises/drone_cat_mouse/web-template/hal_guest.py index 7b660068a..e9d64b353 100644 --- a/exercises/static/exercises/drone_cat_mouse/web-template/hal_guest.py +++ b/exercises/static/exercises/drone_cat_mouse/web-template/hal_guest.py @@ -13,7 +13,7 @@ def __init__(self): rospy.init_node("HAL_mouse") self.image = None - self.mouse = DroneWrapper(name="rqt", ns="/firefly1/") + self.mouse = DroneWrapper(name="rqt", ns="/iris1/") # Explicit initialization functions # Class method, so user can call it without instantiation diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/launch/drone_cat_mouse.launch b/exercises/static/exercises/drone_cat_mouse/web-template/launch/drone_cat_mouse.launch index 1180e9ebf..32582e4c3 100644 --- a/exercises/static/exercises/drone_cat_mouse/web-template/launch/drone_cat_mouse.launch +++ b/exercises/static/exercises/drone_cat_mouse/web-template/launch/drone_cat_mouse.launch @@ -1,6 +1,6 @@ - + From ffcd9c8bcead4fe0ffe5fcb3c4a819c5327898bc Mon Sep 17 00:00:00 2001 From: RUFFY-369 Date: Mon, 5 Sep 2022 20:21:56 +0530 Subject: [PATCH 08/42] drone_cat_and_mouse completed with fixes --- .../drone_cat_mouse/web-template/brain.py | 166 +++++++++++ .../web-template/brain_guest.py | 165 +++++++++++ .../drone_cat_mouse/web-template/exercise.py | 257 ++++++----------- .../web-template/exercise_guest.py | 259 +++++++---------- .../drone_cat_mouse/web-template/gui.py | 216 +++++++------- .../drone_cat_mouse/web-template/gui_guest.py | 268 ++++++++---------- .../drone_cat_mouse/web-template/hal.py | 136 +++++++-- .../drone_cat_mouse/web-template/hal_guest.py | 140 +++++++-- .../web-template/shared/__init__.py | 0 .../web-template/shared/image.py | 109 +++++++ .../web-template/{ => shared}/mouse.py | 0 .../web-template/shared/structure_img.py | 9 + .../web-template/shared/value.py | 93 ++++++ .../web-template/user_functions.py | 117 ++++++++ .../web-template/user_functions_guest.py | 116 ++++++++ .../follow_turtlebot/web-template/exercise.py | 12 +- .../follow_turtlebot/web-template/gui.py | 57 +--- .../follow_turtlebot/web-template/hal.py | 10 +- 18 files changed, 1406 insertions(+), 724 deletions(-) create mode 100755 exercises/static/exercises/drone_cat_mouse/web-template/brain.py create mode 100644 exercises/static/exercises/drone_cat_mouse/web-template/brain_guest.py create mode 100755 exercises/static/exercises/drone_cat_mouse/web-template/shared/__init__.py create mode 100755 exercises/static/exercises/drone_cat_mouse/web-template/shared/image.py rename exercises/static/exercises/drone_cat_mouse/web-template/{ => shared}/mouse.py (100%) create mode 100755 exercises/static/exercises/drone_cat_mouse/web-template/shared/structure_img.py create mode 100755 exercises/static/exercises/drone_cat_mouse/web-template/shared/value.py create mode 100755 exercises/static/exercises/drone_cat_mouse/web-template/user_functions.py create mode 100644 exercises/static/exercises/drone_cat_mouse/web-template/user_functions_guest.py diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/brain.py b/exercises/static/exercises/drone_cat_mouse/web-template/brain.py new file mode 100755 index 000000000..2330c04ba --- /dev/null +++ b/exercises/static/exercises/drone_cat_mouse/web-template/brain.py @@ -0,0 +1,166 @@ +import time +import threading +import multiprocessing +import sys +from datetime import datetime +import re +import json +import importlib + +import rospy +from std_srvs.srv import Empty +import cv2 + +from user_functions import GUIFunctions, HALFunctions +from console import start_console, close_console + +from shared.value import SharedValue + +# The brain process class +class BrainProcess(multiprocessing.Process): + def __init__(self, code, exit_signal): + super(BrainProcess, self).__init__() + + # Initialize exit signal + self.exit_signal = exit_signal + + # Function definitions for users to use + self.hal = HALFunctions() + self.gui = GUIFunctions() + + # Time variables + self.time_cycle = SharedValue('brain_time_cycle') + self.ideal_cycle = SharedValue('brain_ideal_cycle') + self.iteration_counter = 0 + + # Get the sequential and iterative code + # Something wrong over here! The code is reversing + # Found a solution but could not find the reason for this (parse_code function's return line of exercise.py is the reason) + self.sequential_code = code[1] + self.iterative_code = code[0] + + # Function to run to start the process + def run(self): + # Two threads for running and measuring + self.measure_thread = threading.Thread(target=self.measure_frequency) + self.thread = threading.Thread(target=self.process_code) + + self.measure_thread.start() + self.thread.start() + + print("Brain Process Started!") + + self.exit_signal.wait() + + # The process function + def process_code(self): + # Redirect information to console + start_console() + + # Reference Environment for the exec() function + iterative_code, sequential_code = self.iterative_code, self.sequential_code + + # The Python exec function + # Run the sequential part + gui_module, hal_module = self.generate_modules() + if sequential_code != "": + reference_environment = {"GUI": gui_module, "HAL": hal_module} + exec(sequential_code, reference_environment) + + # Run the iterative part inside template + # and keep the check for flag + while not self.exit_signal.is_set(): + start_time = datetime.now() + + # Execute the iterative portion + if iterative_code != "": + exec(iterative_code, reference_environment) + + # Template specifics to run! + finish_time = datetime.now() + dt = finish_time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + + # Keep updating the iteration counter + if(iterative_code == ""): + self.iteration_counter = 0 + else: + self.iteration_counter = self.iteration_counter + 1 + + # The code should be run for atleast the target time step + # If it's less put to sleep + # If it's more no problem as such, but we can change it! + time_cycle = self.time_cycle.get() + + if(ms < time_cycle): + time.sleep((time_cycle - ms) / 1000.0) + + close_console() + print("Current Thread Joined!") + + + # Function to generate the modules for use in ACE Editor + def generate_modules(self): + # Define HAL module + hal_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) + hal_module.HAL = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) + hal_module.HAL.motors = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("motors", None)) + + # Add HAL functions + hal_module.HAL.get_frontal_image = self.hal.get_frontal_image + hal_module.HAL.get_ventral_image = self.hal.get_ventral_image + hal_module.HAL.takeoff = self.hal.takeoff + hal_module.HAL.land = self.hal.land + hal_module.HAL.set_cmd_pos = self.hal.set_cmd_pos + hal_module.HAL.set_cmd_vel = self.hal.set_cmd_vel + hal_module.HAL.set_cmd_mix = self.hal.set_cmd_mix + hal_module.HAL.get_position = self.hal.get_position + hal_module.HAL.get_velocity = self.hal.get_velocity + hal_module.HAL.get_orientation = self.hal.get_orientation + hal_module.HAL.get_roll = self.hal.get_roll + hal_module.HAL.get_pitch = self.hal.get_pitch + hal_module.HAL.get_yaw = self.hal.get_yaw + hal_module.HAL.get_landed_state = self.hal.get_landed_state + hal_module.HAL.get_yaw_rate = self.hal.get_yaw_rate + + + + # Define GUI module + gui_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) + gui_module.GUI = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) + + # Add GUI functions + gui_module.GUI.showImage = self.gui.showImage + gui_module.GUI.showLeftImage = self.gui.showLeftImage + + # Adding modules to system + # Protip: The names should be different from + # other modules, otherwise some errors + sys.modules["HAL"] = hal_module + sys.modules["GUI"] = gui_module + + return gui_module, hal_module + + # Function to measure the frequency of iterations + def measure_frequency(self): + previous_time = datetime.now() + # An infinite loop + while not self.exit_signal.is_set(): + # Sleep for 2 seconds + time.sleep(2) + + # Measure the current time and subtract from the previous time to get real time interval + current_time = datetime.now() + dt = current_time - previous_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + previous_time = current_time + + # Get the time period + try: + # Division by zero + self.ideal_cycle.add(ms / self.iteration_counter) + except: + self.ideal_cycle.add(0) + + # Reset the counter + self.iteration_counter = 0 \ No newline at end of file diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/brain_guest.py b/exercises/static/exercises/drone_cat_mouse/web-template/brain_guest.py new file mode 100644 index 000000000..ccf71a3b4 --- /dev/null +++ b/exercises/static/exercises/drone_cat_mouse/web-template/brain_guest.py @@ -0,0 +1,165 @@ +import time +import threading +import multiprocessing +import sys +from datetime import datetime +import re +import json +import importlib + +import rospy +from std_srvs.srv import Empty +import cv2 + +from user_functions_guest import GUIFunctions, HALFunctions +from console import start_console, close_console + +from shared.value import SharedValue + +# The brain process class +class BrainProcess(multiprocessing.Process): + def __init__(self, code, exit_signal): + super(BrainProcess, self).__init__() + + # Initialize exit signal + self.exit_signal = exit_signal + + # Function definitions for users to use + self.hal = HALFunctions() + self.gui = GUIFunctions() + + # Time variables + self.time_cycle = SharedValue('brain_time_cycle') + self.ideal_cycle = SharedValue('brain_ideal_cycle') + self.iteration_counter = 0 + + # Get the sequential and iterative code + # Something wrong over here! The code is reversing + # Found a solution but could not find the reason for this + self.sequential_code = code[1] + self.iterative_code = code[0] + + # Function to run to start the process + def run(self): + # Two threads for running and measuring + self.measure_thread = threading.Thread(target=self.measure_frequency) + self.thread = threading.Thread(target=self.process_code) + + self.measure_thread.start() + self.thread.start() + + print("[GUEST] Brain Process Started!") + + self.exit_signal.wait() + + # The process function + def process_code(self): + # Redirect information to console + start_console() + + # Reference Environment for the exec() function + iterative_code, sequential_code = self.iterative_code, self.sequential_code + + # The Python exec function + # Run the sequential part + gui_module, hal_module = self.generate_modules() + if sequential_code != "": + reference_environment = {"GUI": gui_module, "HAL": hal_module} + exec(sequential_code, reference_environment) + + # Run the iterative part inside template + # and keep the check for flag + while not self.exit_signal.is_set(): + start_time = datetime.now() + + # Execute the iterative portion + if iterative_code != "": + exec(iterative_code, reference_environment) + + # Template specifics to run! + finish_time = datetime.now() + dt = finish_time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + + # Keep updating the iteration counter + if(iterative_code == ""): + self.iteration_counter = 0 + else: + self.iteration_counter = self.iteration_counter + 1 + + # The code should be run for atleast the target time step + # If it's less put to sleep + # If it's more no problem as such, but we can change it! + time_cycle = self.time_cycle.get() + + if(ms < time_cycle): + time.sleep((time_cycle - ms) / 1000.0) + + close_console() + print("Current Thread Joined!") + + + # Function to generate the modules for use in ACE Editor + def generate_modules(self): + # Define HAL module + hal_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) + hal_module.HAL = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) + hal_module.HAL.motors = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("motors", None)) + + # Add HAL functions + hal_module.HAL.get_frontal_image = self.hal.get_frontal_image + hal_module.HAL.get_ventral_image = self.hal.get_ventral_image + hal_module.HAL.takeoff = self.hal.takeoff + hal_module.HAL.land = self.hal.land + hal_module.HAL.set_cmd_pos = self.hal.set_cmd_pos + hal_module.HAL.set_cmd_vel = self.hal.set_cmd_vel + hal_module.HAL.set_cmd_mix = self.hal.set_cmd_mix + hal_module.HAL.get_position = self.hal.get_position + hal_module.HAL.get_velocity = self.hal.get_velocity + hal_module.HAL.get_orientation = self.hal.get_orientation + hal_module.HAL.get_roll = self.hal.get_roll + hal_module.HAL.get_pitch = self.hal.get_pitch + hal_module.HAL.get_yaw = self.hal.get_yaw + hal_module.HAL.get_landed_state = self.hal.get_landed_state + hal_module.HAL.get_yaw_rate = self.hal.get_yaw_rate + + # Define GUI module + gui_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) + gui_module.GUI = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) + + # Add GUI functions + gui_module.GUI.showImage = self.gui.showImage + gui_module.GUI.showLeftImage = self.gui.showLeftImage + + + # Adding modules to system + # Protip: The names should be different from + # other modules, otherwise some errors + sys.modules["HAL"] = hal_module + sys.modules["GUI"] = gui_module + + return gui_module, hal_module + + # Function to measure the frequency of iterations + def measure_frequency(self): + previous_time = datetime.now() + # An infinite loop + while not self.exit_signal.is_set(): + # Sleep for 2 seconds + time.sleep(2) + + # Measure the current time and subtract from the previous time to get real time interval + current_time = datetime.now() + dt = current_time - previous_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + previous_time = current_time + + # Get the time period + try: + # Division by zero + self.ideal_cycle.add(ms / self.iteration_counter) + except: + self.ideal_cycle.add(0) + + # Reset the counter + self.iteration_counter = 0 diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/exercise.py b/exercises/static/exercises/drone_cat_mouse/web-template/exercise.py index c5cb647da..34492ac29 100644 --- a/exercises/static/exercises/drone_cat_mouse/web-template/exercise.py +++ b/exercises/static/exercises/drone_cat_mouse/web-template/exercise.py @@ -5,6 +5,7 @@ from websocket_server import WebsocketServer import time import threading +import multiprocessing import subprocess import sys from datetime import datetime @@ -14,8 +15,9 @@ import rospy from std_srvs.srv import Empty - -from gui import GUI, ThreadGUI +from shared.value import SharedValue +from brain import BrainProcess +import queue from hal import HAL from console import start_console, close_console @@ -25,36 +27,62 @@ class Template: # self.ideal_cycle to run an execution for at least 1 second # self.process for the current running process def __init__(self): - self.measure_thread = None - self.thread = None - self.reload = False - self.stop_brain = True - self.user_code = "" + self.brain_process = None + self.reload = multiprocessing.Event() + # Time variables - self.ideal_cycle = 80 - self.measured_cycle = 80 - self.iteration_counter = 0 + self.brain_time_cycle = SharedValue('brain_time_cycle') + self.brain_ideal_cycle = SharedValue('brain_ideal_cycle') self.real_time_factor = 0 self.frequency_message = {'brain': '', 'gui': '', 'rtf': ''} + # GUI variables + self.gui_time_cycle = SharedValue('gui_time_cycle') + self.gui_ideal_cycle = SharedValue('gui_ideal_cycle') self.server = None self.client = None self.host = sys.argv[1] - # Initialize the GUI, HAL and Console behind the scenes self.hal = HAL() - self.gui = GUI(self.host, self.hal) + self.paused = False # Function to parse the code # A few assumptions: # 1. The user always passes sequential and iterative codes # 2. Only a single infinite loop def parse_code(self, source_code): - sequential_code, iterative_code = self.seperate_seq_iter(source_code) + # Check for save/load + if(source_code[:5] == "#save"): + source_code = source_code[5:] + self.save_code(source_code) + + return "", "" + + elif(source_code[:5] == "#load"): + source_code = source_code + self.load_code() + self.server.send_message(self.client, source_code) + + return "", "" + + else: + sequential_code, iterative_code = self.seperate_seq_iter(source_code[6:]) return iterative_code, sequential_code - # Function to separate the iterative and sequential code + + # Function for saving + def save_code(self, source_code): + with open('code/academy.py', 'w') as code_file: + code_file.write(source_code) + + # Function for loading + def load_code(self): + with open('code/academy.py', 'r') as code_file: + source_code = code_file.read() + + return source_code + + # Function to seperate the iterative and sequential code def seperate_seq_iter(self, source_code): if source_code == "": return "", "" @@ -62,8 +90,8 @@ def seperate_seq_iter(self, source_code): # Search for an instance of while True infinite_loop = re.search(r'[^ ]while\s*\(\s*True\s*\)\s*:|[^ ]while\s*True\s*:|[^ ]while\s*1\s*:|[^ ]while\s*\(\s*1\s*\)\s*:', source_code) - # Separate the content inside while True and the other - # (Separating the sequential and iterative part!) + # Seperate the content inside while True and the other + # (Seperating the sequential and iterative part!) try: start_index = infinite_loop.start() iterative_code = source_code[start_index:] @@ -84,138 +112,17 @@ def seperate_seq_iter(self, source_code): iterative_code = "" return sequential_code, iterative_code - - # The process function - def process_code(self, source_code): - # Redirect the information to console - start_console() - - iterative_code, sequential_code = self.parse_code(source_code) - - # print(sequential_code) - # print(iterative_code) - - # The Python exec function - # Run the sequential part - gui_module, hal_module = self.generate_modules() - reference_environment = {"GUI": gui_module, "HAL": hal_module} - while (self.stop_brain == True): - if (self.reload == True): - return - time.sleep(0.1) - exec(sequential_code, reference_environment) - - # Run the iterative part inside template - # and keep the check for flag - while self.reload == False: - while (self.stop_brain == True): - if (self.reload == True): - return - time.sleep(0.1) - - start_time = datetime.now() - - # Execute the iterative portion - exec(iterative_code, reference_environment) - - # Template specifics to run! - finish_time = datetime.now() - dt = finish_time - start_time - ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 - - # Keep updating the iteration counter - if (iterative_code == ""): - self.iteration_counter = 0 - else: - self.iteration_counter = self.iteration_counter + 1 - - # The code should be run for atleast the target time step - # If it's less put to sleep - if (ms < self.ideal_cycle): - time.sleep((self.ideal_cycle - ms) / 1000.0) - - close_console() - print("Current Thread Joined!") - - # Function to generate the modules for use in ACE Editor - def generate_modules(self): - # Define HAL module - hal_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) - hal_module.HAL = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) - # hal_module.drone = imp.new_module("drone") - # motors# hal_module.HAL.motors = imp.new_module("motors") - - # Add HAL functions - hal_module.HAL.get_frontal_image = self.hal.get_frontal_image - hal_module.HAL.get_ventral_image = self.hal.get_ventral_image - hal_module.HAL.get_position = self.hal.get_position - hal_module.HAL.get_velocity = self.hal.get_velocity - hal_module.HAL.get_yaw_rate = self.hal.get_yaw_rate - hal_module.HAL.get_orientation = self.hal.get_orientation - hal_module.HAL.get_roll = self.hal.get_roll - hal_module.HAL.get_pitch = self.hal.get_pitch - hal_module.HAL.get_yaw = self.hal.get_yaw - hal_module.HAL.get_landed_state = self.hal.get_landed_state - hal_module.HAL.set_cmd_pos = self.hal.set_cmd_pos - hal_module.HAL.set_cmd_vel = self.hal.set_cmd_vel - hal_module.HAL.set_cmd_mix = self.hal.set_cmd_mix - hal_module.HAL.takeoff = self.hal.takeoff - hal_module.HAL.land = self.hal.land - - # Define GUI module - gui_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) - gui_module.GUI = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) - - # Add GUI functions - gui_module.GUI.showImage = self.gui.showImage - gui_module.GUI.showLeftImage = self.gui.showLeftImage - - # Adding modules to system - # Protip: The names should be different from - # other modules, otherwise some errors - sys.modules["HAL"] = hal_module - sys.modules["GUI"] = gui_module - - return gui_module, hal_module - - # Function to measure the frequency of iterations - def measure_frequency(self): - previous_time = datetime.now() - # An infinite loop - while True: - # Sleep for 2 seconds - time.sleep(2) - - # Measure the current time and subtract from the previous time to get real time interval - current_time = datetime.now() - dt = current_time - previous_time - ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 - previous_time = current_time - - # Get the time period - try: - # Division by zero - self.measured_cycle = ms / self.iteration_counter - except: - self.measured_cycle = 0 - - # Reset the counter - self.iteration_counter = 0 - - # Send to client - self.send_frequency_message() - - # Function to generate and send frequency messages + def send_frequency_message(self): # This function generates and sends frequency measures of the brain and gui - brain_frequency = 0; gui_frequency = 0 + brain_frequency = 0;gui_frequency = 0 try: - brain_frequency = round(1000 / self.measured_cycle, 1) + brain_frequency = round(1000 / self.brain_ideal_cycle.get(), 1) except ZeroDivisionError: brain_frequency = 0 try: - gui_frequency = round(1000 / self.thread_gui.measured_cycle, 1) + gui_frequency = round(1000 / self.gui_ideal_cycle.get(), 1) except ZeroDivisionError: gui_frequency = 0 @@ -240,29 +147,32 @@ def track_stats(self): args = ["gz", "stats", "-p"] # Prints gz statistics. "-p": Output comma-separated values containing- # real-time factor (percent), simtime (sec), realtime (sec), paused (T or F) - stats_process = subprocess.Popen(args, stdout=subprocess.PIPE, universal_newlines=True) + stats_process = subprocess.Popen(args, stdout=subprocess.PIPE, bufsize=0) # bufsize=1 enables line-bufferred mode (the input buffer is flushed # automatically on newlines if you would write to process.stdin ) with stats_process.stdout: - for line in iter(stats_process.stdout.readline, ''): - stats_list = [x.strip() for x in line.split(',')] - self.real_time_factor = stats_list[0] + for line in iter(stats_process.stdout.readline, b''): + stats_list = [x.strip() for x in line.split(b',')] + self.real_time_factor = stats_list[0].decode("utf-8") # Function to maintain thread execution def execute_thread(self, source_code): # Keep checking until the thread is alive # The thread will die when the coming iteration reads the flag - if self.thread is not None: - while self.thread.is_alive(): - time.sleep(0.2) + if(self.brain_process != None): + while self.brain_process.is_alive(): + pass # Turn the flag down, the iteration has successfully stopped! - self.reload = False + self.reload.clear() # New thread execution - self.thread = threading.Thread(target=self.process_code, args=[source_code]) - self.thread.start() + code = self.parse_code(source_code) + if code[0] == "" and code[1] == "": + return + + self.brain_process = BrainProcess(code, self.reload) + self.brain_process.start() self.send_code_message() - print("New Thread Started!") # Function to read and set frequency from incoming message def read_frequency_message(self, message): @@ -270,66 +180,57 @@ def read_frequency_message(self, message): # Set brain frequency frequency = float(frequency_message["brain"]) - self.ideal_cycle = 1000.0 / frequency + self.brain_time_cycle.add(1000.0 / frequency) # Set gui frequency frequency = float(frequency_message["gui"]) - self.thread_gui.ideal_cycle = 1000.0 / frequency + self.gui_time_cycle.add(1000.0 / frequency) return # The websocket function # Gets called when there is an incoming message from the client def handle(self, client, server, message): - if message[:5] == "#freq": + if(message[:5] == "#freq"): frequency_message = message[5:] self.read_frequency_message(frequency_message) time.sleep(1) + self.send_frequency_message() return - elif(message[:5] == "#ping"): time.sleep(1) self.send_ping_message() return - elif (message[:5] == "#code"): try: # Once received turn the reload flag up and send it to execute_thread function - self.user_code = message[6:] + code = message # print(repr(code)) - self.reload = True - self.execute_thread(self.user_code) + self.reload.set() + self.execute_thread(code) except: pass - elif (message[:5] == "#rest"): + elif (message[:5] == "#stop"): try: - self.reload = True - self.stop_brain = True - self.execute_thread(self.user_code) + self.reload.set() + self.execute_thread(code) except: pass - - elif (message[:5] == "#stop"): - self.stop_brain = True - - elif (message[:5] == "#play"): - self.stop_brain = False + self.server.send_message(self.client, "#stpd") # Function that gets called when the server is connected def connected(self, client, server): self.client = client - # Start the GUI update thread - self.thread_gui = ThreadGUI(self.gui) - self.thread_gui.start() - - # Start the real time factor tracker thread + # Start the HAL update thread + self.hal.start_thread() + # Start real time factor tracker thread self.stats_thread = threading.Thread(target=self.track_stats) self.stats_thread.start() - # Start measure frequency - self.measure_thread = threading.Thread(target=self.measure_frequency) - self.measure_thread.start() + # Initialize the ping message + self.send_frequency_message() + print("After sneding freweq msg") print(client, 'connected') diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/exercise_guest.py b/exercises/static/exercises/drone_cat_mouse/web-template/exercise_guest.py index 3ccaf1926..d42270c98 100644 --- a/exercises/static/exercises/drone_cat_mouse/web-template/exercise_guest.py +++ b/exercises/static/exercises/drone_cat_mouse/web-template/exercise_guest.py @@ -1,10 +1,8 @@ #!/usr/bin/env python - -from __future__ import print_function - from websocket_server import WebsocketServer import time import threading +import multiprocessing import subprocess import sys from datetime import datetime @@ -14,45 +12,94 @@ import rospy from std_srvs.srv import Empty +import cv2 + +from shared.value import SharedValue -from gui_guest import GUI, ThreadGUI from hal_guest import HAL -from console import start_console, close_console +from brain_guest import BrainProcess +import queue class Template: # Initialize class variables - # self.ideal_cycle to run an execution for at least 1 second + # self.time_cycle to run an execution for atleast 1 second # self.process for the current running process def __init__(self): - self.measure_thread = None - self.thread = None - self.reload = False + self.brain_process = None + self.reload = multiprocessing.Event() # Time variables - self.ideal_cycle = 80 - self.measured_cycle = 80 - self.iteration_counter = 0 + self.brain_time_cycle = SharedValue('brain_time_cycle') + self.brain_ideal_cycle = SharedValue('brain_ideal_cycle') self.real_time_factor = 0 + self.frequency_message = {'brain': '', 'gui': '', 'rtf': ''} + # GUI variables + self.gui_time_cycle = SharedValue('gui_time_cycle') + self.gui_ideal_cycle = SharedValue('gui_ideal_cycle') + self.server = None self.client = None self.host = sys.argv[1] - # Initialize the GUI, HAL and Console behind the scenes + # Initialize the GUI and HAL behind the scenes self.hal = HAL() - self.gui = GUI(self.host, self.hal) + self.paused = False + + # Function for saving + def save_code(self, source_code): + with open('code/academy.py', 'w') as code_file: + code_file.write(source_code) + + # Function for loading + def load_code(self): + with open('code/academy.py', 'r') as code_file: + source_code = code_file.read() + + return source_code # Function to parse the code # A few assumptions: # 1. The user always passes sequential and iterative codes # 2. Only a single infinite loop def parse_code(self, source_code): - sequential_code, iterative_code = self.seperate_seq_iter(source_code) - return iterative_code, sequential_code + # Check for save/load + if(source_code[:5] == "#save"): + source_code = source_code[5:] + self.save_code(source_code) + + return "", "" + + elif(source_code[:5] == "#load"): + source_code = source_code + self.load_code() + self.server.send_message(self.client, source_code) + + return "", "" + + elif(source_code[:5] == "#resu"): + restart_simulation = rospy.ServiceProxy('/gazebo/unpause_physics', Empty) + restart_simulation() + + return "", "" + + elif(source_code[:5] == "#paus"): + pause_simulation = rospy.ServiceProxy('/gazebo/pause_physics', Empty) + pause_simulation() + + return "", "" - # Function to separate the iterative and sequential code + elif(source_code[:5] == "#rest"): + reset_simulation = rospy.ServiceProxy('/gazebo/reset_world', Empty) + reset_simulation() + return "", "" + + else: + sequential_code, iterative_code = self.seperate_seq_iter(source_code) + return iterative_code, sequential_code + + # Function to seperate the iterative and sequential code def seperate_seq_iter(self, source_code): if source_code == "": return "", "" @@ -60,8 +107,8 @@ def seperate_seq_iter(self, source_code): # Search for an instance of while True infinite_loop = re.search(r'[^ ]while\s*\(\s*True\s*\)\s*:|[^ ]while\s*True\s*:|[^ ]while\s*1\s*:|[^ ]while\s*\(\s*1\s*\)\s*:', source_code) - # Separate the content inside while True and the other - # (Separating the sequential and iterative part!) + # Seperate the content inside while True and the other + # (Seperating the sequential and iterative part!) try: start_index = infinite_loop.start() iterative_code = source_code[start_index:] @@ -78,128 +125,19 @@ def seperate_seq_iter(self, source_code): return sequential_code, iterative_code - # The process function - def process_code(self, source_code): - # Redirect the information to console - start_console() - - iterative_code, sequential_code = self.parse_code(source_code) - - # print(sequential_code) - # print(iterative_code) - - # The Python exec function - # Run the sequential part - gui_module, hal_module = self.generate_modules() - reference_environment = {"GUI": gui_module, "HAL": hal_module} - exec(sequential_code, reference_environment) - - # Run the iterative part inside template - # and keep the check for flag - while self.reload == False: - start_time = datetime.now() - - # Execute the iterative portion - exec(iterative_code, reference_environment) - - # Template specifics to run! - finish_time = datetime.now() - dt = finish_time - start_time - ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 - - # Keep updating the iteration counter - if (iterative_code == ""): - self.iteration_counter = 0 - else: - self.iteration_counter = self.iteration_counter + 1 - - # The code should be run for atleast the target time step - # If it's less put to sleep - if (ms < self.ideal_cycle): - time.sleep((self.ideal_cycle - ms) / 1000.0) - - close_console() - print("Current Thread Joined!") - - # Function to generate the modules for use in ACE Editor - def generate_modules(self): - # Define HAL module - hal_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) - hal_module.HAL = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) - # hal_module.drone = imp.new_module("drone") - # motors# hal_module.HAL.motors = imp.new_module("motors") - - # Add HAL functions - hal_module.HAL.get_frontal_image = self.hal.get_frontal_image - hal_module.HAL.get_ventral_image = self.hal.get_ventral_image - hal_module.HAL.get_position = self.hal.get_position - hal_module.HAL.get_velocity = self.hal.get_velocity - hal_module.HAL.get_yaw_rate = self.hal.get_yaw_rate - hal_module.HAL.get_orientation = self.hal.get_orientation - hal_module.HAL.get_roll = self.hal.get_roll - hal_module.HAL.get_pitch = self.hal.get_pitch - hal_module.HAL.get_yaw = self.hal.get_yaw - hal_module.HAL.get_landed_state = self.hal.get_landed_state - hal_module.HAL.set_cmd_pos = self.hal.set_cmd_pos - hal_module.HAL.set_cmd_vel = self.hal.set_cmd_vel - hal_module.HAL.set_cmd_mix = self.hal.set_cmd_mix - hal_module.HAL.takeoff = self.hal.takeoff - hal_module.HAL.land = self.hal.land - - # Define GUI module - gui_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) - gui_module.GUI = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) - - # Add GUI functions - gui_module.GUI.showImage = self.gui.showImage - gui_module.GUI.showLeftImage = self.gui.showLeftImage - - # Adding modules to system - # Protip: The names should be different from - # other modules, otherwise some errors - sys.modules["HAL"] = hal_module - sys.modules["GUI"] = gui_module - - return gui_module, hal_module - - # Function to measure the frequency of iterations - def measure_frequency(self): - previous_time = datetime.now() - # An infinite loop - while True: - # Sleep for 2 seconds - time.sleep(2) - - # Measure the current time and subtract from the previous time to get real time interval - current_time = datetime.now() - dt = current_time - previous_time - ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 - previous_time = current_time - - # Get the time period - try: - # Division by zero - self.measured_cycle = ms / self.iteration_counter - except: - self.measured_cycle = 0 - - # Reset the counter - self.iteration_counter = 0 - - # Send to client - self.send_frequency_message() - # Function to generate and send frequency messages + def send_frequency_message(self): # This function generates and sends frequency measures of the brain and gui - brain_frequency = 0; gui_frequency = 0 + brain_frequency = 0 + gui_frequency = 0 try: - brain_frequency = round(1000 / self.measured_cycle, 1) + brain_frequency = round(1000 / self.brain_ideal_cycle.get(), 1) except ZeroDivisionError: brain_frequency = 0 try: - gui_frequency = round(1000 / self.thread_gui.measured_cycle, 1) + gui_frequency = round(1000 / self.gui_ideal_cycle.get(), 1) except ZeroDivisionError: gui_frequency = 0 @@ -217,28 +155,33 @@ def track_stats(self): args = ["gz", "stats", "-p"] # Prints gz statistics. "-p": Output comma-separated values containing- # real-time factor (percent), simtime (sec), realtime (sec), paused (T or F) - stats_process = subprocess.Popen(args, stdout=subprocess.PIPE, bufsize=1, universal_newlines=True) + stats_process = subprocess.Popen(args, stdout=subprocess.PIPE, bufsize=0) # bufsize=1 enables line-bufferred mode (the input buffer is flushed # automatically on newlines if you would write to process.stdin ) with stats_process.stdout: - for line in iter(stats_process.stdout.readline, ''): - stats_list = [x.strip() for x in line.split(',')] - self.real_time_factor = stats_list[0] + for line in iter(stats_process.stdout.readline, b''): + stats_list = [x.strip() for x in line.split(b',')] + self.real_time_factor = stats_list[0].decode("utf-8") + + # Function to maintain thread execution def execute_thread(self, source_code): # Keep checking until the thread is alive # The thread will die when the coming iteration reads the flag - if self.thread is not None: - while self.thread.is_alive(): - time.sleep(0.2) + if(self.brain_process != None): + while self.brain_process.is_alive(): + pass # Turn the flag down, the iteration has successfully stopped! - self.reload = False + self.reload.clear() # New thread execution - self.thread = threading.Thread(target=self.process_code, args=[source_code]) - self.thread.start() - print("New Thread Started!") + code = self.parse_code(source_code) + if code[0] == "" and code[1] == "": + return + + self.brain_process = BrainProcess(code, self.reload) + self.brain_process.start() # Function to read and set frequency from incoming message def read_frequency_message(self, message): @@ -246,46 +189,46 @@ def read_frequency_message(self, message): # Set brain frequency frequency = float(frequency_message["brain"]) - self.ideal_cycle = 1000.0 / frequency + self.brain_time_cycle.add(1000.0 / frequency) # Set gui frequency frequency = float(frequency_message["gui"]) - self.thread_gui.ideal_cycle = 1000.0 / frequency + self.gui_time_cycle.add(1000.0 / frequency) return # The websocket function # Gets called when there is an incoming message from the client def handle(self, client, server, message): - if message[:5] == "#freq": + if(message[:5] == "#freq"): frequency_message = message[5:] self.read_frequency_message(frequency_message) time.sleep(1) - return - - try: + self.send_frequency_message() + return + try: + self.paused = False # Once received turn the reload flag up and send it to execute_thread function code = message # print(repr(code)) - self.reload = True + self.reload.set() self.execute_thread(code) except: pass + # Function that gets called when the server is connected def connected(self, client, server): self.client = client - # Start the GUI update thread - self.thread_gui = ThreadGUI(self.gui) - self.thread_gui.start() + # Start the HAL update thread + self.hal.start_thread() - # Start the real time factor tracker thread + # Start real time factor tracker thread self.stats_thread = threading.Thread(target=self.track_stats) self.stats_thread.start() - # Start measure frequency - self.measure_thread = threading.Thread(target=self.measure_frequency) - self.measure_thread.start() + # Initialize the ping message + self.send_frequency_message() print(client, 'connected') diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/gui.py b/exercises/static/exercises/drone_cat_mouse/web-template/gui.py index 7ab9f1055..15d62dc63 100644 --- a/exercises/static/exercises/drone_cat_mouse/web-template/gui.py +++ b/exercises/static/exercises/drone_cat_mouse/web-template/gui.py @@ -6,62 +6,51 @@ from datetime import datetime from websocket_server import WebsocketServer import logging +import rospy +import cv2 +import sys import numpy as np -from hal import HAL +import multiprocessing + +from interfaces.pose3d import ListenerPose3d +from shared.image import SharedImage +from shared.value import SharedValue +from shared.mouse import Mouse # Graphical User Interface Class class GUI: # Initialization function # The actual initialization - def __init__(self, host, hal): - t = threading.Thread(target=self.run_server) + def __init__(self, host, mouse): + rospy.init_node("GUI") self.payload = {'image': ''} self.left_payload = {'image': ''} - self.dist = {'dist': '', 'ready': ''} self.server = None self.client = None self.host = host - # Image variables - self.image_to_be_shown = None - self.image_to_be_shown_updated = False - self.image_show_lock = threading.Lock() - - self.left_image_to_be_shown = None - self.left_image_to_be_shown_updated = False - self.left_image_show_lock = threading.Lock() - - self.acknowledge = False - self.acknowledge_lock = threading.Lock() + # Image variable host + self.shared_image = SharedImage("guifrontalimage") + self.shared_left_image = SharedImage("guiventralimage") - # Take the console object to set the same websocket and client - self.hal = hal - t.start() + # Event objects for multiprocessing + self.ack_event = multiprocessing.Event() + self.cli_event = multiprocessing.Event() - # Explicit initialization function - # Class method, so user can call it without instantiation - @classmethod - def initGUI(cls, host): - # self.payload = {'image': '', 'shape': []} - new_instance = cls(host) - return new_instance + self.mouse = mouse + # Start server thread + t = threading.Thread(target=self.run_server) + t.start() + # Function to prepare image payload # Encodes the image as a JSON string and sends through the WS def payloadImage(self): - self.image_show_lock.acquire() - image_to_be_shown_updated = self.image_to_be_shown_updated - image_to_be_shown = self.image_to_be_shown - self.image_show_lock.release() - - image = image_to_be_shown + image = self.shared_image.get() payload = {'image': '', 'shape': ''} - if not image_to_be_shown_updated: - return payload - shape = image.shape frame = cv2.imencode('.JPEG', image)[1] encoded_image = base64.b64encode(frame) @@ -69,26 +58,14 @@ def payloadImage(self): payload['image'] = encoded_image.decode('utf-8') payload['shape'] = shape - self.image_show_lock.acquire() - self.image_to_be_shown_updated = False - self.image_show_lock.release() - return payload # Function to prepare image payload # Encodes the image as a JSON string and sends through the WS def payloadLeftImage(self): - self.left_image_show_lock.acquire() - left_image_to_be_shown_updated = self.left_image_to_be_shown_updated - left_image_to_be_shown = self.left_image_to_be_shown - self.left_image_show_lock.release() - - image = left_image_to_be_shown + image = self.shared_left_image.get() payload = {'image': '', 'shape': ''} - if not left_image_to_be_shown_updated: - return payload - shape = image.shape frame = cv2.imencode('.JPEG', image)[1] encoded_image = base64.b64encode(frame) @@ -96,10 +73,6 @@ def payloadLeftImage(self): payload['image'] = encoded_image.decode('utf-8') payload['shape'] = shape - self.left_image_show_lock.acquire() - self.left_image_to_be_shown_updated = False - self.left_image_show_lock.release() - return payload # Function for student to call @@ -120,29 +93,19 @@ def showLeftImage(self, image): # Called when a new client is received def get_client(self, client, server): self.client = client + self.cli_event.set() - # Function to get value of Acknowledge - def get_acknowledge(self): - self.acknowledge_lock.acquire() - acknowledge = self.acknowledge - self.acknowledge_lock.release() - - return acknowledge - - # Function to get value of Acknowledge - def set_acknowledge(self, value): - self.acknowledge_lock.acquire() - self.acknowledge = value - self.acknowledge_lock.release() - + print(client, 'connected') + + # Update the gui def update_gui(self): # Payload Image Message payload = self.payloadImage() self.payload["image"] = json.dumps(payload) - # Add position to payload - cat_x, cat_y, cat_z = self.hal.get_position() - self.payload["pos"] = [cat_x, cat_y, cat_z] + # # Add position to payload + # cat_x, cat_y, cat_z = self.hal.get_position() + # self.payload["pos"] = [cat_x, cat_y, cat_z] message = "#gui" + json.dumps(self.payload) self.server.send_message(self.client, message) @@ -154,39 +117,44 @@ def update_gui(self): message = "#gul" + json.dumps(self.left_payload) self.server.send_message(self.client, message) - # Update distance between cat and mouse drones - def update_dist(self): - cat_x, cat_y, cat_z = self.hal.get_position() - mouse_x, mouse_y, mouse_z = self.mouse.get_position() + # # Update distance between cat and mouse drones + # def update_dist(self): + # cat_x, cat_y, cat_z = self.hal.get_position() + # mouse_x, mouse_y, mouse_z = self.mouse.get_position() - if mouse_z > 0.1: self.dist["ready"] = "true" - else: self.dist["ready"] = "false" + # if mouse_z > 0.1: self.dist["ready"] = "true" + # else: self.dist["ready"] = "false" - dist = np.sqrt((mouse_x+2-cat_x)**2 + (mouse_y-cat_y)**2 + (mouse_z-cat_z)**2) - dist = int(dist*100)/100 - self.dist["dist"] = dist + # dist = np.sqrt((mouse_x+2-cat_x)**2 + (mouse_y-cat_y)**2 + (mouse_z-cat_z)**2) + # dist = int(dist*100)/100 + # self.dist["dist"] = dist - message = '#dst' + json.dumps(self.dist) - self.server.send_message(self.client, message) + # message = '#dst' + json.dumps(self.dist) + # self.server.send_message(self.client, message) # Function to read the message from websocket # Gets called when there is an incoming message from the client def get_message(self, client, server, message): # Acknowledge Message for GUI Thread - if message[:4] == "#ack": - self.set_acknowledge(True) - # elif message[:4] == "#mou": - # self.mouse.start_mouse(int(message[4:5])) - # elif message[:4] == "#stp": - # self.mouse.stop_mouse() - # elif message[:4] == "#rst": - # self.mouse.reset_mouse() + if(message[:4] == "#ack"): + # Set acknowledgement flag + self.ack_event.set() + elif message[:4] == "#mou": + self.mouse.start_mouse(int(message[4:5])) + elif message[:4] == "#stp": + self.mouse.stop_mouse() + elif message[:4] == "#rst": + self.mouse.reset_mouse() + # Function that gets called when the connected closes + def handle_close(self, client, server): + print(client, 'closed') # Activate the server def run_server(self): self.server = WebsocketServer(port=2303, host=self.host) self.server.set_fn_new_client(self.get_client) self.server.set_fn_message_received(self.get_message) + self.server.set_fn_client_left(self.handle_close) logged = False while not logged: @@ -207,32 +175,45 @@ def reset_gui(self): # This class decouples the user thread # and the GUI update thread -class ThreadGUI: - def __init__(self, gui): - self.gui = gui +class ProcessGUI(multiprocessing.Process): + def __init__(self): + super(ProcessGUI, self).__init__() + self.host = sys.argv[1] + self.mouse = Mouse() # Time variables - self.ideal_cycle = 80 - self.measured_cycle = 80 + self.time_cycle = SharedValue("gui_time_cycle") + self.ideal_cycle = SharedValue("gui_ideal_cycle") self.iteration_counter = 0 + # Function to initialize events + def initialize_events(self): + # Events + self.ack_event = self.gui.ack_event + self.cli_event = self.gui.cli_event + self.exit_signal = multiprocessing.Event() # Function to start the execution of threads - def start(self): + def run(self): + # Initialize GUI + self.gui = GUI(self.host, self.mouse) + self.initialize_events() + + # Wait for client before starting + self.cli_event.wait() self.measure_thread = threading.Thread(target=self.measure_thread) - self.thread = threading.Thread(target=self.run) + self.thread = threading.Thread(target=self.run_gui) self.measure_thread.start() self.thread.start() - print("GUI Thread Started!") + print("GUI Process Started!") + + self.exit_signal.wait() # The measuring thread to measure frequency def measure_thread(self): - while self.gui.client is None: - pass - previous_time = datetime.now() - while True: + while(True): # Sleep for 2 seconds time.sleep(2) @@ -245,33 +226,40 @@ def measure_thread(self): # Get the time period try: # Division by zero - self.measured_cycle = ms / self.iteration_counter + self.ideal_cycle.add(ms / self.iteration_counter) except: - self.measured_cycle = 0 + self.ideal_cycle.add(0) # Reset the counter self.iteration_counter = 0 # The main thread of execution - def run(self): - while self.gui.client is None: - pass - - while True: + def run_gui(self): + while(True): start_time = datetime.now() + # Send update signal self.gui.update_gui() - #self.gui.update_dist() - acknowledge_message = self.gui.get_acknowledge() - while not acknowledge_message: - acknowledge_message = self.gui.get_acknowledge() - - self.gui.set_acknowledge(False) + # Wait for acknowldege signal + self.ack_event.wait() + self.ack_event.clear() finish_time = datetime.now() self.iteration_counter = self.iteration_counter + 1 dt = finish_time - start_time ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 - if ms < self.ideal_cycle: - time.sleep((self.ideal_cycle-ms) / 1000.0) \ No newline at end of file + time_cycle = self.time_cycle.get() + + if(ms < time_cycle): + time.sleep((time_cycle-ms) / 1000.0) + + self.exit_signal.set() + + # Functions to handle auxillary GUI functions + def reset_gui(self): + self.gui.reset_gui() + +if __name__ == "__main__": + gui = ProcessGUI() + gui.start() \ No newline at end of file diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/gui_guest.py b/exercises/static/exercises/drone_cat_mouse/web-template/gui_guest.py index 7aac25b87..cb38e5f51 100644 --- a/exercises/static/exercises/drone_cat_mouse/web-template/gui_guest.py +++ b/exercises/static/exercises/drone_cat_mouse/web-template/gui_guest.py @@ -1,198 +1,132 @@ import json +import rospy import cv2 +import sys import base64 import threading import time +import numpy as np from datetime import datetime from websocket_server import WebsocketServer +import multiprocessing import logging -import numpy as np -from hal_guest import HAL + +from interfaces.pose3d import ListenerPose3d +from shared.image import SharedImage +from shared.value import SharedValue +from shared.mouse import Mouse # Graphical User Interface Class class GUI: # Initialization function # The actual initialization - def __init__(self, host, hal): - t = threading.Thread(target=self.run_server) - - self.payload = {'image': ''} - self.left_payload = {'image': ''} - self.dist = {'dist': '', 'ready': ''} + def __init__(self, host, mouse): + rospy.init_node("GUI_guest") + + self.payload = {'image_guest': ''} + self.left_payload = {'image_guest': ''} self.server = None self.client = None self.host = host - - # Image variables - self.image_to_be_shown = None - self.image_to_be_shown_updated = False - self.image_show_lock = threading.Lock() - - self.left_image_to_be_shown = None - self.left_image_to_be_shown_updated = False - self.left_image_show_lock = threading.Lock() - self.acknowledge = False - self.acknowledge_lock = threading.Lock() + # Image variable guest + self.shared_image_guest = SharedImage("guifrontalimageguest") + self.shared_left_image_guest = SharedImage("guiventralimageguest") - # Take the console object to set the same websocket and client - self.hal = hal - t.start() + # Event objects for multiprocessing + self.ack_event = multiprocessing.Event() + self.cli_event = multiprocessing.Event() - # Explicit initialization function - # Class method, so user can call it without instantiation - @classmethod - def initGUI(cls, host): - # self.payload = {'image': '', 'shape': []} - new_instance = cls(host) - return new_instance - - # Function to prepare image payload - # Encodes the image as a JSON string and sends through the WS + self.mouse = mouse + # Start server thread + t = threading.Thread(target=self.run_server) + t.start() + def payloadImage(self): - self.image_show_lock.acquire() - image_to_be_shown_updated = self.image_to_be_shown_updated - image_to_be_shown = self.image_to_be_shown - self.image_show_lock.release() - - image = image_to_be_shown - payload = {'image': '', 'shape': ''} - - if not image_to_be_shown_updated: - return payload - + image = self.shared_image_guest.get() + payload = {'image_guest': '', 'shape_guest': ''} + shape = image.shape frame = cv2.imencode('.JPEG', image)[1] encoded_image = base64.b64encode(frame) - - payload['image'] = encoded_image.decode('utf-8') - payload['shape'] = shape - - self.image_show_lock.acquire() - self.image_to_be_shown_updated = False - self.image_show_lock.release() - + + payload['image_guest'] = encoded_image.decode('utf-8') + payload['shape_guest'] = shape + return payload - + # Function to prepare image payload # Encodes the image as a JSON string and sends through the WS def payloadLeftImage(self): - self.left_image_show_lock.acquire() - left_image_to_be_shown_updated = self.left_image_to_be_shown_updated - left_image_to_be_shown = self.left_image_to_be_shown - self.left_image_show_lock.release() - - image = left_image_to_be_shown - payload = {'image': '', 'shape': ''} - - if not left_image_to_be_shown_updated: - return payload + image = self.shared_left_image_guest.get() + payload = {'image_guest': '', 'shape_guest': ''} shape = image.shape frame = cv2.imencode('.JPEG', image)[1] encoded_image = base64.b64encode(frame) - payload['image'] = encoded_image.decode('utf-8') - payload['shape'] = shape - - self.left_image_show_lock.acquire() - self.left_image_to_be_shown_updated = False - self.left_image_show_lock.release() + payload['image_guest'] = encoded_image.decode('utf-8') + payload['shape_guest'] = shape return payload - # Function for student to call - def showImage(self, image): - self.image_show_lock.acquire() - self.image_to_be_shown = image - self.image_to_be_shown_updated = True - self.image_show_lock.release() - - # Function for student to call - def showLeftImage(self, image): - self.left_image_show_lock.acquire() - self.left_image_to_be_shown = image - self.left_image_to_be_shown_updated = True - self.left_image_show_lock.release() - # Function to get the client # Called when a new client is received def get_client(self, client, server): self.client = client - - # Function to get value of Acknowledge - def get_acknowledge(self): - self.acknowledge_lock.acquire() - acknowledge = self.acknowledge - self.acknowledge_lock.release() - - return acknowledge - - # Function to get value of Acknowledge - def set_acknowledge(self, value): - self.acknowledge_lock.acquire() - self.acknowledge = value - self.acknowledge_lock.release() + self.cli_event.set() + + print(client, 'connected') # Update the gui def update_gui(self): - # Payload Image Message - payload = self.payloadImage() - self.payload["image"] = json.dumps(payload) - # Add position to payload - mouse_x, mouse_y, mouse_z = self.hal.get_position() - self.payload["pos"] = [mouse_x, mouse_y, mouse_z] - + # Payload Image Guest + payload_guest = self.payloadImage() + self.payload["image_guest"] = json.dumps(payload_guest) + message = "#gui" + json.dumps(self.payload) self.server.send_message(self.client, message) # Payload Left Image Message - left_payload = self.payloadLeftImage() - self.left_payload["image"] = json.dumps(left_payload) + left_payload_guest = self.payloadLeftImage() + self.left_payload["image_guest"] = json.dumps(left_payload_guest) message = "#gul" + json.dumps(self.left_payload) self.server.send_message(self.client, message) - # Update distance between cat and mouse drones - def update_dist(self): - cat_x, cat_y, cat_z = self.hal.get_position() - mouse_x, mouse_y, mouse_z = self.mouse.get_position() - - if mouse_z > 0.1: self.dist["ready"] = "true" - else: self.dist["ready"] = "false" - - dist = np.sqrt((mouse_x+2-cat_x)**2 + (mouse_y-cat_y)**2 + (mouse_z-cat_z)**2) - dist = int(dist*100)/100 - self.dist["dist"] = dist - - message = '#dst' + json.dumps(self.dist) - self.server.send_message(self.client, message) - # Function to read the message from websocket # Gets called when there is an incoming message from the client def get_message(self, client, server, message): # Acknowledge Message for GUI Thread - if message[:4] == "#ack": - self.set_acknowledge(True) - # elif message[:4] == "#mou": - # self.mouse.start_mouse(int(message[4:5])) - # elif message[:4] == "#stp": - # self.mouse.stop_mouse() - # elif message[:4] == "#rst": - # self.mouse.reset_mouse() + if(message[:4] == "#ack"): + # Set acknowledgement flag + self.ack_event.set() + elif message[:4] == "#mou": + self.mouse.start_mouse(int(message[4:5])) + elif message[:4] == "#stp": + self.mouse.stop_mouse() + elif message[:4] == "#rst": + self.mouse.reset_mouse() + # Reset message + elif(message[:5] == "#rest"): + self.reset_gui() + # Function that gets called when the connected closes + def handle_close(self, client, server): + print(client, 'closed') # Activate the server def run_server(self): self.server = WebsocketServer(port=2304, host=self.host) self.server.set_fn_new_client(self.get_client) self.server.set_fn_message_received(self.get_message) + self.server.set_fn_client_left(self.handle_close) logged = False while not logged: try: - f = open("/ws_gui_code.log", "w") - f.write("websocket_gui_code=ready") + f = open("/ws_gui_guest.log", "w") + f.write("websocket_gui_guest=ready") f.close() logged = True except: @@ -207,32 +141,46 @@ def reset_gui(self): # This class decouples the user thread # and the GUI update thread -class ThreadGUI: - def __init__(self, gui): - self.gui = gui +class ProcessGUI(multiprocessing.Process): + def __init__(self): + super(ProcessGUI, self).__init__() + self.host = sys.argv[1] + self.mouse = Mouse() # Time variables - self.ideal_cycle = 80 - self.measured_cycle = 80 + self.time_cycle = SharedValue("gui_time_cycle") + self.ideal_cycle = SharedValue("gui_ideal_cycle") self.iteration_counter = 0 + # Function to initialize events + def initialize_events(self): + # Events + self.ack_event = self.gui.ack_event + self.cli_event = self.gui.cli_event + self.exit_signal = multiprocessing.Event() + # Function to start the execution of threads - def start(self): + def run(self): + # Initialize GUI + self.gui = GUI(self.host, self.mouse) + self.initialize_events() + + # Wait for client before starting + self.cli_event.wait() self.measure_thread = threading.Thread(target=self.measure_thread) - self.thread = threading.Thread(target=self.run) + self.thread = threading.Thread(target=self.run_gui) self.measure_thread.start() self.thread.start() - print("GUI Thread Started!") + print("GUI Process Started!") + + self.exit_signal.wait() # The measuring thread to measure frequency def measure_thread(self): - while self.gui.client is None: - pass - previous_time = datetime.now() - while True: + while(True): # Sleep for 2 seconds time.sleep(2) @@ -245,33 +193,39 @@ def measure_thread(self): # Get the time period try: # Division by zero - self.measured_cycle = ms / self.iteration_counter + self.ideal_cycle.add(ms / self.iteration_counter) except: - self.measured_cycle = 0 + self.ideal_cycle.add(0) # Reset the counter self.iteration_counter = 0 # The main thread of execution - def run(self): - while self.gui.client is None: - pass - - while True: + def run_gui(self): + while(True): start_time = datetime.now() + # Send update signal self.gui.update_gui() - #self.gui.update_dist() - acknowledge_message = self.gui.get_acknowledge() - - while not acknowledge_message: - acknowledge_message = self.gui.get_acknowledge() - self.gui.set_acknowledge(False) + # Wait for acknowldege signal + self.ack_event.wait() + self.ack_event.clear() finish_time = datetime.now() self.iteration_counter = self.iteration_counter + 1 dt = finish_time - start_time ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 - if ms < self.ideal_cycle: - time.sleep((self.ideal_cycle-ms) / 1000.0) \ No newline at end of file + time_cycle = self.time_cycle.get() + + if(ms < time_cycle): + time.sleep((time_cycle-ms) / 1000.0) + + self.exit_signal.set() + + # Functions to handle auxillary GUI functions + def reset_gui(self): + self.gui.reset_gui() +if __name__ == "__main__": + gui = ProcessGUI() + gui.start() \ No newline at end of file diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/hal.py b/exercises/static/exercises/drone_cat_mouse/web-template/hal.py index 8174aa3a5..92690646a 100644 --- a/exercises/static/exercises/drone_cat_mouse/web-template/hal.py +++ b/exercises/static/exercises/drone_cat_mouse/web-template/hal.py @@ -1,8 +1,12 @@ import rospy import cv2 +import threading +import time +from datetime import datetime from drone_wrapper import DroneWrapper - +from shared.image import SharedImage +from shared.value import SharedValue # Hardware Abstraction Layer class HAL: @@ -12,70 +16,162 @@ class HAL: def __init__(self): rospy.init_node("HAL_cat") + self.shared_frontal_image = SharedImage("halfrontalimage") + self.shared_ventral_image = SharedImage("halventralimage") + self.shared_x = SharedValue("x") + self.shared_y = SharedValue("y") + self.shared_z = SharedValue("z") + self.shared_takeoff_z = SharedValue("sharedtakeoffz") + self.shared_az = SharedValue("az") + self.shared_azt = SharedValue("azt") + self.shared_vx = SharedValue("vx") + self.shared_vy = SharedValue("vy") + self.shared_vz = SharedValue("vz") + self.shared_landed_state = SharedValue("landedstate") + self.shared_position = SharedValue("position") + self.shared_velocity = SharedValue("velocity") + self.shared_orientation = SharedValue("orientation") + self.shared_roll = SharedValue("roll") + self.shared_pitch = SharedValue("pitch") + self.shared_yaw = SharedValue("yaw") + self.shared_yaw_rate = SharedValue("yawrate") + self.image = None self.cat = DroneWrapper(name="rqt", ns="/iris/") + # Update thread + self.thread = ThreadHAL(self.update_hal) # Explicit initialization functions # Class method, so user can call it without instantiation - @classmethod - def initRobot(cls): - new_instance = cls() - return new_instance + # Function to start the update thread + def start_thread(self): + self.thread.start() # Get Image from ROS Driver Camera def get_frontal_image(self): image = self.cat.get_frontal_image() image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) - return image_rgb + self.shared_frontal_image.add(image_rgb) def get_ventral_image(self): image = self.cat.get_ventral_image() image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) - return image_rgb + self.shared_ventral_image.add(image_rgb) def get_position(self): pos = self.cat.get_position() - return pos + self.shared_position.add(pos,type_name="list") def get_velocity(self): vel = self.cat.get_velocity() - return vel + self.shared_velocity.add(vel ,type_name="list") def get_yaw_rate(self): yaw_rate = self.cat.get_yaw_rate() - return yaw_rate + self.shared_yaw_rate.add(yaw_rate) def get_orientation(self): orientation = self.cat.get_orientation() - return orientation + self.shared_orientation.add(orientation ,type_name="list") def get_roll(self): roll = self.cat.get_roll() - return roll + self.shared_roll.add(roll) def get_pitch(self): pitch = self.cat.get_pitch() - return pitch + self.shared_pitch.add(pitch) def get_yaw(self): yaw = self.cat.get_yaw() - return yaw + self.shared_yaw.add(yaw) def get_landed_state(self): state = self.cat.get_landed_state() - return state + self.shared_landed_state.add(state) + + def set_cmd_pos(self): + x = self.shared_x.get() + y = self.shared_y.get() + z = self.shared_z.get() + az = self.shared_az.get() - def set_cmd_pos(self, x, y, z, az): self.cat.set_cmd_pos(x, y, z, az) - def set_cmd_vel(self, vx, vy, vz, az): + def set_cmd_vel(self): + vx = self.shared_vx.get() + vy = self.shared_vy.get() + vz = self.shared_vz.get() + az = self.shared_azt.get() self.cat.set_cmd_vel(vx, vy, vz, az) - def set_cmd_mix(self, vx, vy, z, az): + def set_cmd_mix(self): + vx = self.shared_vx.get() + vy = self.shared_vy.get() + z = self.shared_z.get() + az = self.shared_azt.get() self.cat.set_cmd_mix(vx, vy, z, az) - def takeoff(self, h=3): + def takeoff(self): + h = self.shared_takeoff_z.get() self.cat.takeoff(h) def land(self): - self.cat.land() \ No newline at end of file + self.cat.land() + + def update_hal(self): + self.get_frontal_image() + self.get_ventral_image() + self.get_position() + self.get_velocity() + self.get_yaw_rate() + self.get_orientation() + self.get_pitch() + self.get_roll() + self.get_yaw() + self.get_landed_state() + self.set_cmd_pos() + self.set_cmd_vel() + self.set_cmd_mix() + + # Destructor function to close all fds + def __del__(self): + self.shared_frontal_image.close() + self.shared_ventral_image.close() + self.shared_x.close() + self.shared_y.close() + self.shared_z.close() + self.shared_takeoff_z.close() + self.shared_az.close() + self.shared_azt.close() + self.shared_vx.close() + self.shared_vy.close() + self.shared_vz.close() + self.shared_landed_state.close() + self.shared_position.close() + self.shared_velocity.close() + self.shared_orientation.close() + self.shared_roll.close() + self.shared_pitch.close() + self.shared_yaw.close() + self.shared_yaw_rate.close() + +class ThreadHAL(threading.Thread): + def __init__(self, update_function): + super(ThreadHAL, self).__init__() + self.time_cycle = 80 + self.update_function = update_function + + def run(self): + while(True): + start_time = datetime.now() + + self.update_function() + + finish_time = datetime.now() + + dt = finish_time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + + if(ms < self.time_cycle): + time.sleep((self.time_cycle - ms) / 1000.0) \ No newline at end of file diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/hal_guest.py b/exercises/static/exercises/drone_cat_mouse/web-template/hal_guest.py index e9d64b353..fcaf26cbe 100644 --- a/exercises/static/exercises/drone_cat_mouse/web-template/hal_guest.py +++ b/exercises/static/exercises/drone_cat_mouse/web-template/hal_guest.py @@ -1,8 +1,12 @@ import rospy import cv2 +import threading +import time +from datetime import datetime from drone_wrapper import DroneWrapper - +from shared.image import SharedImage +from shared.value import SharedValue # Hardware Abstraction Layer class HAL: @@ -12,70 +16,162 @@ class HAL: def __init__(self): rospy.init_node("HAL_mouse") + # Shared memory variables + self.shared_frontal_image = SharedImage("halfrontalimageguest") + self.shared_ventral_image = SharedImage("halventralimageguest") + self.shared_x = SharedValue("xguest") + self.shared_y = SharedValue("yguest") + self.shared_z = SharedValue("zguest") + self.shared_takeoff_z = SharedValue("sharedtakeoffzguest") + self.shared_az = SharedValue("azguest") + self.shared_azt = SharedValue("aztguest") + self.shared_vx = SharedValue("vxguest") + self.shared_vy = SharedValue("vyguest") + self.shared_vz = SharedValue("vzguest") + self.shared_landed_state = SharedValue("landedstateguest") + self.shared_position = SharedValue("positionguest") + self.shared_velocity = SharedValue("velocityguest") + self.shared_orientation = SharedValue("orientationguest") + self.shared_roll = SharedValue("rollguest") + self.shared_pitch = SharedValue("pitchguest") + self.shared_yaw = SharedValue("yawguest") + self.shared_yaw_rate = SharedValue("yawrateguest") + self.image = None self.mouse = DroneWrapper(name="rqt", ns="/iris1/") + + # Update thread + self.thread = ThreadHAL(self.update_hal) - # Explicit initialization functions - # Class method, so user can call it without instantiation - @classmethod - def initRobot(cls): - new_instance = cls() - return new_instance + # Function to start the update thread + def start_thread(self): + self.thread.start() # Get Image from ROS Driver Camera def get_frontal_image(self): image = self.mouse.get_frontal_image() image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) - return image_rgb + self.shared_frontal_image.add(image_rgb) def get_ventral_image(self): image = self.mouse.get_ventral_image() image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) - return image_rgb + self.shared_ventral_image.add(image_rgb) def get_position(self): pos = self.mouse.get_position() - return pos + self.shared_position.add(pos,type_name="list") def get_velocity(self): vel = self.mouse.get_velocity() - return vel + self.shared_velocity.add(vel ,type_name="list") def get_yaw_rate(self): yaw_rate = self.mouse.get_yaw_rate() - return yaw_rate + self.shared_yaw_rate.add(yaw_rate) def get_orientation(self): orientation = self.mouse.get_orientation() - return orientation + self.shared_orientation.add(orientation ,type_name="list") def get_roll(self): roll = self.mouse.get_roll() - return roll + self.shared_roll.add(roll) def get_pitch(self): pitch = self.mouse.get_pitch() - return pitch + self.shared_pitch.add(pitch) def get_yaw(self): yaw = self.mouse.get_yaw() - return yaw + self.shared_yaw.add(yaw) def get_landed_state(self): state = self.mouse.get_landed_state() - return state + self.shared_landed_state.add(state) + + def set_cmd_pos(self): + x = self.shared_x.get() + y = self.shared_y.get() + z = self.shared_z.get() + az = self.shared_az.get() - def set_cmd_pos(self, x, y, z, az): self.mouse.set_cmd_pos(x, y, z, az) - def set_cmd_vel(self, vx, vy, vz, az): + def set_cmd_vel(self): + vx = self.shared_vx.get() + vy = self.shared_vy.get() + vz = self.shared_vz.get() + az = self.shared_azt.get() self.mouse.set_cmd_vel(vx, vy, vz, az) - def set_cmd_mix(self, vx, vy, z, az): + def set_cmd_mix(self): + vx = self.shared_vx.get() + vy = self.shared_vy.get() + z = self.shared_z.get() + az = self.shared_azt.get() self.mouse.set_cmd_mix(vx, vy, z, az) - def takeoff(self, h=3): + def takeoff(self): + h = self.shared_takeoff_z.get() self.mouse.takeoff(h) def land(self): - self.mouse.land() \ No newline at end of file + self.mouse.land() + + def update_hal(self): + self.get_frontal_image() + self.get_ventral_image() + self.get_position() + self.get_velocity() + self.get_yaw_rate() + self.get_orientation() + self.get_pitch() + self.get_roll() + self.get_yaw() + self.get_landed_state() + self.set_cmd_pos() + self.set_cmd_vel() + self.set_cmd_mix() + + # Destructor function to close all fds + def __del__(self): + self.shared_frontal_image.close() + self.shared_ventral_image.close() + self.shared_x.close() + self.shared_y.close() + self.shared_z.close() + self.shared_takeoff_z.close() + self.shared_az.close() + self.shared_azt.close() + self.shared_vx.close() + self.shared_vy.close() + self.shared_vz.close() + self.shared_landed_state.close() + self.shared_position.close() + self.shared_velocity.close() + self.shared_orientation.close() + self.shared_roll.close() + self.shared_pitch.close() + self.shared_yaw.close() + self.shared_yaw_rate.close() + +class ThreadHAL(threading.Thread): + def __init__(self, update_function): + super(ThreadHAL, self).__init__() + self.time_cycle = 80 + self.update_function = update_function + + def run(self): + while(True): + start_time = datetime.now() + + self.update_function() + + finish_time = datetime.now() + + dt = finish_time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + + if(ms < self.time_cycle): + time.sleep((self.time_cycle - ms) / 1000.0) \ No newline at end of file diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/shared/__init__.py b/exercises/static/exercises/drone_cat_mouse/web-template/shared/__init__.py new file mode 100755 index 000000000..e69de29bb diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/shared/image.py b/exercises/static/exercises/drone_cat_mouse/web-template/shared/image.py new file mode 100755 index 000000000..de5c9f9d6 --- /dev/null +++ b/exercises/static/exercises/drone_cat_mouse/web-template/shared/image.py @@ -0,0 +1,109 @@ +import numpy as np +import mmap +from posix_ipc import Semaphore, O_CREX, ExistentialError, O_CREAT, SharedMemory, unlink_shared_memory +from ctypes import sizeof, memmove, addressof, create_string_buffer +from shared.structure_img import MD + +# Probably, using self variables gives errors with memmove +# Therefore, a global variable for utility +md_buf = create_string_buffer(sizeof(MD)) + +class SharedImage: + def __init__(self, name): + # Initialize variables for memory regions and buffers and Semaphore + self.shm_buf = None; self.shm_region = None + self.md_buf = None; self.md_region = None + self.image_lock = None + + self.shm_name = name; self.md_name = name + "-meta" + self.image_lock_name = name + + # Initialize or retreive metadata memory region + try: + self.md_region = SharedMemory(self.md_name) + self.md_buf = mmap.mmap(self.md_region.fd, sizeof(MD)) + self.md_region.close_fd() + except ExistentialError: + self.md_region = SharedMemory(self.md_name, O_CREAT, size=sizeof(MD)) + self.md_buf = mmap.mmap(self.md_region.fd, self.md_region.size) + self.md_region.close_fd() + + # Initialize or retreive Semaphore + try: + self.image_lock = Semaphore(self.image_lock_name, O_CREX) + except ExistentialError: + image_lock = Semaphore(self.image_lock_name, O_CREAT) + image_lock.unlink() + self.image_lock = Semaphore(self.image_lock_name, O_CREX) + + self.image_lock.release() + + # Get the shared image + def get(self): + # Define metadata + metadata = MD() + + # Get metadata from the shared region + self.image_lock.acquire() + md_buf[:] = self.md_buf + memmove(addressof(metadata), md_buf, sizeof(metadata)) + self.image_lock.release() + + # Try to retreive the image from shm_buffer + # Otherwise return a zero image + try: + self.shm_region = SharedMemory(self.shm_name) + self.shm_buf = mmap.mmap(self.shm_region.fd, metadata.size) + self.shm_region.close_fd() + + self.image_lock.acquire() + image = np.ndarray(shape=(metadata.shape_0, metadata.shape_1, metadata.shape_2), + dtype='uint8', buffer=self.shm_buf) + self.image_lock.release() + + # Check for a None image + if(image.size == 0): + image = np.zeros((3, 3, 3), np.uint8) + + except ExistentialError: + image = np.zeros((3, 3, 3), np.uint8) + + return image + + # Add the shared image + def add(self, image): + try: + # Get byte size of the image + byte_size = image.nbytes + + # Get the shared memory buffer to read from + if not self.shm_region: + self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=byte_size) + self.shm_buf = mmap.mmap(self.shm_region.fd, byte_size) + self.shm_region.close_fd() + + # Generate meta data + metadata = MD(image.shape[0], image.shape[1], image.shape[2], byte_size) + + # Send the meta data and image to shared regions + self.image_lock.acquire() + memmove(md_buf, addressof(metadata), sizeof(metadata)) + self.md_buf[:] = md_buf[:] + self.shm_buf[:] = image.tobytes() + self.image_lock.release() + except: + pass + + # Destructor function to unlink and disconnect + def close(self): + self.image_lock.acquire() + self.md_buf.close() + + try: + unlink_shared_memory(self.md_name) + unlink_shared_memory(self.shm_name) + except ExistentialError: + pass + + self.image_lock.release() + self.image_lock.close() diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/mouse.py b/exercises/static/exercises/drone_cat_mouse/web-template/shared/mouse.py similarity index 100% rename from exercises/static/exercises/drone_cat_mouse/web-template/mouse.py rename to exercises/static/exercises/drone_cat_mouse/web-template/shared/mouse.py diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/shared/structure_img.py b/exercises/static/exercises/drone_cat_mouse/web-template/shared/structure_img.py new file mode 100755 index 000000000..ae40b3707 --- /dev/null +++ b/exercises/static/exercises/drone_cat_mouse/web-template/shared/structure_img.py @@ -0,0 +1,9 @@ +from ctypes import Structure, c_int32, c_int64 + +class MD(Structure): + _fields_ = [ + ('shape_0', c_int32), + ('shape_1', c_int32), + ('shape_2', c_int32), + ('size', c_int64) + ] \ No newline at end of file diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/shared/value.py b/exercises/static/exercises/drone_cat_mouse/web-template/shared/value.py new file mode 100755 index 000000000..e7bfad8a2 --- /dev/null +++ b/exercises/static/exercises/drone_cat_mouse/web-template/shared/value.py @@ -0,0 +1,93 @@ +import numpy as np +import mmap +from posix_ipc import Semaphore, O_CREX, ExistentialError, O_CREAT, SharedMemory, unlink_shared_memory +from ctypes import sizeof, memmove, addressof, create_string_buffer, c_float +import struct + +class SharedValue: + def __init__(self, name): + # Initialize varaibles for memory regions and buffers and Semaphore + self.shm_buf = None; self.shm_region = None + self.value_lock = None + + self.shm_name = name; self.value_lock_name = name + + # Initialize or retreive Semaphore + try: + self.value_lock = Semaphore(self.value_lock_name, O_CREX) + except ExistentialError: + value_lock = Semaphore(self.value_lock_name, O_CREAT) + value_lock.unlink() + self.value_lock = Semaphore(self.value_lock_name, O_CREX) + + self.value_lock.release() + + # Get the shared value + def get(self, type_name= "value"): + # Retreive the data from buffer + if type_name=="value": + try: + self.shm_region = SharedMemory(self.shm_name) + self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) + self.shm_region.close_fd() + except ExistentialError: + self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=sizeof(c_float)) + self.shm_buf = mmap.mmap(self.shm_region.fd, self.shm_region.size) + self.shm_region.close_fd() + self.value_lock.acquire() + value = struct.unpack('f', self.shm_buf)[0] + self.value_lock.release() + + return value + elif type_name=="list": + self.shm_region = SharedMemory(self.shm_name) + self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) + self.shm_region.close_fd() + self.value_lock.acquire() + array_val = np.ndarray(shape=(3,), + dtype='float32', buffer=self.shm_buf) + self.value_lock.release() + + return array_val + + else: + print("missing argument for return type") + + + # Add the shared value + def add(self, value, type_name= "value"): + # Send the data to shared regions + if type_name=="value": + try: + self.shm_region = SharedMemory(self.shm_name) + self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) + self.shm_region.close_fd() + except ExistentialError: + self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=sizeof(c_float)) + self.shm_buf = mmap.mmap(self.shm_region.fd, self.shm_region.size) + self.shm_region.close_fd() + + self.value_lock.acquire() + self.shm_buf[:] = struct.pack('f', value) + self.value_lock.release() + elif type_name=="list": + byte_size = value.nbytes + self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=byte_size) + self.shm_buf = mmap.mmap(self.shm_region.fd, byte_size) + self.shm_region.close_fd() + self.value_lock.acquire() + self.shm_buf[:] = value.tobytes() + self.value_lock.release() + + # Destructor function to unlink and disconnect + def close(self): + self.value_lock.acquire() + self.shm_buf.close() + + try: + unlink_shared_memory(self.shm_name) + except ExistentialError: + pass + + self.value_lock.release() + self.value_lock.close() diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/user_functions.py b/exercises/static/exercises/drone_cat_mouse/web-template/user_functions.py new file mode 100755 index 000000000..d741601fc --- /dev/null +++ b/exercises/static/exercises/drone_cat_mouse/web-template/user_functions.py @@ -0,0 +1,117 @@ +from shared.image import SharedImage +from shared.value import SharedValue +import numpy as np +import cv2 + +# Define HAL functions +class HALFunctions: + def __init__(self): + # Initialize image variable + self.shared_frontal_image = SharedImage("halfrontalimage") + self.shared_ventral_image = SharedImage("halventralimage") + self.shared_x = SharedValue("x") + self.shared_y = SharedValue("y") + self.shared_z = SharedValue("z") + self.shared_takeoff_z = SharedValue("sharedtakeoffz") + self.shared_az = SharedValue("az") + self.shared_azt = SharedValue("azt") + self.shared_vx = SharedValue("vx") + self.shared_vy = SharedValue("vy") + self.shared_vz = SharedValue("vz") + self.shared_landed_state = SharedValue("landedstate") + self.shared_position = SharedValue("position") + self.shared_velocity = SharedValue("velocity") + self.shared_orientation = SharedValue("orientation") + self.shared_roll = SharedValue("roll") + self.shared_pitch = SharedValue("pitch") + self.shared_yaw = SharedValue("yaw") + self.shared_yaw_rate = SharedValue("yawrate") + + + # Get image function + def get_frontal_image(self): + image = self.shared_frontal_image.get() + return image + + # Get left image function + def get_ventral_image(self): + image = self.shared_ventral_image.get() + return image + + def takeoff(self, height): + self.shared_takeoff_z.add(height) + + def land(self): + pass + + def set_cmd_pos(self, x, y , z, az): + self.shared_x.add(x) + self.shared_y.add(y) + self.shared_z.add(z) + self.shared_az.add(az) + + def set_cmd_vel(self, vx, vy, vz, az): + self.shared_vx.add(vx) + self.shared_vy.add(vy) + self.shared_vz.add(vz) + self.shared_azt.add(az) + + def set_cmd_mix(self, vx, vy, z, az): + self.shared_vx.add(vx) + self.shared_vy.add(vy) + self.shared_vz.add(z) + self.shared_azt.add(az) + + + def get_position(self): + position = self.shared_position.get(type_name = "list") + return position + + def get_velocity(self): + velocity = self.shared_velocity.get(type_name = "list") + return velocity + + def get_yaw_rate(self): + yaw_rate = self.shared_yaw_rate.get(type_name = "value") + return yaw_rate + + def get_orientation(self): + orientation = self.shared_orientation.get(type_name = "list") + return orientation + + def get_roll(self): + roll = self.shared_roll.get(type_name = "value") + return roll + + def get_pitch(self): + pitch = self.shared_pitch.get(type_name = "value") + return pitch + + def get_yaw(self): + yaw = self.shared_yaw.get(type_name = "value") + return yaw + + def get_landed_state(self): + landed_state = self.shared_landed_state.get(type_name = "value") + return landed_state + +# Define GUI functions +class GUIFunctions: + def __init__(self): + # Initialize image variable + self.shared_image = SharedImage("guifrontalimage") + self.shared_left_image = SharedImage("guiventralimage") + + # Show image function + def showImage(self, image): + # Reshape to 3 channel if it has only 1 in order to display it + if (len(image.shape) < 3): + image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB) + self.shared_image.add(image) + + # Show left image function + def showLeftImage(self, image): + # Reshape to 3 channel if it has only 1 in order to display it + if (len(image.shape) < 3): + image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB) + self.shared_left_image.add(image) \ No newline at end of file diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/user_functions_guest.py b/exercises/static/exercises/drone_cat_mouse/web-template/user_functions_guest.py new file mode 100644 index 000000000..dfe9c9873 --- /dev/null +++ b/exercises/static/exercises/drone_cat_mouse/web-template/user_functions_guest.py @@ -0,0 +1,116 @@ +from shared.image import SharedImage +from shared.value import SharedValue +import numpy as np +import cv2 + +# Define Guest HAL functions +class HALFunctions: + def __init__(self): + # Initialize image variable + self.shared_frontal_image = SharedImage("halfrontalimageguest") + self.shared_ventral_image = SharedImage("halventralimageguest") + self.shared_x = SharedValue("xguest") + self.shared_y = SharedValue("yguest") + self.shared_z = SharedValue("zguest") + self.shared_takeoff_z = SharedValue("sharedtakeoffzguest") + self.shared_az = SharedValue("azguest") + self.shared_azt = SharedValue("aztguest") + self.shared_vx = SharedValue("vxguest") + self.shared_vy = SharedValue("vyguest") + self.shared_vz = SharedValue("vzguest") + self.shared_landed_state = SharedValue("landedstateguest") + self.shared_position = SharedValue("positionguest") + self.shared_velocity = SharedValue("velocityguest") + self.shared_orientation = SharedValue("orientationguest") + self.shared_roll = SharedValue("rollguest") + self.shared_pitch = SharedValue("pitchguest") + self.shared_yaw = SharedValue("yawguest") + self.shared_yaw_rate = SharedValue("yawrateguest") + + # Get image function + def get_frontal_image(self): + image = self.shared_frontal_image.get() + return image + + # Get left image function + def get_ventral_image(self): + image = self.shared_ventral_image.get() + return image + + def takeoff(self, height): + self.shared_takeoff_z.add(height) + + def land(self): + pass + + def set_cmd_pos(self, x, y , z, az): + self.shared_x.add(x) + self.shared_y.add(y) + self.shared_z.add(z) + self.shared_az.add(az) + + def set_cmd_vel(self, vx, vy, vz, az): + self.shared_vx.add(vx) + self.shared_vy.add(vy) + self.shared_vz.add(vz) + self.shared_azt.add(az) + + def set_cmd_mix(self, vx, vy, z, az): + self.shared_vx.add(vx) + self.shared_vy.add(vy) + self.shared_vz.add(z) + self.shared_azt.add(az) + + + def get_position(self): + position = self.shared_position.get(type_name = "list") + return position + + def get_velocity(self): + velocity = self.shared_velocity.get(type_name = "list") + return velocity + + def get_yaw_rate(self): + yaw_rate = self.shared_yaw_rate.get(type_name = "value") + return yaw_rate + + def get_orientation(self): + orientation = self.shared_orientation.get(type_name = "list") + return orientation + + def get_roll(self): + roll = self.shared_roll.get(type_name = "value") + return roll + + def get_pitch(self): + pitch = self.shared_pitch.get(type_name = "value") + return pitch + + def get_yaw(self): + yaw = self.shared_yaw.get(type_name = "value") + return yaw + + def get_landed_state(self): + landed_state = self.shared_landed_state.get(type_name = "value") + return landed_state + +# Define GUI functions +class GUIFunctions: + def __init__(self): + # Initialize image variable + self.shared_image = SharedImage("guifrontalimageguest") + self.shared_left_image = SharedImage("guiventralimageguest") + + # Show image function + def showImage(self, image): + # Reshape to 3 channel if it has only 1 in order to display it + if (len(image.shape) < 3): + image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB) + self.shared_image.add(image) + + # Show left image function + def showLeftImage(self, image): + # Reshape to 3 channel if it has only 1 in order to display it + if (len(image.shape) < 3): + image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB) + self.shared_left_image.add(image) \ No newline at end of file diff --git a/exercises/static/exercises/follow_turtlebot/web-template/exercise.py b/exercises/static/exercises/follow_turtlebot/web-template/exercise.py index 39b1a527c..0b48f60f1 100755 --- a/exercises/static/exercises/follow_turtlebot/web-template/exercise.py +++ b/exercises/static/exercises/follow_turtlebot/web-template/exercise.py @@ -52,12 +52,9 @@ def __init__(self): self.server = None self.client = None self.host = sys.argv[1] - print("before hal") # Initialize the GUI, HAL and Console behind the scenes self.hal = HAL() - print("after hal") self.turtlebot = Turtlebot() - print("After tbbbbbbbbbbbbb") # self.gui = GUI(self.host, self.turtlebot) self.paused = False @@ -85,7 +82,6 @@ def parse_code(self, source_code): return iterative_code, sequential_code - # Function for saving def save_code(self, source_code): with open('code/academy.py', 'w') as code_file: @@ -238,15 +234,12 @@ def handle(self, client, server, message): # Function that gets called when the server is connected def connected(self, client, server): self.client = client - print("before hal threaddddddddd") # Start the HAL update thread self.hal.start_thread() - print("after hal threadDDDDDDDD") # Start real time factor tracker thread self.stats_thread = threading.Thread(target=self.track_stats) self.stats_thread.start() - print("after stats thereadddddddd") # Initialize the ping message self.send_frequency_message() @@ -259,7 +252,6 @@ def handle_close(self, client, server): print(client, 'closed') def run_server(self): - print("cycleeeeeeee", self.brain_ideal_cycle.get()) self.server = WebsocketServer(port=1905, host=self.host) self.server.set_fn_new_client(self.connected) self.server.set_fn_client_left(self.handle_close) @@ -280,7 +272,5 @@ def run_server(self): # Execute! if __name__ == "__main__": - print("in aminnnnnnnnnnnnnnnn") server = Template() - print("afrer remp") - server.run_server() + server.run_server() \ No newline at end of file diff --git a/exercises/static/exercises/follow_turtlebot/web-template/gui.py b/exercises/static/exercises/follow_turtlebot/web-template/gui.py index b576f2cca..74be0a153 100755 --- a/exercises/static/exercises/follow_turtlebot/web-template/gui.py +++ b/exercises/static/exercises/follow_turtlebot/web-template/gui.py @@ -39,19 +39,6 @@ def __init__(self, host, turtlebot): # Event objects for multiprocessing self.ack_event = multiprocessing.Event() self.cli_event = multiprocessing.Event() - - # Image variables - # self.image_to_be_shown = None - # self.image_to_be_shown_updated = False - # self.image_show_lock = threading.Lock() - - # self.left_image_to_be_shown = None - # self.left_image_to_be_shown_updated = False - # self.left_image_show_lock = threading.Lock() - - # self.acknowledge = False - # self.acknowledge_lock = threading.Lock() - # Take the console object to set the same websocket and client self.turtlebot = turtlebot @@ -59,14 +46,6 @@ def __init__(self, host, turtlebot): t = threading.Thread(target=self.run_server) t.start() - - # Explicit initialization function - # Class method, so user can call it without instantiation - # @classmethod - # def initGUI(cls, host): - # # self.payload = {'image': '', 'shape': []} - # new_instance = cls(host) - # return new_instance # Function to prepare image payload # Encodes the image as a JSON string and sends through the WS @@ -83,7 +62,6 @@ def payloadImage(self): return payload - # Function to prepare image payload # Encodes the image as a JSON string and sends through the WS def payloadLeftImage(self): @@ -99,7 +77,6 @@ def payloadLeftImage(self): return payload - # Function for student to call def showImage(self, image): self.image_show_lock.acquire() @@ -122,19 +99,6 @@ def get_client(self, client, server): print(client, 'connected') - # # Function to get value of Acknowledge - # def get_acknowledge(self): - # self.acknowledge_lock.acquire() - # acknowledge = self.acknowledge - # self.acknowledge_lock.release() - - # return acknowledge - - # # Function to get value of Acknowledge - # def set_acknowledge(self, value): - # self.acknowledge_lock.acquire() - # self.acknowledge = value - # self.acknowledge_lock.release() # Update the gui def update_gui(self): @@ -145,7 +109,6 @@ def update_gui(self): message = "#gui" + json.dumps(self.payload) self.server.send_message(self.client, message) - # Payload Left Image Message left_payload = self.payloadLeftImage() self.left_payload["image"] = json.dumps(left_payload) @@ -158,7 +121,6 @@ def update_gui(self): def get_message(self, client, server, message): # Acknowledge Message for GUI Thread - if(message[:4] == "#ack"): # Set acknowledgement flag self.ack_event.set() @@ -169,9 +131,6 @@ def get_message(self, client, server, message): # Reset message elif message[:4] == "#rst": self.turtlebot.reset_turtlebot() - - - # Function that gets called when the connected closes def handle_close(self, client, server): print(client, 'closed') @@ -187,7 +146,6 @@ def run_server(self): logged = False while not logged: - print("IN WHILEEEEEEEEEEEEEEEEEEEEEEE") try: f = open("/ws_gui.log", "w") f.write("websocket_gui=ready") @@ -231,7 +189,6 @@ def run(self): # Wait for client before starting self.cli_event.wait() - self.measure_thread = threading.Thread(target=self.measure_thread) self.thread = threading.Thread(target=self.run_gui) @@ -242,16 +199,6 @@ def run(self): self.exit_signal.wait() - # Function to start the execution of threads - # def start(self): - # self.measure_thread = threading.Thread(target=self.measure_thread) - # self.thread = threading.Thread(target=self.run) - - # self.measure_thread.start() - # self.thread.start() - - # print("GUI Thread Started!") - # The measuring thread to measure frequency def measure_thread(self): previous_time = datetime.now() @@ -275,7 +222,6 @@ def measure_thread(self): # Reset the counter self.iteration_counter = 0 - # The main thread of execution # The main thread of execution def run_gui(self): while(True): @@ -305,5 +251,4 @@ def reset_gui(self): if __name__ == "__main__": gui = ProcessGUI() - gui.start() - + gui.start() \ No newline at end of file diff --git a/exercises/static/exercises/follow_turtlebot/web-template/hal.py b/exercises/static/exercises/follow_turtlebot/web-template/hal.py index 4bade1683..17c39cd6b 100755 --- a/exercises/static/exercises/follow_turtlebot/web-template/hal.py +++ b/exercises/static/exercises/follow_turtlebot/web-template/hal.py @@ -1,5 +1,3 @@ - - import rospy import cv2 import threading @@ -46,10 +44,7 @@ def __init__(self): # Explicit initialization functions # Class method, so user can call it without instantiation - # @classmethod - # def initRobot(cls): - # new_instance = cls() - # return new_instance + # Function to start the update thread def start_thread(self): @@ -182,5 +177,4 @@ def run(self): ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 if(ms < self.time_cycle): - time.sleep((self.time_cycle - ms) / 1000.0) - + time.sleep((self.time_cycle - ms) / 1000.0) \ No newline at end of file From 1e3c9ab4a12b1fd0c4e66f01e876ab263e980765 Mon Sep 17 00:00:00 2001 From: RUFFY-369 Date: Mon, 5 Sep 2022 20:36:30 +0530 Subject: [PATCH 09/42] instructions.json upd for drone_cat_mouse multiprocessing --- scripts/instructions.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/instructions.json b/scripts/instructions.json index f512c7c4b..a9ddd8170 100644 --- a/scripts/instructions.json +++ b/scripts/instructions.json @@ -27,7 +27,9 @@ "gazebo_path": "/RoboticsAcademy/exercises/drone_cat_mouse/web-template/launch", "instructions_ros": ["/opt/ros/noetic/bin/roslaunch ./RoboticsAcademy/exercises/drone_cat_mouse/web-template/launch/drone_cat_mouse.launch"], "instructions_host": "python3 /RoboticsAcademy/exercises/drone_cat_mouse/web-template/exercise.py 0.0.0.0", - "instructions_guest": "python3 /RoboticsAcademy/exercises/drone_cat_mouse/web-template/exercise_guest.py 0.0.0.0" + "instructions_gui": "python3 /RoboticsAcademy/exercises/drone_cat_mouse/web-template/gui.py 0.0.0.0 {}", + "instructions_guest": "python3 /RoboticsAcademy/exercises/drone_cat_mouse/web-template/exercise_guest.py 0.0.0.0", + "instructions_gui_guest": "python3 /RoboticsAcademy/exercises/drone_cat_mouse/web-template/gui_guest.py 0.0.0.0 {}" }, "3d_reconstruction": { "gazebo_path": "/RoboticsAcademy/exercises/3d_reconstruction/web-template/launch", From 8bf0172f8ae85ee3de2618de1590d091d2fb022e Mon Sep 17 00:00:00 2001 From: RUFFY-369 Date: Tue, 6 Sep 2022 00:51:52 +0530 Subject: [PATCH 10/42] sample time tag type chnage for drone_cat_mouse --- .../web-template/launch/drone_cat_mouse.launch | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/launch/drone_cat_mouse.launch b/exercises/static/exercises/drone_cat_mouse/web-template/launch/drone_cat_mouse.launch index 32582e4c3..84d10dc11 100644 --- a/exercises/static/exercises/drone_cat_mouse/web-template/launch/drone_cat_mouse.launch +++ b/exercises/static/exercises/drone_cat_mouse/web-template/launch/drone_cat_mouse.launch @@ -56,7 +56,7 @@ - + @@ -86,7 +86,7 @@ - + From 60c11f072893f7f017fad05494521817c436fb5f Mon Sep 17 00:00:00 2001 From: RUFFY-369 Date: Tue, 6 Sep 2022 14:33:22 +0530 Subject: [PATCH 11/42] drone_Cat_mouse ex mouse.py node bug fix --- .../static/exercises/drone_cat_mouse/web-template/gui.py | 7 +++---- .../exercises/drone_cat_mouse/web-template/gui_guest.py | 7 +++---- .../exercises/drone_cat_mouse/web-template/shared/mouse.py | 2 +- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/gui.py b/exercises/static/exercises/drone_cat_mouse/web-template/gui.py index 15d62dc63..b82eeaa93 100644 --- a/exercises/static/exercises/drone_cat_mouse/web-template/gui.py +++ b/exercises/static/exercises/drone_cat_mouse/web-template/gui.py @@ -21,7 +21,7 @@ class GUI: # Initialization function # The actual initialization - def __init__(self, host, mouse): + def __init__(self, host): rospy.init_node("GUI") self.payload = {'image': ''} @@ -39,7 +39,7 @@ def __init__(self, host, mouse): self.ack_event = multiprocessing.Event() self.cli_event = multiprocessing.Event() - self.mouse = mouse + self.mouse = Mouse() # Start server thread t = threading.Thread(target=self.run_server) t.start() @@ -180,7 +180,6 @@ def __init__(self): super(ProcessGUI, self).__init__() self.host = sys.argv[1] - self.mouse = Mouse() # Time variables self.time_cycle = SharedValue("gui_time_cycle") self.ideal_cycle = SharedValue("gui_ideal_cycle") @@ -195,7 +194,7 @@ def initialize_events(self): # Function to start the execution of threads def run(self): # Initialize GUI - self.gui = GUI(self.host, self.mouse) + self.gui = GUI(self.host) self.initialize_events() # Wait for client before starting diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/gui_guest.py b/exercises/static/exercises/drone_cat_mouse/web-template/gui_guest.py index cb38e5f51..170d22a1f 100644 --- a/exercises/static/exercises/drone_cat_mouse/web-template/gui_guest.py +++ b/exercises/static/exercises/drone_cat_mouse/web-template/gui_guest.py @@ -20,7 +20,7 @@ class GUI: # Initialization function # The actual initialization - def __init__(self, host, mouse): + def __init__(self, host): rospy.init_node("GUI_guest") self.payload = {'image_guest': ''} @@ -38,7 +38,7 @@ def __init__(self, host, mouse): self.ack_event = multiprocessing.Event() self.cli_event = multiprocessing.Event() - self.mouse = mouse + self.mouse = Mouse() # Start server thread t = threading.Thread(target=self.run_server) t.start() @@ -146,7 +146,6 @@ def __init__(self): super(ProcessGUI, self).__init__() self.host = sys.argv[1] - self.mouse = Mouse() # Time variables self.time_cycle = SharedValue("gui_time_cycle") self.ideal_cycle = SharedValue("gui_ideal_cycle") @@ -162,7 +161,7 @@ def initialize_events(self): # Function to start the execution of threads def run(self): # Initialize GUI - self.gui = GUI(self.host, self.mouse) + self.gui = GUI(self.host) self.initialize_events() # Wait for client before starting diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/shared/mouse.py b/exercises/static/exercises/drone_cat_mouse/web-template/shared/mouse.py index 6c6046801..14b99db67 100644 --- a/exercises/static/exercises/drone_cat_mouse/web-template/shared/mouse.py +++ b/exercises/static/exercises/drone_cat_mouse/web-template/shared/mouse.py @@ -10,7 +10,7 @@ class Mouse: def __init__(self): - self.mouse = DroneWrapper(name="rqt", ns="mouse/") + self.mouse = DroneWrapper(name="rqt", ns="/iris1/") self.reset_state = rospy.ServiceProxy('/gazebo/set_model_state', SetModelState) # Explicit initialization functions From 083acbf9d9462d47b583d1dd57a3d7b34356c16c Mon Sep 17 00:00:00 2001 From: RUFFY-369 Date: Sat, 10 Sep 2022 16:19:37 +0530 Subject: [PATCH 12/42] add mp to rescue people final and frontend task for random spawn of faces done --- .../rescue_people/web-template/brain.py | 166 +++++++++++ .../rescue_people/web-template/exercise.py | 266 ++++++------------ .../rescue_people/web-template/gui.py | 182 ++++++------ .../rescue_people/web-template/hal.py | 142 ++++++++-- .../web-template/launch/gazebo.launch | 24 -- .../web-template/launch/launch.py | 131 --------- .../web-template/launch/mavros.launch | 13 - .../web-template/launch/px4.launch | 20 -- .../web-template/launch/rescue_people.launch | 82 ++++++ .../web-template/launch/rescue_people.sh | 23 ++ .../web-template/rescue_people.world | 81 +++--- .../web-template/shared/__init__.py | 0 .../web-template/shared/image.py | 109 +++++++ .../web-template/shared/structure_img.py | 9 + .../web-template/shared/value.py | 93 ++++++ .../web-template/user_functions.py | 117 ++++++++ scripts/instructions.json | 5 +- 17 files changed, 935 insertions(+), 528 deletions(-) create mode 100755 exercises/static/exercises/rescue_people/web-template/brain.py mode change 100644 => 100755 exercises/static/exercises/rescue_people/web-template/exercise.py mode change 100644 => 100755 exercises/static/exercises/rescue_people/web-template/gui.py mode change 100644 => 100755 exercises/static/exercises/rescue_people/web-template/hal.py delete mode 100644 exercises/static/exercises/rescue_people/web-template/launch/gazebo.launch delete mode 100644 exercises/static/exercises/rescue_people/web-template/launch/launch.py delete mode 100644 exercises/static/exercises/rescue_people/web-template/launch/mavros.launch delete mode 100644 exercises/static/exercises/rescue_people/web-template/launch/px4.launch create mode 100755 exercises/static/exercises/rescue_people/web-template/launch/rescue_people.launch create mode 100755 exercises/static/exercises/rescue_people/web-template/launch/rescue_people.sh create mode 100755 exercises/static/exercises/rescue_people/web-template/shared/__init__.py create mode 100755 exercises/static/exercises/rescue_people/web-template/shared/image.py create mode 100755 exercises/static/exercises/rescue_people/web-template/shared/structure_img.py create mode 100755 exercises/static/exercises/rescue_people/web-template/shared/value.py create mode 100755 exercises/static/exercises/rescue_people/web-template/user_functions.py diff --git a/exercises/static/exercises/rescue_people/web-template/brain.py b/exercises/static/exercises/rescue_people/web-template/brain.py new file mode 100755 index 000000000..2330c04ba --- /dev/null +++ b/exercises/static/exercises/rescue_people/web-template/brain.py @@ -0,0 +1,166 @@ +import time +import threading +import multiprocessing +import sys +from datetime import datetime +import re +import json +import importlib + +import rospy +from std_srvs.srv import Empty +import cv2 + +from user_functions import GUIFunctions, HALFunctions +from console import start_console, close_console + +from shared.value import SharedValue + +# The brain process class +class BrainProcess(multiprocessing.Process): + def __init__(self, code, exit_signal): + super(BrainProcess, self).__init__() + + # Initialize exit signal + self.exit_signal = exit_signal + + # Function definitions for users to use + self.hal = HALFunctions() + self.gui = GUIFunctions() + + # Time variables + self.time_cycle = SharedValue('brain_time_cycle') + self.ideal_cycle = SharedValue('brain_ideal_cycle') + self.iteration_counter = 0 + + # Get the sequential and iterative code + # Something wrong over here! The code is reversing + # Found a solution but could not find the reason for this (parse_code function's return line of exercise.py is the reason) + self.sequential_code = code[1] + self.iterative_code = code[0] + + # Function to run to start the process + def run(self): + # Two threads for running and measuring + self.measure_thread = threading.Thread(target=self.measure_frequency) + self.thread = threading.Thread(target=self.process_code) + + self.measure_thread.start() + self.thread.start() + + print("Brain Process Started!") + + self.exit_signal.wait() + + # The process function + def process_code(self): + # Redirect information to console + start_console() + + # Reference Environment for the exec() function + iterative_code, sequential_code = self.iterative_code, self.sequential_code + + # The Python exec function + # Run the sequential part + gui_module, hal_module = self.generate_modules() + if sequential_code != "": + reference_environment = {"GUI": gui_module, "HAL": hal_module} + exec(sequential_code, reference_environment) + + # Run the iterative part inside template + # and keep the check for flag + while not self.exit_signal.is_set(): + start_time = datetime.now() + + # Execute the iterative portion + if iterative_code != "": + exec(iterative_code, reference_environment) + + # Template specifics to run! + finish_time = datetime.now() + dt = finish_time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + + # Keep updating the iteration counter + if(iterative_code == ""): + self.iteration_counter = 0 + else: + self.iteration_counter = self.iteration_counter + 1 + + # The code should be run for atleast the target time step + # If it's less put to sleep + # If it's more no problem as such, but we can change it! + time_cycle = self.time_cycle.get() + + if(ms < time_cycle): + time.sleep((time_cycle - ms) / 1000.0) + + close_console() + print("Current Thread Joined!") + + + # Function to generate the modules for use in ACE Editor + def generate_modules(self): + # Define HAL module + hal_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) + hal_module.HAL = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) + hal_module.HAL.motors = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("motors", None)) + + # Add HAL functions + hal_module.HAL.get_frontal_image = self.hal.get_frontal_image + hal_module.HAL.get_ventral_image = self.hal.get_ventral_image + hal_module.HAL.takeoff = self.hal.takeoff + hal_module.HAL.land = self.hal.land + hal_module.HAL.set_cmd_pos = self.hal.set_cmd_pos + hal_module.HAL.set_cmd_vel = self.hal.set_cmd_vel + hal_module.HAL.set_cmd_mix = self.hal.set_cmd_mix + hal_module.HAL.get_position = self.hal.get_position + hal_module.HAL.get_velocity = self.hal.get_velocity + hal_module.HAL.get_orientation = self.hal.get_orientation + hal_module.HAL.get_roll = self.hal.get_roll + hal_module.HAL.get_pitch = self.hal.get_pitch + hal_module.HAL.get_yaw = self.hal.get_yaw + hal_module.HAL.get_landed_state = self.hal.get_landed_state + hal_module.HAL.get_yaw_rate = self.hal.get_yaw_rate + + + + # Define GUI module + gui_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) + gui_module.GUI = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) + + # Add GUI functions + gui_module.GUI.showImage = self.gui.showImage + gui_module.GUI.showLeftImage = self.gui.showLeftImage + + # Adding modules to system + # Protip: The names should be different from + # other modules, otherwise some errors + sys.modules["HAL"] = hal_module + sys.modules["GUI"] = gui_module + + return gui_module, hal_module + + # Function to measure the frequency of iterations + def measure_frequency(self): + previous_time = datetime.now() + # An infinite loop + while not self.exit_signal.is_set(): + # Sleep for 2 seconds + time.sleep(2) + + # Measure the current time and subtract from the previous time to get real time interval + current_time = datetime.now() + dt = current_time - previous_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + previous_time = current_time + + # Get the time period + try: + # Division by zero + self.ideal_cycle.add(ms / self.iteration_counter) + except: + self.ideal_cycle.add(0) + + # Reset the counter + self.iteration_counter = 0 \ No newline at end of file diff --git a/exercises/static/exercises/rescue_people/web-template/exercise.py b/exercises/static/exercises/rescue_people/web-template/exercise.py old mode 100644 new mode 100755 index 6e97c15d8..82bf0c72a --- a/exercises/static/exercises/rescue_people/web-template/exercise.py +++ b/exercises/static/exercises/rescue_people/web-template/exercise.py @@ -1,3 +1,5 @@ + + #!/usr/bin/env python from __future__ import print_function @@ -5,6 +7,7 @@ from websocket_server import WebsocketServer import time import threading +import multiprocessing import subprocess import sys from datetime import datetime @@ -14,8 +17,13 @@ import rospy from std_srvs.srv import Empty +import cv2 + +from shared.value import SharedValue +from brain import BrainProcess +import queue + -from gui import GUI, ThreadGUI from hal import HAL from console import start_console, close_console @@ -25,36 +33,65 @@ class Template: # self.ideal_cycle to run an execution for at least 1 second # self.process for the current running process def __init__(self): - self.measure_thread = None - self.thread = None - self.reload = False - self.stop_brain = True - self.user_code = "" + + self.brain_process = None + self.reload = multiprocessing.Event() # Time variables - self.ideal_cycle = 80 - self.measured_cycle = 80 - self.iteration_counter = 0 + self.brain_time_cycle = SharedValue('brain_time_cycle') + self.brain_ideal_cycle = SharedValue('brain_ideal_cycle') self.real_time_factor = 0 + self.frequency_message = {'brain': '', 'gui': '', 'rtf': ''} + # GUI variables + self.gui_time_cycle = SharedValue('gui_time_cycle') + self.gui_ideal_cycle = SharedValue('gui_ideal_cycle') + self.server = None self.client = None self.host = sys.argv[1] - # Initialize the GUI, HAL and Console behind the scenes self.hal = HAL() - self.gui = GUI(self.host) + self.paused = False + # Function to parse the code # A few assumptions: # 1. The user always passes sequential and iterative codes # 2. Only a single infinite loop def parse_code(self, source_code): - sequential_code, iterative_code = self.seperate_seq_iter(source_code) - return iterative_code, sequential_code + # Check for save/load + if(source_code[:5] == "#save"): + source_code = source_code[5:] + self.save_code(source_code) + + return "", "" + + elif(source_code[:5] == "#load"): + source_code = source_code + self.load_code() + self.server.send_message(self.client, source_code) + + return "", "" + + else: + sequential_code, iterative_code = self.seperate_seq_iter(source_code[6:]) + return iterative_code, sequential_code + - # Function to separate the iterative and sequential code + # Function for saving + def save_code(self, source_code): + with open('code/academy.py', 'w') as code_file: + code_file.write(source_code) + + # Function for loading + def load_code(self): + with open('code/academy.py', 'r') as code_file: + source_code = code_file.read() + + return source_code + + # Function to seperate the iterative and sequential code def seperate_seq_iter(self, source_code): if source_code == "": return "", "" @@ -62,8 +99,8 @@ def seperate_seq_iter(self, source_code): # Search for an instance of while True infinite_loop = re.search(r'[^ ]while\s*\(\s*True\s*\)\s*:|[^ ]while\s*True\s*:|[^ ]while\s*1\s*:|[^ ]while\s*\(\s*1\s*\)\s*:', source_code) - # Separate the content inside while True and the other - # (Separating the sequential and iterative part!) + # Seperate the content inside while True and the other + # (Seperating the sequential and iterative part!) try: start_index = infinite_loop.start() iterative_code = source_code[start_index:] @@ -85,137 +122,16 @@ def seperate_seq_iter(self, source_code): return sequential_code, iterative_code - # The process function - def process_code(self, source_code): - # Redirect the information to console - start_console() - - iterative_code, sequential_code = self.parse_code(source_code) - - # print(sequential_code) - # print(iterative_code) - - # The Python exec function - # Run the sequential part - gui_module, hal_module = self.generate_modules() - reference_environment = {"GUI": gui_module, "HAL": hal_module} - while (self.stop_brain == True): - if (self.reload == True): - return - time.sleep(0.1) - exec(sequential_code, reference_environment) - - # Run the iterative part inside template - # and keep the check for flag - while self.reload == False: - while (self.stop_brain == True): - if (self.reload == True): - return - time.sleep(0.1) - - start_time = datetime.now() - - # Execute the iterative portion - exec(iterative_code, reference_environment) - - # Template specifics to run! - finish_time = datetime.now() - dt = finish_time - start_time - ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 - - # Keep updating the iteration counter - if (iterative_code == ""): - self.iteration_counter = 0 - else: - self.iteration_counter = self.iteration_counter + 1 - - # The code should be run for atleast the target time step - # If it's less put to sleep - if (ms < self.ideal_cycle): - time.sleep((self.ideal_cycle - ms) / 1000.0) - - close_console() - print("Current Thread Joined!") - - # Function to generate the modules for use in ACE Editor - def generate_modules(self): - # Define HAL module - hal_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) - hal_module.HAL = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) - # hal_module.drone = imp.new_module("drone") - # motors# hal_module.HAL.motors = imp.new_module("motors") - - # Add HAL functions - hal_module.HAL.get_frontal_image = self.hal.get_frontal_image - hal_module.HAL.get_ventral_image = self.hal.get_ventral_image - hal_module.HAL.get_position = self.hal.get_position - hal_module.HAL.get_velocity = self.hal.get_velocity - hal_module.HAL.get_yaw_rate = self.hal.get_yaw_rate - hal_module.HAL.get_orientation = self.hal.get_orientation - hal_module.HAL.get_roll = self.hal.get_roll - hal_module.HAL.get_pitch = self.hal.get_pitch - hal_module.HAL.get_yaw = self.hal.get_yaw - hal_module.HAL.get_landed_state = self.hal.get_landed_state - hal_module.HAL.set_cmd_pos = self.hal.set_cmd_pos - hal_module.HAL.set_cmd_vel = self.hal.set_cmd_vel - hal_module.HAL.set_cmd_mix = self.hal.set_cmd_mix - hal_module.HAL.takeoff = self.hal.takeoff - hal_module.HAL.land = self.hal.land - - # Define GUI module - gui_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) - gui_module.GUI = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) - - # Add GUI functions - gui_module.GUI.showImage = self.gui.showImage - gui_module.GUI.showLeftImage = self.gui.showLeftImage - - # Adding modules to system - # Protip: The names should be different from - # other modules, otherwise some errors - sys.modules["HAL"] = hal_module - sys.modules["GUI"] = gui_module - - return gui_module, hal_module - - # Function to measure the frequency of iterations - def measure_frequency(self): - previous_time = datetime.now() - # An infinite loop - while True: - # Sleep for 2 seconds - time.sleep(2) - - # Measure the current time and subtract from the previous time to get real time interval - current_time = datetime.now() - dt = current_time - previous_time - ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 - previous_time = current_time - - # Get the time period - try: - # Division by zero - self.measured_cycle = ms / self.iteration_counter - except: - self.measured_cycle = 0 - - # Reset the counter - self.iteration_counter = 0 - - # Send to client - self.send_frequency_message() - - # Function to generate and send frequency messages def send_frequency_message(self): # This function generates and sends frequency measures of the brain and gui - brain_frequency = 0; gui_frequency = 0 + brain_frequency = 0;gui_frequency = 0 try: - brain_frequency = round(1000 / self.measured_cycle, 1) + brain_frequency = round(1000 / self.brain_ideal_cycle.get(), 1) except ZeroDivisionError: brain_frequency = 0 try: - gui_frequency = round(1000 / self.thread_gui.measured_cycle, 1) + gui_frequency = round(1000 / self.gui_ideal_cycle.get(), 1) except ZeroDivisionError: gui_frequency = 0 @@ -240,29 +156,32 @@ def track_stats(self): args = ["gz", "stats", "-p"] # Prints gz statistics. "-p": Output comma-separated values containing- # real-time factor (percent), simtime (sec), realtime (sec), paused (T or F) - stats_process = subprocess.Popen(args, stdout=subprocess.PIPE, bufsize=1, universal_newlines=True) + stats_process = subprocess.Popen(args, stdout=subprocess.PIPE, bufsize=0) # bufsize=1 enables line-bufferred mode (the input buffer is flushed # automatically on newlines if you would write to process.stdin ) with stats_process.stdout: - for line in iter(stats_process.stdout.readline, ''): - stats_list = [x.strip() for x in line.split(',')] - self.real_time_factor = stats_list[0] + for line in iter(stats_process.stdout.readline, b''): + stats_list = [x.strip() for x in line.split(b',')] + self.real_time_factor = stats_list[0].decode("utf-8") # Function to maintain thread execution def execute_thread(self, source_code): # Keep checking until the thread is alive # The thread will die when the coming iteration reads the flag - if self.thread is not None: - while self.thread.is_alive(): - time.sleep(0.2) + if(self.brain_process != None): + while self.brain_process.is_alive(): + pass # Turn the flag down, the iteration has successfully stopped! - self.reload = False + self.reload.clear() # New thread execution - self.thread = threading.Thread(target=self.process_code, args=[source_code]) - self.thread.start() + code = self.parse_code(source_code) + if code[0] == "" and code[1] == "": + return + + self.brain_process = BrainProcess(code, self.reload) + self.brain_process.start() self.send_code_message() - print("New Thread Started!") # Function to read and set frequency from incoming message def read_frequency_message(self, message): @@ -270,65 +189,58 @@ def read_frequency_message(self, message): # Set brain frequency frequency = float(frequency_message["brain"]) - self.ideal_cycle = 1000.0 / frequency + self.brain_time_cycle.add(1000.0 / frequency) # Set gui frequency frequency = float(frequency_message["gui"]) - self.thread_gui.ideal_cycle = 1000.0 / frequency + self.gui_time_cycle.add(1000.0 / frequency) return # The websocket function # Gets called when there is an incoming message from the client def handle(self, client, server, message): - if message[:5] == "#freq": + if(message[:5] == "#freq"): frequency_message = message[5:] self.read_frequency_message(frequency_message) time.sleep(1) + self.send_frequency_message() return - elif(message[:5] == "#ping"): time.sleep(1) self.send_ping_message() return - elif (message[:5] == "#code"): + elif (message[:5] == "#code"): try: # Once received turn the reload flag up and send it to execute_thread function - self.user_code = message[6:] + code = message # print(repr(code)) - self.reload = True - self.execute_thread(self.user_code) + self.reload.set() + self.execute_thread(code) except: pass - - elif (message[:5] == "#rest"): + + elif (message[:5] == "#stop"): try: - self.reload = True - self.stop_brain = True - self.execute_thread(self.user_code) + self.reload.set() + self.execute_thread(code) except: pass - - elif (message[:5] == "#stop"): - self.stop_brain = True - - elif (message[:5] == "#play"): - self.stop_brain = False + self.server.send_message(self.client, "#stpd") # Function that gets called when the server is connected def connected(self, client, server): self.client = client - # Start the GUI update thread - self.thread_gui = ThreadGUI(self.gui) - self.thread_gui.start() + # Start the HAL update thread + self.hal.start_thread() - # Start the real time factor tracker thread + # Start real time factor tracker thread self.stats_thread = threading.Thread(target=self.track_stats) self.stats_thread.start() - # Start measure frequency - self.measure_thread = threading.Thread(target=self.measure_frequency) - self.measure_thread.start() + # Initialize the ping message + self.send_frequency_message() + print("After sneding freweq msg") print(client, 'connected') diff --git a/exercises/static/exercises/rescue_people/web-template/gui.py b/exercises/static/exercises/rescue_people/web-template/gui.py old mode 100644 new mode 100755 index 51d327f1a..2b2e10176 --- a/exercises/static/exercises/rescue_people/web-template/gui.py +++ b/exercises/static/exercises/rescue_people/web-template/gui.py @@ -5,15 +5,23 @@ import time from datetime import datetime from websocket_server import WebsocketServer +import logging +import rospy +import cv2 +import sys +import numpy as np +import multiprocessing +from shared.image import SharedImage +from shared.value import SharedValue # Graphical User Interface Class class GUI: # Initialization function # The actual initialization def __init__(self, host): - t = threading.Thread(target=self.run_server) + rospy.init_node("GUI") self.payload = {'image': ''} self.left_payload = {'image': ''} self.server = None @@ -21,81 +29,47 @@ def __init__(self, host): self.host = host - # Image variables - self.image_to_be_shown = None - self.image_to_be_shown_updated = False - self.image_show_lock = threading.Lock() - - self.left_image_to_be_shown = None - self.left_image_to_be_shown_updated = False - self.left_image_show_lock = threading.Lock() - - self.acknowledge = False - self.acknowledge_lock = threading.Lock() + # Image variable host + self.shared_image = SharedImage("guifrontalimage") + self.shared_left_image = SharedImage("guiventralimage") - # Take the console object to set the same websocket and client + # Event objects for multiprocessing + self.ack_event = multiprocessing.Event() + self.cli_event = multiprocessing.Event() + + # Start server thread + t = threading.Thread(target=self.run_server) t.start() - # Explicit initialization function - # Class method, so user can call it without instantiation - @classmethod - def initGUI(cls, host): - # self.payload = {'image': '', 'shape': []} - new_instance = cls(host) - return new_instance # Function to prepare image payload # Encodes the image as a JSON string and sends through the WS def payloadImage(self): - self.image_show_lock.acquire() - image_to_be_shown_updated = self.image_to_be_shown_updated - image_to_be_shown = self.image_to_be_shown - self.image_show_lock.release() - - image = image_to_be_shown + image = self.shared_image.get() payload = {'image': '', 'shape': ''} - - if not image_to_be_shown_updated: - return payload - + shape = image.shape frame = cv2.imencode('.JPEG', image)[1] encoded_image = base64.b64encode(frame) - + payload['image'] = encoded_image.decode('utf-8') payload['shape'] = shape - - self.image_show_lock.acquire() - self.image_to_be_shown_updated = False - self.image_show_lock.release() - + return payload - + # Function to prepare image payload # Encodes the image as a JSON string and sends through the WS def payloadLeftImage(self): - self.left_image_show_lock.acquire() - left_image_to_be_shown_updated = self.left_image_to_be_shown_updated - left_image_to_be_shown = self.left_image_to_be_shown - self.left_image_show_lock.release() - - image = left_image_to_be_shown + image = self.shared_left_image.get() payload = {'image': '', 'shape': ''} - - if not left_image_to_be_shown_updated: - return payload - + shape = image.shape frame = cv2.imencode('.JPEG', image)[1] encoded_image = base64.b64encode(frame) - + payload['image'] = encoded_image.decode('utf-8') payload['shape'] = shape - - self.left_image_show_lock.acquire() - self.left_image_to_be_shown_updated = False - self.left_image_show_lock.release() - + return payload # Function for student to call @@ -116,20 +90,10 @@ def showLeftImage(self, image): # Called when a new client is received def get_client(self, client, server): self.client = client + self.cli_event.set() + + print(client, 'connected') - # Function to get value of Acknowledge - def get_acknowledge(self): - self.acknowledge_lock.acquire() - acknowledge = self.acknowledge - self.acknowledge_lock.release() - - return acknowledge - - # Function to get value of Acknowledge - def set_acknowledge(self, value): - self.acknowledge_lock.acquire() - self.acknowledge = value - self.acknowledge_lock.release() # Update the gui def update_gui(self): @@ -151,14 +115,23 @@ def update_gui(self): # Gets called when there is an incoming message from the client def get_message(self, client, server, message): # Acknowledge Message for GUI Thread - if message[:4] == "#ack": - self.set_acknowledge(True) + + if(message[:4] == "#ack"): + # Set acknowledgement flag + self.ack_event.set() + + # Function that gets called when the connected closes + def handle_close(self, client, server): + print(client, 'closed') + + # Activate the server def run_server(self): self.server = WebsocketServer(port=2303, host=self.host) self.server.set_fn_new_client(self.get_client) self.server.set_fn_message_received(self.get_message) + self.server.set_fn_client_left(self.handle_close) logged = False while not logged: @@ -179,32 +152,45 @@ def reset_gui(self): # This class decouples the user thread # and the GUI update thread -class ThreadGUI: - def __init__(self, gui): - self.gui = gui +class ProcessGUI(multiprocessing.Process): + def __init__(self): + super(ProcessGUI, self).__init__() + self.host = sys.argv[1] # Time variables - self.ideal_cycle = 80 - self.measured_cycle = 80 + self.time_cycle = SharedValue("gui_time_cycle") + self.ideal_cycle = SharedValue("gui_ideal_cycle") self.iteration_counter = 0 + # Function to initialize events + def initialize_events(self): + # Events + self.ack_event = self.gui.ack_event + self.cli_event = self.gui.cli_event + self.exit_signal = multiprocessing.Event() + # Function to start the execution of threads - def start(self): + def run(self): + # Initialize GUI + self.gui = GUI(self.host) + self.initialize_events() + + # Wait for client before starting + self.cli_event.wait() self.measure_thread = threading.Thread(target=self.measure_thread) - self.thread = threading.Thread(target=self.run) + self.thread = threading.Thread(target=self.run_gui) self.measure_thread.start() self.thread.start() - print("GUI Thread Started!") + print("GUI Process Started!") + + self.exit_signal.wait() # The measuring thread to measure frequency def measure_thread(self): - while self.gui.client is None: - pass - previous_time = datetime.now() - while True: + while(True): # Sleep for 2 seconds time.sleep(2) @@ -217,32 +203,40 @@ def measure_thread(self): # Get the time period try: # Division by zero - self.measured_cycle = ms / self.iteration_counter + self.ideal_cycle.add(ms / self.iteration_counter) except: - self.measured_cycle = 0 + self.ideal_cycle.add(0) # Reset the counter self.iteration_counter = 0 # The main thread of execution - def run(self): - while self.gui.client is None: - pass - - while True: + def run_gui(self): + while(True): start_time = datetime.now() + # Send update signal self.gui.update_gui() - acknowledge_message = self.gui.get_acknowledge() - - while not acknowledge_message: - acknowledge_message = self.gui.get_acknowledge() - self.gui.set_acknowledge(False) + # Wait for acknowldege signal + self.ack_event.wait() + self.ack_event.clear() finish_time = datetime.now() self.iteration_counter = self.iteration_counter + 1 dt = finish_time - start_time ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 - if ms < self.ideal_cycle: - time.sleep((self.ideal_cycle-ms) / 1000.0) + time_cycle = self.time_cycle.get() + + if(ms < time_cycle): + time.sleep((time_cycle-ms) / 1000.0) + + self.exit_signal.set() + + # Functions to handle auxillary GUI functions + def reset_gui(self): + self.gui.reset_gui() + +if __name__ == "__main__": + gui = ProcessGUI() + gui.start() \ No newline at end of file diff --git a/exercises/static/exercises/rescue_people/web-template/hal.py b/exercises/static/exercises/rescue_people/web-template/hal.py old mode 100644 new mode 100755 index 25635a119..17c39cd6b --- a/exercises/static/exercises/rescue_people/web-template/hal.py +++ b/exercises/static/exercises/rescue_people/web-template/hal.py @@ -1,9 +1,12 @@ -import numpy as np import rospy import cv2 +import threading +import time +from datetime import datetime from drone_wrapper import DroneWrapper - +from shared.image import SharedImage +from shared.value import SharedValue # Hardware Abstraction Layer class HAL: @@ -12,71 +15,166 @@ class HAL: def __init__(self): rospy.init_node("HAL") - + + self.shared_frontal_image = SharedImage("halfrontalimage") + self.shared_ventral_image = SharedImage("halventralimage") + self.shared_x = SharedValue("x") + self.shared_y = SharedValue("y") + self.shared_z = SharedValue("z") + self.shared_takeoff_z = SharedValue("sharedtakeoffz") + self.shared_az = SharedValue("az") + self.shared_azt = SharedValue("azt") + self.shared_vx = SharedValue("vx") + self.shared_vy = SharedValue("vy") + self.shared_vz = SharedValue("vz") + self.shared_landed_state = SharedValue("landedstate") + self.shared_position = SharedValue("position") + self.shared_velocity = SharedValue("velocity") + self.shared_orientation = SharedValue("orientation") + self.shared_roll = SharedValue("roll") + self.shared_pitch = SharedValue("pitch") + self.shared_yaw = SharedValue("yaw") + self.shared_yaw_rate = SharedValue("yawrate") + self.image = None - self.drone = DroneWrapper(name="rqt") + self.drone = DroneWrapper(name="rqt",ns="/iris/") + + # Update thread + self.thread = ThreadHAL(self.update_hal) # Explicit initialization functions # Class method, so user can call it without instantiation - @classmethod - def initRobot(cls): - new_instance = cls() - return new_instance + + + # Function to start the update thread + def start_thread(self): + self.thread.start() # Get Image from ROS Driver Camera def get_frontal_image(self): image = self.drone.get_frontal_image() image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) - return image_rgb + self.shared_frontal_image.add(image_rgb) def get_ventral_image(self): image = self.drone.get_ventral_image() image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) - return image_rgb + self.shared_ventral_image.add(image_rgb) def get_position(self): pos = self.drone.get_position() - return pos + self.shared_position.add(pos,type_name="list") def get_velocity(self): vel = self.drone.get_velocity() - return vel + self.shared_velocity.add(vel ,type_name="list") def get_yaw_rate(self): yaw_rate = self.drone.get_yaw_rate() - return yaw_rate + self.shared_yaw_rate.add(yaw_rate) def get_orientation(self): orientation = self.drone.get_orientation() - return orientation + self.shared_orientation.add(orientation ,type_name="list") def get_roll(self): roll = self.drone.get_roll() - return roll + self.shared_roll.add(roll) def get_pitch(self): pitch = self.drone.get_pitch() - return pitch + self.shared_pitch.add(pitch) def get_yaw(self): yaw = self.drone.get_yaw() - return yaw + self.shared_yaw.add(yaw) def get_landed_state(self): state = self.drone.get_landed_state() - return state + self.shared_landed_state.add(state) + + def set_cmd_pos(self): + x = self.shared_x.get() + y = self.shared_y.get() + z = self.shared_z.get() + az = self.shared_az.get() - def set_cmd_pos(self, x, y, z, az): self.drone.set_cmd_pos(x, y, z, az) - def set_cmd_vel(self, vx, vy, vz, az): + def set_cmd_vel(self): + vx = self.shared_vx.get() + vy = self.shared_vy.get() + vz = self.shared_vz.get() + az = self.shared_azt.get() self.drone.set_cmd_vel(vx, vy, vz, az) - def set_cmd_mix(self, vx, vy, z, az): + def set_cmd_mix(self): + vx = self.shared_vx.get() + vy = self.shared_vy.get() + z = self.shared_z.get() + az = self.shared_azt.get() self.drone.set_cmd_mix(vx, vy, z, az) - def takeoff(self, h=3): + def takeoff(self): + h = self.shared_takeoff_z.get() self.drone.takeoff(h) def land(self): self.drone.land() + + def update_hal(self): + self.get_frontal_image() + self.get_ventral_image() + self.get_position() + self.get_velocity() + self.get_yaw_rate() + self.get_orientation() + self.get_pitch() + self.get_roll() + self.get_yaw() + self.get_landed_state() + self.set_cmd_pos() + self.set_cmd_vel() + self.set_cmd_mix() + + # Destructor function to close all fds + def __del__(self): + self.shared_frontal_image.close() + self.shared_ventral_image.close() + self.shared_x.close() + self.shared_y.close() + self.shared_z.close() + self.shared_takeoff_z.close() + self.shared_az.close() + self.shared_azt.close() + self.shared_vx.close() + self.shared_vy.close() + self.shared_vz.close() + self.shared_landed_state.close() + self.shared_position.close() + self.shared_velocity.close() + self.shared_orientation.close() + self.shared_roll.close() + self.shared_pitch.close() + self.shared_yaw.close() + self.shared_yaw_rate.close() + +class ThreadHAL(threading.Thread): + def __init__(self, update_function): + super(ThreadHAL, self).__init__() + self.time_cycle = 80 + self.update_function = update_function + + def run(self): + while(True): + start_time = datetime.now() + + self.update_function() + + finish_time = datetime.now() + + dt = finish_time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + + if(ms < self.time_cycle): + time.sleep((self.time_cycle - ms) / 1000.0) \ No newline at end of file diff --git a/exercises/static/exercises/rescue_people/web-template/launch/gazebo.launch b/exercises/static/exercises/rescue_people/web-template/launch/gazebo.launch deleted file mode 100644 index b959ebdc7..000000000 --- a/exercises/static/exercises/rescue_people/web-template/launch/gazebo.launch +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/exercises/static/exercises/rescue_people/web-template/launch/launch.py b/exercises/static/exercises/rescue_people/web-template/launch/launch.py deleted file mode 100644 index dade2943c..000000000 --- a/exercises/static/exercises/rescue_people/web-template/launch/launch.py +++ /dev/null @@ -1,131 +0,0 @@ -#!/usr/bin/env python3 - -import stat -import rospy -from os import lstat -from subprocess import Popen, PIPE - - -DRI_PATH = "/dev/dri/card0" -EXERCISE = "rescue_people" -TIMEOUT = 30 -MAX_ATTEMPT = 2 - - -# Check if acceleration can be enabled -def check_device(device_path): - try: - return stat.S_ISCHR(lstat(device_path)[stat.ST_MODE]) - except: - return False - - -# Spawn new process -def spawn_process(args, insert_vglrun=False): - if insert_vglrun: - args.insert(0, "vglrun") - process = Popen(args, stdout=PIPE, bufsize=1, universal_newlines=True) - return process - - -class Test(): - def gazebo(self): - rospy.logwarn("[GAZEBO] Launching") - try: - rospy.wait_for_service("/gazebo/get_model_properties", TIMEOUT) - return True - except rospy.ROSException: - return False - - def px4(self): - rospy.logwarn("[PX4-SITL] Launching") - start_time = rospy.get_time() - args = ["./PX4-Autopilot/build/px4_sitl_default/bin/px4-commander","--instance", "0", "check"] - while rospy.get_time() - start_time < TIMEOUT: - process = spawn_process(args, insert_vglrun=False) - with process.stdout: - for line in iter(process.stdout.readline, ''): - if ("Prearm check: OK" in line): - return True - rospy.sleep(2) - return False - - def mavros(self, ns=""): - rospy.logwarn("[MAVROS] Launching") - try: - rospy.wait_for_service(ns + "/mavros/cmd/arming", TIMEOUT) - return True - except rospy.ROSException: - return False - - -class Launch(): - def __init__(self): - self.test = Test() - self.acceleration_enabled = check_device(DRI_PATH) - - # Start roscore - args = ["/opt/ros/noetic/bin/roscore"] - spawn_process(args, insert_vglrun=False) - - rospy.init_node("launch", anonymous=True) - - def start(self): - ######## LAUNCH GAZEBO ######## - args = ["/opt/ros/noetic/bin/roslaunch", - "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/gazebo.launch", - "--wait", - "--log" - ] - - attempt = 1 - while True: - spawn_process(args, insert_vglrun=self.acceleration_enabled) - if self.test.gazebo() == True: - break - if attempt == MAX_ATTEMPT: - rospy.logerr("[GAZEBO] Launch Failed") - return - attempt = attempt + 1 - - - ######## LAUNCH PX4 ######## - args = ["/opt/ros/noetic/bin/roslaunch", - "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/px4.launch", - "--log" - ] - - attempt = 1 - while True: - spawn_process(args, insert_vglrun=self.acceleration_enabled) - if self.test.px4() == True: - break - if attempt == MAX_ATTEMPT: - rospy.logerr("[PX4] Launch Failed") - return - attempt = attempt + 1 - - - ######## LAUNCH MAVROS ######## - args = ["/opt/ros/noetic/bin/roslaunch", - "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/mavros.launch", - "--log" - ] - - attempt = 1 - while True: - spawn_process(args, insert_vglrun=self.acceleration_enabled) - if self.test.mavros() == True: - break - if attempt == MAX_ATTEMPT: - rospy.logerr("[MAVROS] Launch Failed") - return - attempt = attempt + 1 - - -if __name__ == "__main__": - launch = Launch() - launch.start() - - with open("/drones_launch.log", "w") as f: - f.write("success") \ No newline at end of file diff --git a/exercises/static/exercises/rescue_people/web-template/launch/mavros.launch b/exercises/static/exercises/rescue_people/web-template/launch/mavros.launch deleted file mode 100644 index b899c0ec1..000000000 --- a/exercises/static/exercises/rescue_people/web-template/launch/mavros.launch +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/exercises/static/exercises/rescue_people/web-template/launch/px4.launch b/exercises/static/exercises/rescue_people/web-template/launch/px4.launch deleted file mode 100644 index bb3f656b5..000000000 --- a/exercises/static/exercises/rescue_people/web-template/launch/px4.launch +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/exercises/static/exercises/rescue_people/web-template/launch/rescue_people.launch b/exercises/static/exercises/rescue_people/web-template/launch/rescue_people.launch new file mode 100755 index 000000000..e740c2eed --- /dev/null +++ b/exercises/static/exercises/rescue_people/web-template/launch/rescue_people.launch @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/exercises/static/exercises/rescue_people/web-template/launch/rescue_people.sh b/exercises/static/exercises/rescue_people/web-template/launch/rescue_people.sh new file mode 100755 index 000000000..ba757f815 --- /dev/null +++ b/exercises/static/exercises/rescue_people/web-template/launch/rescue_people.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +#People faces spawn loc random +Y1=$((RANDOM%(4))).$((RANDOM%999)) +x1=$(shuf -i25-45 -n1) +y1=$(shuf -i25-45 -n1) +Y2=$((RANDOM%(4))).$((RANDOM%999)) +x2=$(shuf -i25-45 -n1) +y2=$(shuf -i25-45 -n1) +Y3=$((RANDOM%(4))).$((RANDOM%999)) +x3=$(shuf -i25-45 -n1) +y3=$(shuf -i25-45 -n1) +Y4=$((RANDOM%(4))).$((RANDOM%999)) +x4=$(shuf -i25-45 -n1) +y4=$(shuf -i25-45 -n1) +Y5=$((RANDOM%(4))).$((RANDOM%999)) +x5=$(shuf -i25-45 -n1) +y5=$(shuf -i25-45 -n1) +Y6=$((RANDOM%(4))).$((RANDOM%999)) +x6=$(shuf -i25-45 -n1) +y6=$(shuf -i25-45 -n1) + +/opt/ros/noetic/bin/roslaunch ./RoboticsAcademy/exercises/rescue_people/web-template/launch/rescue_people.launch x1:=$x1 y1:=$y1 Y1:=$Y1 x2:=$x2 y2:=$y2 Y2:=$Y2 x3:=$x3 y3:=$y3 Y3:=$Y3 x4:=$x4 y4:=$y4 Y4:=$Y4 x5:=$x5 y5:=$y5 Y5:=$Y5 x6:=$x6 y6:=$y6 Y6:=$Y6 diff --git a/exercises/static/exercises/rescue_people/web-template/rescue_people.world b/exercises/static/exercises/rescue_people/web-template/rescue_people.world index 957ba13c1..3f4ec35af 100644 --- a/exercises/static/exercises/rescue_people/web-template/rescue_people.world +++ b/exercises/static/exercises/rescue_people/web-template/rescue_people.world @@ -3,31 +3,8 @@ - - - model://face1 - 35 -35 0 0 0 -1.57 - - - model://face2 - 42 -40 0 0 0 0.57 - - - model://face3 - 32 -39 0 0 0 2.57 - - - model://face4 - 40 -34 0 0 0 0 - - - model://face5 - 25 -35 0 0 0 1.11 - - - model://face6 - 37 -31 0 0 0 -1.0 - + + @@ -57,26 +34,40 @@ - - 0 0 -9.8066 - - - quick - 10 - 1.3 - 0 - - - 0 - 0.2 - 100 - 0.001 - - - 0.004 - 1 - 250 - 6.0e-6 2.3e-5 -4.2e-5 + + + + + + + EARTH_WGS84 + 47.3667 + 8.5500 + 500.0 + 0 + + + + + + + quick + 1000 + 1.3 + + + 0 + 0.2 + 100 + 0.001 + + + 0.004 + 1 + 250 + 6.0e-06 2.3e-05 -4.2e-05 + 0 0 -9.8 diff --git a/exercises/static/exercises/rescue_people/web-template/shared/__init__.py b/exercises/static/exercises/rescue_people/web-template/shared/__init__.py new file mode 100755 index 000000000..e69de29bb diff --git a/exercises/static/exercises/rescue_people/web-template/shared/image.py b/exercises/static/exercises/rescue_people/web-template/shared/image.py new file mode 100755 index 000000000..de5c9f9d6 --- /dev/null +++ b/exercises/static/exercises/rescue_people/web-template/shared/image.py @@ -0,0 +1,109 @@ +import numpy as np +import mmap +from posix_ipc import Semaphore, O_CREX, ExistentialError, O_CREAT, SharedMemory, unlink_shared_memory +from ctypes import sizeof, memmove, addressof, create_string_buffer +from shared.structure_img import MD + +# Probably, using self variables gives errors with memmove +# Therefore, a global variable for utility +md_buf = create_string_buffer(sizeof(MD)) + +class SharedImage: + def __init__(self, name): + # Initialize variables for memory regions and buffers and Semaphore + self.shm_buf = None; self.shm_region = None + self.md_buf = None; self.md_region = None + self.image_lock = None + + self.shm_name = name; self.md_name = name + "-meta" + self.image_lock_name = name + + # Initialize or retreive metadata memory region + try: + self.md_region = SharedMemory(self.md_name) + self.md_buf = mmap.mmap(self.md_region.fd, sizeof(MD)) + self.md_region.close_fd() + except ExistentialError: + self.md_region = SharedMemory(self.md_name, O_CREAT, size=sizeof(MD)) + self.md_buf = mmap.mmap(self.md_region.fd, self.md_region.size) + self.md_region.close_fd() + + # Initialize or retreive Semaphore + try: + self.image_lock = Semaphore(self.image_lock_name, O_CREX) + except ExistentialError: + image_lock = Semaphore(self.image_lock_name, O_CREAT) + image_lock.unlink() + self.image_lock = Semaphore(self.image_lock_name, O_CREX) + + self.image_lock.release() + + # Get the shared image + def get(self): + # Define metadata + metadata = MD() + + # Get metadata from the shared region + self.image_lock.acquire() + md_buf[:] = self.md_buf + memmove(addressof(metadata), md_buf, sizeof(metadata)) + self.image_lock.release() + + # Try to retreive the image from shm_buffer + # Otherwise return a zero image + try: + self.shm_region = SharedMemory(self.shm_name) + self.shm_buf = mmap.mmap(self.shm_region.fd, metadata.size) + self.shm_region.close_fd() + + self.image_lock.acquire() + image = np.ndarray(shape=(metadata.shape_0, metadata.shape_1, metadata.shape_2), + dtype='uint8', buffer=self.shm_buf) + self.image_lock.release() + + # Check for a None image + if(image.size == 0): + image = np.zeros((3, 3, 3), np.uint8) + + except ExistentialError: + image = np.zeros((3, 3, 3), np.uint8) + + return image + + # Add the shared image + def add(self, image): + try: + # Get byte size of the image + byte_size = image.nbytes + + # Get the shared memory buffer to read from + if not self.shm_region: + self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=byte_size) + self.shm_buf = mmap.mmap(self.shm_region.fd, byte_size) + self.shm_region.close_fd() + + # Generate meta data + metadata = MD(image.shape[0], image.shape[1], image.shape[2], byte_size) + + # Send the meta data and image to shared regions + self.image_lock.acquire() + memmove(md_buf, addressof(metadata), sizeof(metadata)) + self.md_buf[:] = md_buf[:] + self.shm_buf[:] = image.tobytes() + self.image_lock.release() + except: + pass + + # Destructor function to unlink and disconnect + def close(self): + self.image_lock.acquire() + self.md_buf.close() + + try: + unlink_shared_memory(self.md_name) + unlink_shared_memory(self.shm_name) + except ExistentialError: + pass + + self.image_lock.release() + self.image_lock.close() diff --git a/exercises/static/exercises/rescue_people/web-template/shared/structure_img.py b/exercises/static/exercises/rescue_people/web-template/shared/structure_img.py new file mode 100755 index 000000000..ae40b3707 --- /dev/null +++ b/exercises/static/exercises/rescue_people/web-template/shared/structure_img.py @@ -0,0 +1,9 @@ +from ctypes import Structure, c_int32, c_int64 + +class MD(Structure): + _fields_ = [ + ('shape_0', c_int32), + ('shape_1', c_int32), + ('shape_2', c_int32), + ('size', c_int64) + ] \ No newline at end of file diff --git a/exercises/static/exercises/rescue_people/web-template/shared/value.py b/exercises/static/exercises/rescue_people/web-template/shared/value.py new file mode 100755 index 000000000..e7bfad8a2 --- /dev/null +++ b/exercises/static/exercises/rescue_people/web-template/shared/value.py @@ -0,0 +1,93 @@ +import numpy as np +import mmap +from posix_ipc import Semaphore, O_CREX, ExistentialError, O_CREAT, SharedMemory, unlink_shared_memory +from ctypes import sizeof, memmove, addressof, create_string_buffer, c_float +import struct + +class SharedValue: + def __init__(self, name): + # Initialize varaibles for memory regions and buffers and Semaphore + self.shm_buf = None; self.shm_region = None + self.value_lock = None + + self.shm_name = name; self.value_lock_name = name + + # Initialize or retreive Semaphore + try: + self.value_lock = Semaphore(self.value_lock_name, O_CREX) + except ExistentialError: + value_lock = Semaphore(self.value_lock_name, O_CREAT) + value_lock.unlink() + self.value_lock = Semaphore(self.value_lock_name, O_CREX) + + self.value_lock.release() + + # Get the shared value + def get(self, type_name= "value"): + # Retreive the data from buffer + if type_name=="value": + try: + self.shm_region = SharedMemory(self.shm_name) + self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) + self.shm_region.close_fd() + except ExistentialError: + self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=sizeof(c_float)) + self.shm_buf = mmap.mmap(self.shm_region.fd, self.shm_region.size) + self.shm_region.close_fd() + self.value_lock.acquire() + value = struct.unpack('f', self.shm_buf)[0] + self.value_lock.release() + + return value + elif type_name=="list": + self.shm_region = SharedMemory(self.shm_name) + self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) + self.shm_region.close_fd() + self.value_lock.acquire() + array_val = np.ndarray(shape=(3,), + dtype='float32', buffer=self.shm_buf) + self.value_lock.release() + + return array_val + + else: + print("missing argument for return type") + + + # Add the shared value + def add(self, value, type_name= "value"): + # Send the data to shared regions + if type_name=="value": + try: + self.shm_region = SharedMemory(self.shm_name) + self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) + self.shm_region.close_fd() + except ExistentialError: + self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=sizeof(c_float)) + self.shm_buf = mmap.mmap(self.shm_region.fd, self.shm_region.size) + self.shm_region.close_fd() + + self.value_lock.acquire() + self.shm_buf[:] = struct.pack('f', value) + self.value_lock.release() + elif type_name=="list": + byte_size = value.nbytes + self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=byte_size) + self.shm_buf = mmap.mmap(self.shm_region.fd, byte_size) + self.shm_region.close_fd() + self.value_lock.acquire() + self.shm_buf[:] = value.tobytes() + self.value_lock.release() + + # Destructor function to unlink and disconnect + def close(self): + self.value_lock.acquire() + self.shm_buf.close() + + try: + unlink_shared_memory(self.shm_name) + except ExistentialError: + pass + + self.value_lock.release() + self.value_lock.close() diff --git a/exercises/static/exercises/rescue_people/web-template/user_functions.py b/exercises/static/exercises/rescue_people/web-template/user_functions.py new file mode 100755 index 000000000..d741601fc --- /dev/null +++ b/exercises/static/exercises/rescue_people/web-template/user_functions.py @@ -0,0 +1,117 @@ +from shared.image import SharedImage +from shared.value import SharedValue +import numpy as np +import cv2 + +# Define HAL functions +class HALFunctions: + def __init__(self): + # Initialize image variable + self.shared_frontal_image = SharedImage("halfrontalimage") + self.shared_ventral_image = SharedImage("halventralimage") + self.shared_x = SharedValue("x") + self.shared_y = SharedValue("y") + self.shared_z = SharedValue("z") + self.shared_takeoff_z = SharedValue("sharedtakeoffz") + self.shared_az = SharedValue("az") + self.shared_azt = SharedValue("azt") + self.shared_vx = SharedValue("vx") + self.shared_vy = SharedValue("vy") + self.shared_vz = SharedValue("vz") + self.shared_landed_state = SharedValue("landedstate") + self.shared_position = SharedValue("position") + self.shared_velocity = SharedValue("velocity") + self.shared_orientation = SharedValue("orientation") + self.shared_roll = SharedValue("roll") + self.shared_pitch = SharedValue("pitch") + self.shared_yaw = SharedValue("yaw") + self.shared_yaw_rate = SharedValue("yawrate") + + + # Get image function + def get_frontal_image(self): + image = self.shared_frontal_image.get() + return image + + # Get left image function + def get_ventral_image(self): + image = self.shared_ventral_image.get() + return image + + def takeoff(self, height): + self.shared_takeoff_z.add(height) + + def land(self): + pass + + def set_cmd_pos(self, x, y , z, az): + self.shared_x.add(x) + self.shared_y.add(y) + self.shared_z.add(z) + self.shared_az.add(az) + + def set_cmd_vel(self, vx, vy, vz, az): + self.shared_vx.add(vx) + self.shared_vy.add(vy) + self.shared_vz.add(vz) + self.shared_azt.add(az) + + def set_cmd_mix(self, vx, vy, z, az): + self.shared_vx.add(vx) + self.shared_vy.add(vy) + self.shared_vz.add(z) + self.shared_azt.add(az) + + + def get_position(self): + position = self.shared_position.get(type_name = "list") + return position + + def get_velocity(self): + velocity = self.shared_velocity.get(type_name = "list") + return velocity + + def get_yaw_rate(self): + yaw_rate = self.shared_yaw_rate.get(type_name = "value") + return yaw_rate + + def get_orientation(self): + orientation = self.shared_orientation.get(type_name = "list") + return orientation + + def get_roll(self): + roll = self.shared_roll.get(type_name = "value") + return roll + + def get_pitch(self): + pitch = self.shared_pitch.get(type_name = "value") + return pitch + + def get_yaw(self): + yaw = self.shared_yaw.get(type_name = "value") + return yaw + + def get_landed_state(self): + landed_state = self.shared_landed_state.get(type_name = "value") + return landed_state + +# Define GUI functions +class GUIFunctions: + def __init__(self): + # Initialize image variable + self.shared_image = SharedImage("guifrontalimage") + self.shared_left_image = SharedImage("guiventralimage") + + # Show image function + def showImage(self, image): + # Reshape to 3 channel if it has only 1 in order to display it + if (len(image.shape) < 3): + image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB) + self.shared_image.add(image) + + # Show left image function + def showLeftImage(self, image): + # Reshape to 3 channel if it has only 1 in order to display it + if (len(image.shape) < 3): + image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB) + self.shared_left_image.add(image) \ No newline at end of file diff --git a/scripts/instructions.json b/scripts/instructions.json index a9ddd8170..d15d965b8 100644 --- a/scripts/instructions.json +++ b/scripts/instructions.json @@ -77,8 +77,9 @@ }, "rescue_people": { "gazebo_path": "/RoboticsAcademy/exercises/rescue_people/web-template/launch", - "instructions_ros": ["python3 ./RoboticsAcademy/exercises/rescue_people/web-template/launch/launch.py"], - "instructions_host": "python3 /RoboticsAcademy/exercises/rescue_people/web-template/exercise.py 0.0.0.0" + "instructions_ros": ["bash ./RoboticsAcademy/exercises/rescue_people/web-template/launch/rescue_people.sh"], + "instructions_host": "python3 /RoboticsAcademy/exercises/rescue_people/web-template/exercise.py 0.0.0.0", + "instructions_gui": "python3 /RoboticsAcademy/exercises/rescue_people/web-template/gui.py 0.0.0.0 {}" }, "drone_hangar": { "gazebo_path": "/RoboticsAcademy/exercises/drone_hangar/web-template/launch", From 1ece9fcd6faf1abfff3a44048387dacf7f3b6007 Mon Sep 17 00:00:00 2001 From: RUFFY-369 Date: Sat, 10 Sep 2022 17:19:12 +0530 Subject: [PATCH 13/42] add multiprocessing for power tower ins final --- .../web-template/brain.py | 166 +++++++++++ .../web-template/exercise.py | 270 ++++++------------ .../web-template/gui.py | 180 ++++++------ .../web-template/hal.py | 138 +++++++-- .../web-template/launch/gazebo.launch | 24 -- .../web-template/launch/launch.py | 131 --------- .../web-template/launch/mavros.launch | 13 - .../launch/power_tower_inspection.launch | 61 ++++ .../web-template/launch/px4.launch | 20 -- .../web-template/shared/__init__.py | 0 .../web-template/shared/image.py | 109 +++++++ .../web-template/shared/structure_img.py | 9 + .../web-template/shared/value.py | 93 ++++++ .../web-template/user_functions.py | 117 ++++++++ scripts/instructions.json | 5 +- 15 files changed, 854 insertions(+), 482 deletions(-) create mode 100755 exercises/static/exercises/power_tower_inspection/web-template/brain.py mode change 100644 => 100755 exercises/static/exercises/power_tower_inspection/web-template/hal.py delete mode 100644 exercises/static/exercises/power_tower_inspection/web-template/launch/gazebo.launch delete mode 100644 exercises/static/exercises/power_tower_inspection/web-template/launch/launch.py delete mode 100644 exercises/static/exercises/power_tower_inspection/web-template/launch/mavros.launch create mode 100755 exercises/static/exercises/power_tower_inspection/web-template/launch/power_tower_inspection.launch delete mode 100644 exercises/static/exercises/power_tower_inspection/web-template/launch/px4.launch create mode 100755 exercises/static/exercises/power_tower_inspection/web-template/shared/__init__.py create mode 100755 exercises/static/exercises/power_tower_inspection/web-template/shared/image.py create mode 100755 exercises/static/exercises/power_tower_inspection/web-template/shared/structure_img.py create mode 100755 exercises/static/exercises/power_tower_inspection/web-template/shared/value.py create mode 100755 exercises/static/exercises/power_tower_inspection/web-template/user_functions.py diff --git a/exercises/static/exercises/power_tower_inspection/web-template/brain.py b/exercises/static/exercises/power_tower_inspection/web-template/brain.py new file mode 100755 index 000000000..2330c04ba --- /dev/null +++ b/exercises/static/exercises/power_tower_inspection/web-template/brain.py @@ -0,0 +1,166 @@ +import time +import threading +import multiprocessing +import sys +from datetime import datetime +import re +import json +import importlib + +import rospy +from std_srvs.srv import Empty +import cv2 + +from user_functions import GUIFunctions, HALFunctions +from console import start_console, close_console + +from shared.value import SharedValue + +# The brain process class +class BrainProcess(multiprocessing.Process): + def __init__(self, code, exit_signal): + super(BrainProcess, self).__init__() + + # Initialize exit signal + self.exit_signal = exit_signal + + # Function definitions for users to use + self.hal = HALFunctions() + self.gui = GUIFunctions() + + # Time variables + self.time_cycle = SharedValue('brain_time_cycle') + self.ideal_cycle = SharedValue('brain_ideal_cycle') + self.iteration_counter = 0 + + # Get the sequential and iterative code + # Something wrong over here! The code is reversing + # Found a solution but could not find the reason for this (parse_code function's return line of exercise.py is the reason) + self.sequential_code = code[1] + self.iterative_code = code[0] + + # Function to run to start the process + def run(self): + # Two threads for running and measuring + self.measure_thread = threading.Thread(target=self.measure_frequency) + self.thread = threading.Thread(target=self.process_code) + + self.measure_thread.start() + self.thread.start() + + print("Brain Process Started!") + + self.exit_signal.wait() + + # The process function + def process_code(self): + # Redirect information to console + start_console() + + # Reference Environment for the exec() function + iterative_code, sequential_code = self.iterative_code, self.sequential_code + + # The Python exec function + # Run the sequential part + gui_module, hal_module = self.generate_modules() + if sequential_code != "": + reference_environment = {"GUI": gui_module, "HAL": hal_module} + exec(sequential_code, reference_environment) + + # Run the iterative part inside template + # and keep the check for flag + while not self.exit_signal.is_set(): + start_time = datetime.now() + + # Execute the iterative portion + if iterative_code != "": + exec(iterative_code, reference_environment) + + # Template specifics to run! + finish_time = datetime.now() + dt = finish_time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + + # Keep updating the iteration counter + if(iterative_code == ""): + self.iteration_counter = 0 + else: + self.iteration_counter = self.iteration_counter + 1 + + # The code should be run for atleast the target time step + # If it's less put to sleep + # If it's more no problem as such, but we can change it! + time_cycle = self.time_cycle.get() + + if(ms < time_cycle): + time.sleep((time_cycle - ms) / 1000.0) + + close_console() + print("Current Thread Joined!") + + + # Function to generate the modules for use in ACE Editor + def generate_modules(self): + # Define HAL module + hal_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) + hal_module.HAL = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) + hal_module.HAL.motors = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("motors", None)) + + # Add HAL functions + hal_module.HAL.get_frontal_image = self.hal.get_frontal_image + hal_module.HAL.get_ventral_image = self.hal.get_ventral_image + hal_module.HAL.takeoff = self.hal.takeoff + hal_module.HAL.land = self.hal.land + hal_module.HAL.set_cmd_pos = self.hal.set_cmd_pos + hal_module.HAL.set_cmd_vel = self.hal.set_cmd_vel + hal_module.HAL.set_cmd_mix = self.hal.set_cmd_mix + hal_module.HAL.get_position = self.hal.get_position + hal_module.HAL.get_velocity = self.hal.get_velocity + hal_module.HAL.get_orientation = self.hal.get_orientation + hal_module.HAL.get_roll = self.hal.get_roll + hal_module.HAL.get_pitch = self.hal.get_pitch + hal_module.HAL.get_yaw = self.hal.get_yaw + hal_module.HAL.get_landed_state = self.hal.get_landed_state + hal_module.HAL.get_yaw_rate = self.hal.get_yaw_rate + + + + # Define GUI module + gui_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) + gui_module.GUI = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) + + # Add GUI functions + gui_module.GUI.showImage = self.gui.showImage + gui_module.GUI.showLeftImage = self.gui.showLeftImage + + # Adding modules to system + # Protip: The names should be different from + # other modules, otherwise some errors + sys.modules["HAL"] = hal_module + sys.modules["GUI"] = gui_module + + return gui_module, hal_module + + # Function to measure the frequency of iterations + def measure_frequency(self): + previous_time = datetime.now() + # An infinite loop + while not self.exit_signal.is_set(): + # Sleep for 2 seconds + time.sleep(2) + + # Measure the current time and subtract from the previous time to get real time interval + current_time = datetime.now() + dt = current_time - previous_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + previous_time = current_time + + # Get the time period + try: + # Division by zero + self.ideal_cycle.add(ms / self.iteration_counter) + except: + self.ideal_cycle.add(0) + + # Reset the counter + self.iteration_counter = 0 \ No newline at end of file diff --git a/exercises/static/exercises/power_tower_inspection/web-template/exercise.py b/exercises/static/exercises/power_tower_inspection/web-template/exercise.py index 4450dd975..82bf0c72a 100755 --- a/exercises/static/exercises/power_tower_inspection/web-template/exercise.py +++ b/exercises/static/exercises/power_tower_inspection/web-template/exercise.py @@ -1,3 +1,5 @@ + + #!/usr/bin/env python from __future__ import print_function @@ -5,6 +7,7 @@ from websocket_server import WebsocketServer import time import threading +import multiprocessing import subprocess import sys from datetime import datetime @@ -14,8 +17,13 @@ import rospy from std_srvs.srv import Empty +import cv2 + +from shared.value import SharedValue +from brain import BrainProcess +import queue + -from gui import GUI, ThreadGUI from hal import HAL from console import start_console, close_console @@ -25,36 +33,65 @@ class Template: # self.ideal_cycle to run an execution for at least 1 second # self.process for the current running process def __init__(self): - self.measure_thread = None - self.thread = None - self.reload = False - self.stop_brain = False - self.user_code = "" + + self.brain_process = None + self.reload = multiprocessing.Event() # Time variables - self.ideal_cycle = 80 - self.measured_cycle = 80 - self.iteration_counter = 0 + self.brain_time_cycle = SharedValue('brain_time_cycle') + self.brain_ideal_cycle = SharedValue('brain_ideal_cycle') self.real_time_factor = 0 + self.frequency_message = {'brain': '', 'gui': '', 'rtf': ''} + # GUI variables + self.gui_time_cycle = SharedValue('gui_time_cycle') + self.gui_ideal_cycle = SharedValue('gui_ideal_cycle') + self.server = None self.client = None self.host = sys.argv[1] - # Initialize the GUI, HAL and Console behind the scenes self.hal = HAL() - self.gui = GUI(self.host) + self.paused = False + # Function to parse the code # A few assumptions: # 1. The user always passes sequential and iterative codes # 2. Only a single infinite loop def parse_code(self, source_code): - sequential_code, iterative_code = self.seperate_seq_iter(source_code) - return iterative_code, sequential_code + # Check for save/load + if(source_code[:5] == "#save"): + source_code = source_code[5:] + self.save_code(source_code) + + return "", "" + + elif(source_code[:5] == "#load"): + source_code = source_code + self.load_code() + self.server.send_message(self.client, source_code) + + return "", "" - # Function to separate the iterative and sequential code + else: + sequential_code, iterative_code = self.seperate_seq_iter(source_code[6:]) + return iterative_code, sequential_code + + + # Function for saving + def save_code(self, source_code): + with open('code/academy.py', 'w') as code_file: + code_file.write(source_code) + + # Function for loading + def load_code(self): + with open('code/academy.py', 'r') as code_file: + source_code = code_file.read() + + return source_code + + # Function to seperate the iterative and sequential code def seperate_seq_iter(self, source_code): if source_code == "": return "", "" @@ -62,8 +99,8 @@ def seperate_seq_iter(self, source_code): # Search for an instance of while True infinite_loop = re.search(r'[^ ]while\s*\(\s*True\s*\)\s*:|[^ ]while\s*True\s*:|[^ ]while\s*1\s*:|[^ ]while\s*\(\s*1\s*\)\s*:', source_code) - # Separate the content inside while True and the other - # (Separating the sequential and iterative part!) + # Seperate the content inside while True and the other + # (Seperating the sequential and iterative part!) try: start_index = infinite_loop.start() iterative_code = source_code[start_index:] @@ -72,6 +109,11 @@ def seperate_seq_iter(self, source_code): # Remove while True: syntax from the code # And remove the the 4 spaces indentation before each command iterative_code = re.sub(r'[^ ]while\s*\(\s*True\s*\)\s*:|[^ ]while\s*True\s*:|[^ ]while\s*1\s*:|[^ ]while\s*\(\s*1\s*\)\s*:', '', iterative_code) + # Add newlines to match line on bug report + extra_lines = sequential_code.count('\n') + while (extra_lines >= 0): + iterative_code = '\n' + iterative_code + extra_lines -= 1 iterative_code = re.sub(r'^[ ]{4}', '', iterative_code, flags=re.M) except: @@ -80,135 +122,16 @@ def seperate_seq_iter(self, source_code): return sequential_code, iterative_code - # The process function - def process_code(self, source_code): - # Redirect the information to console - start_console() - - iterative_code, sequential_code = self.parse_code(source_code) - - # print(sequential_code) - # print(iterative_code) - - # The Python exec function - # Run the sequential part - gui_module, hal_module = self.generate_modules() - reference_environment = {"GUI": gui_module, "HAL": hal_module} - exec(sequential_code, reference_environment) - - # Run the iterative part inside template - # and keep the check for flag - while self.reload == False: - while (self.stop_brain == True): - if (self.reload == True): - break - time.sleep(0.1) - - start_time = datetime.now() - - # Execute the iterative portion - exec(iterative_code, reference_environment) - - # Template specifics to run! - finish_time = datetime.now() - dt = finish_time - start_time - ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 - - # Keep updating the iteration counter - if (iterative_code == ""): - self.iteration_counter = 0 - else: - self.iteration_counter = self.iteration_counter + 1 - - # The code should be run for atleast the target time step - # If it's less put to sleep - if (ms < self.ideal_cycle): - time.sleep((self.ideal_cycle - ms) / 1000.0) - - close_console() - print("Current Thread Joined!") - - # Function to generate the modules for use in ACE Editor - def generate_modules(self): - # Define HAL module - hal_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) - hal_module.HAL = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) - # hal_module.drone = imp.new_module("drone") - # motors# hal_module.HAL.motors = imp.new_module("motors") - - # Add HAL functions - hal_module.HAL.get_frontal_image = self.hal.get_frontal_image - hal_module.HAL.get_ventral_image = self.hal.get_ventral_image - hal_module.HAL.get_position = self.hal.get_position - hal_module.HAL.get_velocity = self.hal.get_velocity - hal_module.HAL.get_yaw_rate = self.hal.get_yaw_rate - hal_module.HAL.get_orientation = self.hal.get_orientation - hal_module.HAL.get_roll = self.hal.get_roll - hal_module.HAL.get_pitch = self.hal.get_pitch - hal_module.HAL.get_yaw = self.hal.get_yaw - hal_module.HAL.get_landed_state = self.hal.get_landed_state - hal_module.HAL.set_cmd_pos = self.hal.set_cmd_pos - hal_module.HAL.set_cmd_vel = self.hal.set_cmd_vel - hal_module.HAL.set_cmd_mix = self.hal.set_cmd_mix - hal_module.HAL.takeoff = self.hal.takeoff - hal_module.HAL.land = self.hal.land - - - - # Define GUI module - gui_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) - gui_module.GUI = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) - - # Add GUI functions - gui_module.GUI.showImage = self.gui.showImage - gui_module.GUI.showLeftImage = self.gui.showLeftImage - - # Adding modules to system - # Protip: The names should be different from - # other modules, otherwise some errors - sys.modules["HAL"] = hal_module - sys.modules["GUI"] = gui_module - - return gui_module, hal_module - - # Function to measure the frequency of iterations - def measure_frequency(self): - previous_time = datetime.now() - # An infinite loop - while True: - # Sleep for 2 seconds - time.sleep(2) - - # Measure the current time and subtract from the previous time to get real time interval - current_time = datetime.now() - dt = current_time - previous_time - ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 - previous_time = current_time - - # Get the time period - try: - # Division by zero - self.measured_cycle = ms / self.iteration_counter - except: - self.measured_cycle = 0 - - # Reset the counter - self.iteration_counter = 0 - - # Send to client - self.send_frequency_message() - - # Function to generate and send frequency messages def send_frequency_message(self): # This function generates and sends frequency measures of the brain and gui - brain_frequency = 0; gui_frequency = 0 + brain_frequency = 0;gui_frequency = 0 try: - brain_frequency = round(1000 / self.measured_cycle, 1) + brain_frequency = round(1000 / self.brain_ideal_cycle.get(), 1) except ZeroDivisionError: brain_frequency = 0 try: - gui_frequency = round(1000 / self.thread_gui.measured_cycle, 1) + gui_frequency = round(1000 / self.gui_ideal_cycle.get(), 1) except ZeroDivisionError: gui_frequency = 0 @@ -233,29 +156,32 @@ def track_stats(self): args = ["gz", "stats", "-p"] # Prints gz statistics. "-p": Output comma-separated values containing- # real-time factor (percent), simtime (sec), realtime (sec), paused (T or F) - stats_process = subprocess.Popen(args, stdout=subprocess.PIPE, bufsize=1, universal_newlines=True) + stats_process = subprocess.Popen(args, stdout=subprocess.PIPE, bufsize=0) # bufsize=1 enables line-bufferred mode (the input buffer is flushed # automatically on newlines if you would write to process.stdin ) with stats_process.stdout: - for line in iter(stats_process.stdout.readline, ''): - stats_list = [x.strip() for x in line.split(',')] - self.real_time_factor = stats_list[0] + for line in iter(stats_process.stdout.readline, b''): + stats_list = [x.strip() for x in line.split(b',')] + self.real_time_factor = stats_list[0].decode("utf-8") # Function to maintain thread execution def execute_thread(self, source_code): # Keep checking until the thread is alive # The thread will die when the coming iteration reads the flag - if self.thread is not None: - while self.thread.is_alive(): - time.sleep(0.2) + if(self.brain_process != None): + while self.brain_process.is_alive(): + pass # Turn the flag down, the iteration has successfully stopped! - self.reload = False + self.reload.clear() # New thread execution - self.thread = threading.Thread(target=self.process_code, args=[source_code]) - self.thread.start() + code = self.parse_code(source_code) + if code[0] == "" and code[1] == "": + return + + self.brain_process = BrainProcess(code, self.reload) + self.brain_process.start() self.send_code_message() - print("New Thread Started!") # Function to read and set frequency from incoming message def read_frequency_message(self, message): @@ -263,66 +189,58 @@ def read_frequency_message(self, message): # Set brain frequency frequency = float(frequency_message["brain"]) - self.ideal_cycle = 1000.0 / frequency + self.brain_time_cycle.add(1000.0 / frequency) # Set gui frequency frequency = float(frequency_message["gui"]) - self.thread_gui.ideal_cycle = 1000.0 / frequency + self.gui_time_cycle.add(1000.0 / frequency) return # The websocket function # Gets called when there is an incoming message from the client def handle(self, client, server, message): - if message[:5] == "#freq": + if(message[:5] == "#freq"): frequency_message = message[5:] self.read_frequency_message(frequency_message) time.sleep(1) + self.send_frequency_message() return - elif(message[:5] == "#ping"): time.sleep(1) self.send_ping_message() return - - elif (message[:5] == "#code"): + elif (message[:5] == "#code"): try: # Once received turn the reload flag up and send it to execute_thread function - self.user_code = message + code = message # print(repr(code)) - self.reload = True - self.execute_thread(self.user_code) + self.reload.set() + self.execute_thread(code) except: pass - - elif (message[:5] == "#rest"): + + elif (message[:5] == "#stop"): try: - self.reload = True - self.stop_brain = True - self.execute_thread(self.user_code) + self.reload.set() + self.execute_thread(code) except: pass - - elif (message[:5] == "#stop"): - self.stop_brain = True - - elif (message[:5] == "#play"): - self.stop_brain = False + self.server.send_message(self.client, "#stpd") # Function that gets called when the server is connected def connected(self, client, server): self.client = client - # Start the GUI update thread - self.thread_gui = ThreadGUI(self.gui) - self.thread_gui.start() + # Start the HAL update thread + self.hal.start_thread() - # Start the real time factor tracker thread + # Start real time factor tracker thread self.stats_thread = threading.Thread(target=self.track_stats) self.stats_thread.start() - # Start measure frequency - self.measure_thread = threading.Thread(target=self.measure_frequency) - self.measure_thread.start() + # Initialize the ping message + self.send_frequency_message() + print("After sneding freweq msg") print(client, 'connected') diff --git a/exercises/static/exercises/power_tower_inspection/web-template/gui.py b/exercises/static/exercises/power_tower_inspection/web-template/gui.py index f4fd34044..2b2e10176 100755 --- a/exercises/static/exercises/power_tower_inspection/web-template/gui.py +++ b/exercises/static/exercises/power_tower_inspection/web-template/gui.py @@ -6,15 +6,22 @@ from datetime import datetime from websocket_server import WebsocketServer import logging +import rospy +import cv2 +import sys +import numpy as np +import multiprocessing +from shared.image import SharedImage +from shared.value import SharedValue # Graphical User Interface Class class GUI: # Initialization function # The actual initialization def __init__(self, host): - t = threading.Thread(target=self.run_server) + rospy.init_node("GUI") self.payload = {'image': ''} self.left_payload = {'image': ''} self.server = None @@ -22,81 +29,47 @@ def __init__(self, host): self.host = host - # Image variables - self.image_to_be_shown = None - self.image_to_be_shown_updated = False - self.image_show_lock = threading.Lock() - - self.left_image_to_be_shown = None - self.left_image_to_be_shown_updated = False - self.left_image_show_lock = threading.Lock() - - self.acknowledge = False - self.acknowledge_lock = threading.Lock() + # Image variable host + self.shared_image = SharedImage("guifrontalimage") + self.shared_left_image = SharedImage("guiventralimage") - # Take the console object to set the same websocket and client + # Event objects for multiprocessing + self.ack_event = multiprocessing.Event() + self.cli_event = multiprocessing.Event() + + # Start server thread + t = threading.Thread(target=self.run_server) t.start() - # Explicit initialization function - # Class method, so user can call it without instantiation - @classmethod - def initGUI(cls, host): - # self.payload = {'image': '', 'shape': []} - new_instance = cls(host) - return new_instance # Function to prepare image payload # Encodes the image as a JSON string and sends through the WS def payloadImage(self): - self.image_show_lock.acquire() - image_to_be_shown_updated = self.image_to_be_shown_updated - image_to_be_shown = self.image_to_be_shown - self.image_show_lock.release() - - image = image_to_be_shown + image = self.shared_image.get() payload = {'image': '', 'shape': ''} - - if not image_to_be_shown_updated: - return payload - + shape = image.shape frame = cv2.imencode('.JPEG', image)[1] encoded_image = base64.b64encode(frame) - + payload['image'] = encoded_image.decode('utf-8') payload['shape'] = shape - - self.image_show_lock.acquire() - self.image_to_be_shown_updated = False - self.image_show_lock.release() - + return payload - + # Function to prepare image payload # Encodes the image as a JSON string and sends through the WS def payloadLeftImage(self): - self.left_image_show_lock.acquire() - left_image_to_be_shown_updated = self.left_image_to_be_shown_updated - left_image_to_be_shown = self.left_image_to_be_shown - self.left_image_show_lock.release() - - image = left_image_to_be_shown + image = self.shared_left_image.get() payload = {'image': '', 'shape': ''} - - if not left_image_to_be_shown_updated: - return payload - + shape = image.shape frame = cv2.imencode('.JPEG', image)[1] encoded_image = base64.b64encode(frame) - + payload['image'] = encoded_image.decode('utf-8') payload['shape'] = shape - - self.left_image_show_lock.acquire() - self.left_image_to_be_shown_updated = False - self.left_image_show_lock.release() - + return payload # Function for student to call @@ -117,20 +90,10 @@ def showLeftImage(self, image): # Called when a new client is received def get_client(self, client, server): self.client = client + self.cli_event.set() + + print(client, 'connected') - # Function to get value of Acknowledge - def get_acknowledge(self): - self.acknowledge_lock.acquire() - acknowledge = self.acknowledge - self.acknowledge_lock.release() - - return acknowledge - - # Function to get value of Acknowledge - def set_acknowledge(self, value): - self.acknowledge_lock.acquire() - self.acknowledge = value - self.acknowledge_lock.release() # Update the gui def update_gui(self): @@ -152,8 +115,15 @@ def update_gui(self): # Gets called when there is an incoming message from the client def get_message(self, client, server, message): # Acknowledge Message for GUI Thread - if message[:4] == "#ack": - self.set_acknowledge(True) + + if(message[:4] == "#ack"): + # Set acknowledgement flag + self.ack_event.set() + + # Function that gets called when the connected closes + def handle_close(self, client, server): + print(client, 'closed') + # Activate the server @@ -161,6 +131,7 @@ def run_server(self): self.server = WebsocketServer(port=2303, host=self.host) self.server.set_fn_new_client(self.get_client) self.server.set_fn_message_received(self.get_message) + self.server.set_fn_client_left(self.handle_close) logged = False while not logged: @@ -181,32 +152,45 @@ def reset_gui(self): # This class decouples the user thread # and the GUI update thread -class ThreadGUI: - def __init__(self, gui): - self.gui = gui +class ProcessGUI(multiprocessing.Process): + def __init__(self): + super(ProcessGUI, self).__init__() + self.host = sys.argv[1] # Time variables - self.ideal_cycle = 80 - self.measured_cycle = 80 + self.time_cycle = SharedValue("gui_time_cycle") + self.ideal_cycle = SharedValue("gui_ideal_cycle") self.iteration_counter = 0 + # Function to initialize events + def initialize_events(self): + # Events + self.ack_event = self.gui.ack_event + self.cli_event = self.gui.cli_event + self.exit_signal = multiprocessing.Event() + # Function to start the execution of threads - def start(self): + def run(self): + # Initialize GUI + self.gui = GUI(self.host) + self.initialize_events() + + # Wait for client before starting + self.cli_event.wait() self.measure_thread = threading.Thread(target=self.measure_thread) - self.thread = threading.Thread(target=self.run) + self.thread = threading.Thread(target=self.run_gui) self.measure_thread.start() self.thread.start() - print("GUI Thread Started!") + print("GUI Process Started!") + + self.exit_signal.wait() # The measuring thread to measure frequency def measure_thread(self): - while self.gui.client is None: - pass - previous_time = datetime.now() - while True: + while(True): # Sleep for 2 seconds time.sleep(2) @@ -219,32 +203,40 @@ def measure_thread(self): # Get the time period try: # Division by zero - self.measured_cycle = ms / self.iteration_counter + self.ideal_cycle.add(ms / self.iteration_counter) except: - self.measured_cycle = 0 + self.ideal_cycle.add(0) # Reset the counter self.iteration_counter = 0 # The main thread of execution - def run(self): - while self.gui.client is None: - pass - - while True: + def run_gui(self): + while(True): start_time = datetime.now() + # Send update signal self.gui.update_gui() - acknowledge_message = self.gui.get_acknowledge() - - while not acknowledge_message: - acknowledge_message = self.gui.get_acknowledge() - self.gui.set_acknowledge(False) + # Wait for acknowldege signal + self.ack_event.wait() + self.ack_event.clear() finish_time = datetime.now() self.iteration_counter = self.iteration_counter + 1 dt = finish_time - start_time ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 - if ms < self.ideal_cycle: - time.sleep((self.ideal_cycle-ms) / 1000.0) + time_cycle = self.time_cycle.get() + + if(ms < time_cycle): + time.sleep((time_cycle-ms) / 1000.0) + + self.exit_signal.set() + + # Functions to handle auxillary GUI functions + def reset_gui(self): + self.gui.reset_gui() + +if __name__ == "__main__": + gui = ProcessGUI() + gui.start() \ No newline at end of file diff --git a/exercises/static/exercises/power_tower_inspection/web-template/hal.py b/exercises/static/exercises/power_tower_inspection/web-template/hal.py old mode 100644 new mode 100755 index 4e68e2c52..17c39cd6b --- a/exercises/static/exercises/power_tower_inspection/web-template/hal.py +++ b/exercises/static/exercises/power_tower_inspection/web-template/hal.py @@ -5,6 +5,8 @@ from datetime import datetime from drone_wrapper import DroneWrapper +from shared.image import SharedImage +from shared.value import SharedValue # Hardware Abstraction Layer class HAL: @@ -13,74 +15,166 @@ class HAL: def __init__(self): rospy.init_node("HAL") - + + self.shared_frontal_image = SharedImage("halfrontalimage") + self.shared_ventral_image = SharedImage("halventralimage") + self.shared_x = SharedValue("x") + self.shared_y = SharedValue("y") + self.shared_z = SharedValue("z") + self.shared_takeoff_z = SharedValue("sharedtakeoffz") + self.shared_az = SharedValue("az") + self.shared_azt = SharedValue("azt") + self.shared_vx = SharedValue("vx") + self.shared_vy = SharedValue("vy") + self.shared_vz = SharedValue("vz") + self.shared_landed_state = SharedValue("landedstate") + self.shared_position = SharedValue("position") + self.shared_velocity = SharedValue("velocity") + self.shared_orientation = SharedValue("orientation") + self.shared_roll = SharedValue("roll") + self.shared_pitch = SharedValue("pitch") + self.shared_yaw = SharedValue("yaw") + self.shared_yaw_rate = SharedValue("yawrate") + self.image = None - self.drone = DroneWrapper(name="rqt") - + self.drone = DroneWrapper(name="rqt",ns="/iris/") + + # Update thread + self.thread = ThreadHAL(self.update_hal) # Explicit initialization functions # Class method, so user can call it without instantiation - @classmethod - def initRobot(cls): - new_instance = cls() - return new_instance + + + # Function to start the update thread + def start_thread(self): + self.thread.start() # Get Image from ROS Driver Camera def get_frontal_image(self): image = self.drone.get_frontal_image() image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) - return image_rgb + self.shared_frontal_image.add(image_rgb) def get_ventral_image(self): image = self.drone.get_ventral_image() image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) - return image_rgb + self.shared_ventral_image.add(image_rgb) def get_position(self): pos = self.drone.get_position() - return pos + self.shared_position.add(pos,type_name="list") def get_velocity(self): vel = self.drone.get_velocity() - return vel + self.shared_velocity.add(vel ,type_name="list") def get_yaw_rate(self): yaw_rate = self.drone.get_yaw_rate() - return yaw_rate + self.shared_yaw_rate.add(yaw_rate) def get_orientation(self): orientation = self.drone.get_orientation() - return orientation + self.shared_orientation.add(orientation ,type_name="list") def get_roll(self): roll = self.drone.get_roll() - return roll + self.shared_roll.add(roll) def get_pitch(self): pitch = self.drone.get_pitch() - return pitch + self.shared_pitch.add(pitch) def get_yaw(self): yaw = self.drone.get_yaw() - return yaw + self.shared_yaw.add(yaw) def get_landed_state(self): state = self.drone.get_landed_state() - return state + self.shared_landed_state.add(state) + + def set_cmd_pos(self): + x = self.shared_x.get() + y = self.shared_y.get() + z = self.shared_z.get() + az = self.shared_az.get() - def set_cmd_pos(self, x, y, z, az): self.drone.set_cmd_pos(x, y, z, az) - def set_cmd_vel(self, vx, vy, vz, az): + def set_cmd_vel(self): + vx = self.shared_vx.get() + vy = self.shared_vy.get() + vz = self.shared_vz.get() + az = self.shared_azt.get() self.drone.set_cmd_vel(vx, vy, vz, az) - def set_cmd_mix(self, vx, vy, z, az): + def set_cmd_mix(self): + vx = self.shared_vx.get() + vy = self.shared_vy.get() + z = self.shared_z.get() + az = self.shared_azt.get() self.drone.set_cmd_mix(vx, vy, z, az) - def takeoff(self, h=5): + def takeoff(self): + h = self.shared_takeoff_z.get() self.drone.takeoff(h) def land(self): self.drone.land() - + def update_hal(self): + self.get_frontal_image() + self.get_ventral_image() + self.get_position() + self.get_velocity() + self.get_yaw_rate() + self.get_orientation() + self.get_pitch() + self.get_roll() + self.get_yaw() + self.get_landed_state() + self.set_cmd_pos() + self.set_cmd_vel() + self.set_cmd_mix() + + # Destructor function to close all fds + def __del__(self): + self.shared_frontal_image.close() + self.shared_ventral_image.close() + self.shared_x.close() + self.shared_y.close() + self.shared_z.close() + self.shared_takeoff_z.close() + self.shared_az.close() + self.shared_azt.close() + self.shared_vx.close() + self.shared_vy.close() + self.shared_vz.close() + self.shared_landed_state.close() + self.shared_position.close() + self.shared_velocity.close() + self.shared_orientation.close() + self.shared_roll.close() + self.shared_pitch.close() + self.shared_yaw.close() + self.shared_yaw_rate.close() + +class ThreadHAL(threading.Thread): + def __init__(self, update_function): + super(ThreadHAL, self).__init__() + self.time_cycle = 80 + self.update_function = update_function + + def run(self): + while(True): + start_time = datetime.now() + + self.update_function() + + finish_time = datetime.now() + + dt = finish_time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + + if(ms < self.time_cycle): + time.sleep((self.time_cycle - ms) / 1000.0) \ No newline at end of file diff --git a/exercises/static/exercises/power_tower_inspection/web-template/launch/gazebo.launch b/exercises/static/exercises/power_tower_inspection/web-template/launch/gazebo.launch deleted file mode 100644 index f8164e32f..000000000 --- a/exercises/static/exercises/power_tower_inspection/web-template/launch/gazebo.launch +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/exercises/static/exercises/power_tower_inspection/web-template/launch/launch.py b/exercises/static/exercises/power_tower_inspection/web-template/launch/launch.py deleted file mode 100644 index e37e3f76f..000000000 --- a/exercises/static/exercises/power_tower_inspection/web-template/launch/launch.py +++ /dev/null @@ -1,131 +0,0 @@ -#!/usr/bin/env python3 - -import stat -import rospy -from os import lstat -from subprocess import Popen, PIPE - - -DRI_PATH = "/dev/dri/card0" -EXERCISE = "power_tower_inspection" -TIMEOUT = 30 -MAX_ATTEMPT = 2 - - -# Function to check if a device exists -def check_device(device_path): - try: - return stat.S_ISCHR(lstat(device_path)[stat.ST_MODE]) - except: - return False - - -# Spawn new process -def spawn_process(args, insert_vglrun=False): - if insert_vglrun: - args.insert(0, "vglrun") - process = Popen(args, stdout=PIPE, bufsize=1, universal_newlines=True) - return process - - -class Test(): - def gazebo(self): - rospy.logwarn("[GAZEBO] Launching") - try: - rospy.wait_for_service("/gazebo/get_model_properties", TIMEOUT) - return True - except rospy.ROSException: - return False - - def px4(self): - rospy.logwarn("[PX4-SITL] Launching") - start_time = rospy.get_time() - args = ["./PX4-Autopilot/build/px4_sitl_default/bin/px4-commander","--instance", "0", "check"] - while rospy.get_time() - start_time < TIMEOUT: - process = spawn_process(args, insert_vglrun=False) - with process.stdout: - for line in iter(process.stdout.readline, ''): - if ("Prearm check: OK" in line): - return True - rospy.sleep(2) - return False - - def mavros(self, ns=""): - rospy.logwarn("[MAVROS] Launching") - try: - rospy.wait_for_service(ns + "/mavros/cmd/arming", TIMEOUT) - return True - except rospy.ROSException: - return False - - -class Launch(): - def __init__(self): - self.test = Test() - self.acceleration_enabled = check_device(DRI_PATH) - - # Start roscore - args = ["/opt/ros/noetic/bin/roscore"] - spawn_process(args, insert_vglrun=False) - - rospy.init_node("launch", anonymous=True) - - def start(self): - ######## LAUNCH GAZEBO ######## - args = ["/opt/ros/noetic/bin/roslaunch", - "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/gazebo.launch", - "--wait", - "--log" - ] - - attempt = 1 - while True: - spawn_process(args, insert_vglrun=self.acceleration_enabled) - if self.test.gazebo() == True: - break - if attempt == MAX_ATTEMPT: - rospy.logerr("[GAZEBO] Launch Failed") - return - attempt = attempt + 1 - - - ######## LAUNCH PX4 ######## - args = ["/opt/ros/noetic/bin/roslaunch", - "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/px4.launch", - "--log" - ] - - attempt = 1 - while True: - spawn_process(args, insert_vglrun=self.acceleration_enabled) - if self.test.px4() == True: - break - if attempt == MAX_ATTEMPT: - rospy.logerr("[PX4] Launch Failed") - return - attempt = attempt + 1 - - - ######## LAUNCH MAVROS ######## - args = ["/opt/ros/noetic/bin/roslaunch", - "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/mavros.launch", - "--log" - ] - - attempt = 1 - while True: - spawn_process(args, insert_vglrun=self.acceleration_enabled) - if self.test.mavros() == True: - break - if attempt == MAX_ATTEMPT: - rospy.logerr("[MAVROS] Launch Failed") - return - attempt = attempt + 1 - - -if __name__ == "__main__": - launch = Launch() - launch.start() - - with open("/drones_launch.log", "w") as f: - f.write("success") diff --git a/exercises/static/exercises/power_tower_inspection/web-template/launch/mavros.launch b/exercises/static/exercises/power_tower_inspection/web-template/launch/mavros.launch deleted file mode 100644 index b899c0ec1..000000000 --- a/exercises/static/exercises/power_tower_inspection/web-template/launch/mavros.launch +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/exercises/static/exercises/power_tower_inspection/web-template/launch/power_tower_inspection.launch b/exercises/static/exercises/power_tower_inspection/web-template/launch/power_tower_inspection.launch new file mode 100755 index 000000000..99dc96729 --- /dev/null +++ b/exercises/static/exercises/power_tower_inspection/web-template/launch/power_tower_inspection.launch @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/exercises/static/exercises/power_tower_inspection/web-template/launch/px4.launch b/exercises/static/exercises/power_tower_inspection/web-template/launch/px4.launch deleted file mode 100644 index 7aebd7951..000000000 --- a/exercises/static/exercises/power_tower_inspection/web-template/launch/px4.launch +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/exercises/static/exercises/power_tower_inspection/web-template/shared/__init__.py b/exercises/static/exercises/power_tower_inspection/web-template/shared/__init__.py new file mode 100755 index 000000000..e69de29bb diff --git a/exercises/static/exercises/power_tower_inspection/web-template/shared/image.py b/exercises/static/exercises/power_tower_inspection/web-template/shared/image.py new file mode 100755 index 000000000..de5c9f9d6 --- /dev/null +++ b/exercises/static/exercises/power_tower_inspection/web-template/shared/image.py @@ -0,0 +1,109 @@ +import numpy as np +import mmap +from posix_ipc import Semaphore, O_CREX, ExistentialError, O_CREAT, SharedMemory, unlink_shared_memory +from ctypes import sizeof, memmove, addressof, create_string_buffer +from shared.structure_img import MD + +# Probably, using self variables gives errors with memmove +# Therefore, a global variable for utility +md_buf = create_string_buffer(sizeof(MD)) + +class SharedImage: + def __init__(self, name): + # Initialize variables for memory regions and buffers and Semaphore + self.shm_buf = None; self.shm_region = None + self.md_buf = None; self.md_region = None + self.image_lock = None + + self.shm_name = name; self.md_name = name + "-meta" + self.image_lock_name = name + + # Initialize or retreive metadata memory region + try: + self.md_region = SharedMemory(self.md_name) + self.md_buf = mmap.mmap(self.md_region.fd, sizeof(MD)) + self.md_region.close_fd() + except ExistentialError: + self.md_region = SharedMemory(self.md_name, O_CREAT, size=sizeof(MD)) + self.md_buf = mmap.mmap(self.md_region.fd, self.md_region.size) + self.md_region.close_fd() + + # Initialize or retreive Semaphore + try: + self.image_lock = Semaphore(self.image_lock_name, O_CREX) + except ExistentialError: + image_lock = Semaphore(self.image_lock_name, O_CREAT) + image_lock.unlink() + self.image_lock = Semaphore(self.image_lock_name, O_CREX) + + self.image_lock.release() + + # Get the shared image + def get(self): + # Define metadata + metadata = MD() + + # Get metadata from the shared region + self.image_lock.acquire() + md_buf[:] = self.md_buf + memmove(addressof(metadata), md_buf, sizeof(metadata)) + self.image_lock.release() + + # Try to retreive the image from shm_buffer + # Otherwise return a zero image + try: + self.shm_region = SharedMemory(self.shm_name) + self.shm_buf = mmap.mmap(self.shm_region.fd, metadata.size) + self.shm_region.close_fd() + + self.image_lock.acquire() + image = np.ndarray(shape=(metadata.shape_0, metadata.shape_1, metadata.shape_2), + dtype='uint8', buffer=self.shm_buf) + self.image_lock.release() + + # Check for a None image + if(image.size == 0): + image = np.zeros((3, 3, 3), np.uint8) + + except ExistentialError: + image = np.zeros((3, 3, 3), np.uint8) + + return image + + # Add the shared image + def add(self, image): + try: + # Get byte size of the image + byte_size = image.nbytes + + # Get the shared memory buffer to read from + if not self.shm_region: + self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=byte_size) + self.shm_buf = mmap.mmap(self.shm_region.fd, byte_size) + self.shm_region.close_fd() + + # Generate meta data + metadata = MD(image.shape[0], image.shape[1], image.shape[2], byte_size) + + # Send the meta data and image to shared regions + self.image_lock.acquire() + memmove(md_buf, addressof(metadata), sizeof(metadata)) + self.md_buf[:] = md_buf[:] + self.shm_buf[:] = image.tobytes() + self.image_lock.release() + except: + pass + + # Destructor function to unlink and disconnect + def close(self): + self.image_lock.acquire() + self.md_buf.close() + + try: + unlink_shared_memory(self.md_name) + unlink_shared_memory(self.shm_name) + except ExistentialError: + pass + + self.image_lock.release() + self.image_lock.close() diff --git a/exercises/static/exercises/power_tower_inspection/web-template/shared/structure_img.py b/exercises/static/exercises/power_tower_inspection/web-template/shared/structure_img.py new file mode 100755 index 000000000..ae40b3707 --- /dev/null +++ b/exercises/static/exercises/power_tower_inspection/web-template/shared/structure_img.py @@ -0,0 +1,9 @@ +from ctypes import Structure, c_int32, c_int64 + +class MD(Structure): + _fields_ = [ + ('shape_0', c_int32), + ('shape_1', c_int32), + ('shape_2', c_int32), + ('size', c_int64) + ] \ No newline at end of file diff --git a/exercises/static/exercises/power_tower_inspection/web-template/shared/value.py b/exercises/static/exercises/power_tower_inspection/web-template/shared/value.py new file mode 100755 index 000000000..e7bfad8a2 --- /dev/null +++ b/exercises/static/exercises/power_tower_inspection/web-template/shared/value.py @@ -0,0 +1,93 @@ +import numpy as np +import mmap +from posix_ipc import Semaphore, O_CREX, ExistentialError, O_CREAT, SharedMemory, unlink_shared_memory +from ctypes import sizeof, memmove, addressof, create_string_buffer, c_float +import struct + +class SharedValue: + def __init__(self, name): + # Initialize varaibles for memory regions and buffers and Semaphore + self.shm_buf = None; self.shm_region = None + self.value_lock = None + + self.shm_name = name; self.value_lock_name = name + + # Initialize or retreive Semaphore + try: + self.value_lock = Semaphore(self.value_lock_name, O_CREX) + except ExistentialError: + value_lock = Semaphore(self.value_lock_name, O_CREAT) + value_lock.unlink() + self.value_lock = Semaphore(self.value_lock_name, O_CREX) + + self.value_lock.release() + + # Get the shared value + def get(self, type_name= "value"): + # Retreive the data from buffer + if type_name=="value": + try: + self.shm_region = SharedMemory(self.shm_name) + self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) + self.shm_region.close_fd() + except ExistentialError: + self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=sizeof(c_float)) + self.shm_buf = mmap.mmap(self.shm_region.fd, self.shm_region.size) + self.shm_region.close_fd() + self.value_lock.acquire() + value = struct.unpack('f', self.shm_buf)[0] + self.value_lock.release() + + return value + elif type_name=="list": + self.shm_region = SharedMemory(self.shm_name) + self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) + self.shm_region.close_fd() + self.value_lock.acquire() + array_val = np.ndarray(shape=(3,), + dtype='float32', buffer=self.shm_buf) + self.value_lock.release() + + return array_val + + else: + print("missing argument for return type") + + + # Add the shared value + def add(self, value, type_name= "value"): + # Send the data to shared regions + if type_name=="value": + try: + self.shm_region = SharedMemory(self.shm_name) + self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) + self.shm_region.close_fd() + except ExistentialError: + self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=sizeof(c_float)) + self.shm_buf = mmap.mmap(self.shm_region.fd, self.shm_region.size) + self.shm_region.close_fd() + + self.value_lock.acquire() + self.shm_buf[:] = struct.pack('f', value) + self.value_lock.release() + elif type_name=="list": + byte_size = value.nbytes + self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=byte_size) + self.shm_buf = mmap.mmap(self.shm_region.fd, byte_size) + self.shm_region.close_fd() + self.value_lock.acquire() + self.shm_buf[:] = value.tobytes() + self.value_lock.release() + + # Destructor function to unlink and disconnect + def close(self): + self.value_lock.acquire() + self.shm_buf.close() + + try: + unlink_shared_memory(self.shm_name) + except ExistentialError: + pass + + self.value_lock.release() + self.value_lock.close() diff --git a/exercises/static/exercises/power_tower_inspection/web-template/user_functions.py b/exercises/static/exercises/power_tower_inspection/web-template/user_functions.py new file mode 100755 index 000000000..d741601fc --- /dev/null +++ b/exercises/static/exercises/power_tower_inspection/web-template/user_functions.py @@ -0,0 +1,117 @@ +from shared.image import SharedImage +from shared.value import SharedValue +import numpy as np +import cv2 + +# Define HAL functions +class HALFunctions: + def __init__(self): + # Initialize image variable + self.shared_frontal_image = SharedImage("halfrontalimage") + self.shared_ventral_image = SharedImage("halventralimage") + self.shared_x = SharedValue("x") + self.shared_y = SharedValue("y") + self.shared_z = SharedValue("z") + self.shared_takeoff_z = SharedValue("sharedtakeoffz") + self.shared_az = SharedValue("az") + self.shared_azt = SharedValue("azt") + self.shared_vx = SharedValue("vx") + self.shared_vy = SharedValue("vy") + self.shared_vz = SharedValue("vz") + self.shared_landed_state = SharedValue("landedstate") + self.shared_position = SharedValue("position") + self.shared_velocity = SharedValue("velocity") + self.shared_orientation = SharedValue("orientation") + self.shared_roll = SharedValue("roll") + self.shared_pitch = SharedValue("pitch") + self.shared_yaw = SharedValue("yaw") + self.shared_yaw_rate = SharedValue("yawrate") + + + # Get image function + def get_frontal_image(self): + image = self.shared_frontal_image.get() + return image + + # Get left image function + def get_ventral_image(self): + image = self.shared_ventral_image.get() + return image + + def takeoff(self, height): + self.shared_takeoff_z.add(height) + + def land(self): + pass + + def set_cmd_pos(self, x, y , z, az): + self.shared_x.add(x) + self.shared_y.add(y) + self.shared_z.add(z) + self.shared_az.add(az) + + def set_cmd_vel(self, vx, vy, vz, az): + self.shared_vx.add(vx) + self.shared_vy.add(vy) + self.shared_vz.add(vz) + self.shared_azt.add(az) + + def set_cmd_mix(self, vx, vy, z, az): + self.shared_vx.add(vx) + self.shared_vy.add(vy) + self.shared_vz.add(z) + self.shared_azt.add(az) + + + def get_position(self): + position = self.shared_position.get(type_name = "list") + return position + + def get_velocity(self): + velocity = self.shared_velocity.get(type_name = "list") + return velocity + + def get_yaw_rate(self): + yaw_rate = self.shared_yaw_rate.get(type_name = "value") + return yaw_rate + + def get_orientation(self): + orientation = self.shared_orientation.get(type_name = "list") + return orientation + + def get_roll(self): + roll = self.shared_roll.get(type_name = "value") + return roll + + def get_pitch(self): + pitch = self.shared_pitch.get(type_name = "value") + return pitch + + def get_yaw(self): + yaw = self.shared_yaw.get(type_name = "value") + return yaw + + def get_landed_state(self): + landed_state = self.shared_landed_state.get(type_name = "value") + return landed_state + +# Define GUI functions +class GUIFunctions: + def __init__(self): + # Initialize image variable + self.shared_image = SharedImage("guifrontalimage") + self.shared_left_image = SharedImage("guiventralimage") + + # Show image function + def showImage(self, image): + # Reshape to 3 channel if it has only 1 in order to display it + if (len(image.shape) < 3): + image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB) + self.shared_image.add(image) + + # Show left image function + def showLeftImage(self, image): + # Reshape to 3 channel if it has only 1 in order to display it + if (len(image.shape) < 3): + image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB) + self.shared_left_image.add(image) \ No newline at end of file diff --git a/scripts/instructions.json b/scripts/instructions.json index d15d965b8..d163ebd76 100644 --- a/scripts/instructions.json +++ b/scripts/instructions.json @@ -136,7 +136,8 @@ }, "power_tower_inspection": { "gazebo_path": "/RoboticsAcademy/exercises/power_tower_inspection/web-template/launch", - "instructions_ros": ["python3 ./RoboticsAcademy/exercises/power_tower_inspection/web-template/launch/launch.py"], - "instructions_host": "python3 /RoboticsAcademy/exercises/power_tower_inspection/web-template/exercise.py 0.0.0.0" + "instructions_ros": ["/opt/ros/noetic/bin/roslaunch ./RoboticsAcademy/exercises/power_tower_inspection/web-template/launch/power_tower_inspection.launch"], + "instructions_host": "python3 /RoboticsAcademy/exercises/power_tower_inspection/web-template/exercise.py 0.0.0.0", + "instructions_gui": "python3 /RoboticsAcademy/exercises/power_tower_inspection/web-template/gui.py 0.0.0.0 {}" } } From 60205443c2ef4154638640ab1ca3cb4816f6fbc0 Mon Sep 17 00:00:00 2001 From: RUFFY-369 Date: Sun, 11 Sep 2022 02:44:33 +0530 Subject: [PATCH 14/42] frontend power tower upd in multiprocesing full test --- .../web-template/power_tower_inspection.world | 78 ++++++++++--------- 1 file changed, 40 insertions(+), 38 deletions(-) diff --git a/exercises/static/exercises/power_tower_inspection/web-template/power_tower_inspection.world b/exercises/static/exercises/power_tower_inspection/web-template/power_tower_inspection.world index 5697153c1..1c9e346ce 100644 --- a/exercises/static/exercises/power_tower_inspection/web-template/power_tower_inspection.world +++ b/exercises/static/exercises/power_tower_inspection/web-template/power_tower_inspection.world @@ -2,7 +2,7 @@ - + -4.70385 10.895 16.2659 -0 0.921795 -1.12701 orbit @@ -38,21 +38,21 @@ model://power_tower_danube_wires - -41 -4 0.05 0 0 + -33.147 -4 0.05 0 -0 0 power_tower_wires0 model://power_tower_danube - -1 -4 0.05 0 0 + -1 -4 0.05 0 -0 0 power_tower1 model://power_tower_danube_wires - -1 -4 0.05 0 0 + -1 -4 0.05 0 -0 0 power_tower_wires1 @@ -60,28 +60,28 @@ model://power_tower_danube - 39 -4 0.05 0 0 + 31.204 -3.99821 0.05 0 -0 0 power_tower2 model://power_tower_danube_wires - 39 -4 0.05 0 0 + 33.2811 -4 0.05 0 -0 0 power_tower_wires2 model://power_tower_danube - 79 -4 0.05 0 0 + 65.4438 -3.98967 0.05 0 -0 0 power_tower3 model://power_tower_danube_wires - 79 -4 0.05 0 0 + 67.3397 -4 0.05 0 -0 0 power_tower_wires3 @@ -90,7 +90,7 @@ model://rust_defect_01 - -2.31 -4 3.79 0 0.17 + -3.60744 -4 7.56915 0 0.133065 0 rust_def1_tower1 @@ -98,7 +98,7 @@ model://rust_defect_01 - 0.232054 -4.00614 3.96187 0 -0.15255 0 + 1.49613 -4.03526 7.70739 0 -0.15255 0 rust_def1_tower1_opp @@ -106,7 +106,7 @@ model://rust_defect_02 - -1.80605 -4.40198 14.7671 0 0.0 + -2.95486 -4.40198 12.7216 0 -0 0 rust_def2_tower1 @@ -114,7 +114,7 @@ model://rust_defect_02 - -0.438877 -4.04055 14.7724 0 0.0 0 + 0.753853 -4.97547 12.7523 0 -0.157217 0 rust_def2_tower1_opp @@ -122,7 +122,7 @@ model://rust_defect_02 - -1.81251 -3.67691 14.7727 0 0.0 + -2.95219 -3.67691 12.7055 0 -0 0 rust_def3_tower1 @@ -130,7 +130,7 @@ model://rust_defect_02 - -0.435477 -3.59453 14.7738 0 0.0 0 + 0.748518 -3.59453 12.7794 0 -0.13313 0 rust_def3_tower1_opp @@ -138,7 +138,7 @@ model://rust_defect_03 - -1.59 -4.33479 20.438 0 0.17 + -2.84033 -5.48227 20.438 0 -0 0 rust_def4_tower1 @@ -146,7 +146,7 @@ model://rust_defect_03 - -0.577392 -3.56405 19.08 0 -0.028 0 + 0.589758 -2.4916 19.1127 0 -0.028 0 rust_def4_tower1_opp @@ -154,7 +154,7 @@ model://rust_defect_03 - -1.73 -3.51 19.1255 0 0.17 + -2.83395 -2.47165 19.6886 0 -0 0 rust_def5_tower1 @@ -163,7 +163,7 @@ model://rust_defect_01 - 37.69 -4 3.79 0 0.17 + 28.5882 -3.99181 7.62227 0 0.13829 0 rust_def1_tower2 @@ -171,7 +171,7 @@ model://rust_defect_01 - 40.232054 -4.00614 3.96187 0 -0.15255 0 + 33.7073 -4.01344 7.68608 0 -0.15255 0 rust_def1_tower2_opp @@ -179,7 +179,7 @@ model://rust_defect_03 - 38.19395 -4.40198 14.7671 0 0.0 + 29.2468 -4.78414 12.7205 0 -0 0 rust_def2_tower2 @@ -187,7 +187,7 @@ model://rust_defect_02 - 39.561123 -4.04055 14.7724 0 0.0 0 + 32.9873 -5.06777 12.7772 0 -0 0 rust_def2_tower2_opp @@ -195,7 +195,7 @@ model://rust_defect_03 - 38.18749 -3.67691 14.7727 0 0.0 + 29.2468 -3.38453 12.7361 0 -0 0 rust_def3_tower2 @@ -203,7 +203,7 @@ model://rust_defect_02 - 39.564523 -3.59453 14.7738 0 0.0 0 + 32.985 -3.20346 12.7921 0 -0 0 rust_def3_tower2_opp @@ -211,7 +211,7 @@ model://rust_defect_02 - 38.41 -4.33479 20.438 0 0.17 + 32.6964 -5.46176 20.438 0 -0 0 rust_def4_tower2 @@ -219,98 +219,100 @@ model://rust_defect_03 - 39.422608 -3.56405 19.08 0 -0.028 0 + 29.3463 -5.48466 18.7967 0 -0.028 0 rust_def4_tower2_opp model://rust_defect_wire - -7.72635 -0.241255 12.3353 -0.004615 -0.109891 -0.001474 + -9.07685 3.51833 24.335 -0.004786 -0.289138 -0.000616 rust_def_wire1 model://rust_defect_wire - -11.058 1.31607 16.5176 -0.004602 -0.082412 -0.001602 + -11.9227 -11.4911 23.6347 -0.004676 -0.196566 -0.001068 rust_def_wire2 model://rust_defect_wire - 19.8548 2.72211 11.5241 -0.004587 -0.002014 -0.001971 + 19.8116 3.52585 23.5406 -0.004686 -0.206067 -0.001021 rust_def_wire3 model://rust_defect_wire - 30.6961 -7.7434 12.1612 -0.004616 -0.113092 -0.001459 + 11.7343 -11.4951 23.042 -0.004606 0.090845 -0.002398 rust_def_wire4 model://rust_defect_wire - -2.37899 -0.352206 13.0936 -0.004633 -0.140673 -0.001331 + -3.88003 3.2803 26.1377 -0.004633 -0.140673 -0.001331 rust_def_spring_tower1 + 2 2 2 model://rust_defect_wire - 37.8828 2.61147 13.101 -0.004636 -0.145252 -0.001309 + 28.5276 3.75649 26.1978 -0.004636 -0.145252 -0.001309 rust_def_spring_tower2 + 2 2 2 model://nest1 - -1.3 1.56 12.55 0 0 0 + -0.939773 2.42735 25.7108 0 -0 0 nest1 model://egg - -1.3 1.5 13.3197 0 -0 1.57 + -0.87535 2.40213 26.4432 0 -0 1.57 egg1 model://bird - -1.06401 1.28978 13.4012 0 -0 -2.07145 + -1.12582 2.0562 26.5533 0 0 -2.07145 bird model://nest1 - 14.86 5.48 2.51339 0 0 0 + 14.86 5.48 2.51339 0 -0 0 nest2 model://egg - 14.86 5.42 3.25989 0 0 0 + 14.86 5.42 3.25989 0 -0 0 egg2 model://electrical_box - 15 5 0 0 0 1.57 + 15 5 0 0 -0 1.57 electrical_box1 model://electrical_box - 55 5 0 0 0 1.57 + 55 5 0 0 -0 1.57 electrical_box2 From 4b70e2ca5e0ded734fdb786696acb6fe7daeb3a4 Mon Sep 17 00:00:00 2001 From: RUFFY-369 Date: Sun, 11 Sep 2022 03:15:47 +0530 Subject: [PATCH 15/42] multiprocessing with rotors fully in package delivery ex --- .../package_delivery/web-template/brain.py | 169 +++++++++++ .../package_delivery/web-template/exercise.py | 272 ++++++------------ .../package_delivery/web-template/gui.py | 180 ++++++------ .../package_delivery/web-template/hal.py | 149 ++++++++-- .../web-template/launch/gazebo.launch | 26 -- .../web-template/launch/launch.py | 131 --------- .../web-template/launch/mavros.launch | 13 - .../launch/package_delivery.launch | 61 ++++ .../web-template/launch/px4.launch | 19 -- .../web-template/package_delivery.yaml | 1 - .../web-template/shared/__init__.py | 0 .../web-template/shared/image.py | 109 +++++++ .../web-template/{ => shared}/magnet.py | 0 .../web-template/shared/structure_img.py | 9 + .../web-template/shared/value.py | 93 ++++++ .../web-template/user_functions.py | 128 +++++++++ scripts/instructions.json | 5 +- 17 files changed, 878 insertions(+), 487 deletions(-) create mode 100755 exercises/static/exercises/package_delivery/web-template/brain.py mode change 100644 => 100755 exercises/static/exercises/package_delivery/web-template/hal.py delete mode 100644 exercises/static/exercises/package_delivery/web-template/launch/gazebo.launch delete mode 100644 exercises/static/exercises/package_delivery/web-template/launch/launch.py delete mode 100644 exercises/static/exercises/package_delivery/web-template/launch/mavros.launch create mode 100755 exercises/static/exercises/package_delivery/web-template/launch/package_delivery.launch delete mode 100644 exercises/static/exercises/package_delivery/web-template/launch/px4.launch delete mode 100644 exercises/static/exercises/package_delivery/web-template/package_delivery.yaml create mode 100755 exercises/static/exercises/package_delivery/web-template/shared/__init__.py create mode 100755 exercises/static/exercises/package_delivery/web-template/shared/image.py rename exercises/static/exercises/package_delivery/web-template/{ => shared}/magnet.py (100%) create mode 100755 exercises/static/exercises/package_delivery/web-template/shared/structure_img.py create mode 100755 exercises/static/exercises/package_delivery/web-template/shared/value.py create mode 100755 exercises/static/exercises/package_delivery/web-template/user_functions.py diff --git a/exercises/static/exercises/package_delivery/web-template/brain.py b/exercises/static/exercises/package_delivery/web-template/brain.py new file mode 100755 index 000000000..71994e08f --- /dev/null +++ b/exercises/static/exercises/package_delivery/web-template/brain.py @@ -0,0 +1,169 @@ +import time +import threading +import multiprocessing +import sys +from datetime import datetime +import re +import json +import importlib + +import rospy +from std_srvs.srv import Empty +import cv2 + +from user_functions import GUIFunctions, HALFunctions +from console import start_console, close_console + +from shared.value import SharedValue + +# The brain process class +class BrainProcess(multiprocessing.Process): + def __init__(self, code, exit_signal): + super(BrainProcess, self).__init__() + + # Initialize exit signal + self.exit_signal = exit_signal + + # Function definitions for users to use + self.hal = HALFunctions() + self.gui = GUIFunctions() + + # Time variables + self.time_cycle = SharedValue('brain_time_cycle') + self.ideal_cycle = SharedValue('brain_ideal_cycle') + self.iteration_counter = 0 + + # Get the sequential and iterative code + # Something wrong over here! The code is reversing + # Found a solution but could not find the reason for this (parse_code function's return line of exercise.py is the reason) + self.sequential_code = code[1] + self.iterative_code = code[0] + + # Function to run to start the process + def run(self): + # Two threads for running and measuring + self.measure_thread = threading.Thread(target=self.measure_frequency) + self.thread = threading.Thread(target=self.process_code) + + self.measure_thread.start() + self.thread.start() + + print("Brain Process Started!") + + self.exit_signal.wait() + + # The process function + def process_code(self): + # Redirect information to console + start_console() + + # Reference Environment for the exec() function + iterative_code, sequential_code = self.iterative_code, self.sequential_code + + # The Python exec function + # Run the sequential part + gui_module, hal_module = self.generate_modules() + if sequential_code != "": + reference_environment = {"GUI": gui_module, "HAL": hal_module} + exec(sequential_code, reference_environment) + + # Run the iterative part inside template + # and keep the check for flag + while not self.exit_signal.is_set(): + start_time = datetime.now() + + # Execute the iterative portion + if iterative_code != "": + exec(iterative_code, reference_environment) + + # Template specifics to run! + finish_time = datetime.now() + dt = finish_time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + + # Keep updating the iteration counter + if(iterative_code == ""): + self.iteration_counter = 0 + else: + self.iteration_counter = self.iteration_counter + 1 + + # The code should be run for atleast the target time step + # If it's less put to sleep + # If it's more no problem as such, but we can change it! + time_cycle = self.time_cycle.get() + + if(ms < time_cycle): + time.sleep((time_cycle - ms) / 1000.0) + + close_console() + print("Current Thread Joined!") + + + # Function to generate the modules for use in ACE Editor + def generate_modules(self): + # Define HAL module + hal_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) + hal_module.HAL = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) + hal_module.HAL.motors = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("motors", None)) + + # Add HAL functions + hal_module.HAL.get_frontal_image = self.hal.get_frontal_image + hal_module.HAL.get_ventral_image = self.hal.get_ventral_image + hal_module.HAL.takeoff = self.hal.takeoff + hal_module.HAL.land = self.hal.land + hal_module.HAL.set_cmd_pos = self.hal.set_cmd_pos + hal_module.HAL.set_cmd_vel = self.hal.set_cmd_vel + hal_module.HAL.set_cmd_mix = self.hal.set_cmd_mix + hal_module.HAL.get_position = self.hal.get_position + hal_module.HAL.get_velocity = self.hal.get_velocity + hal_module.HAL.get_orientation = self.hal.get_orientation + hal_module.HAL.get_roll = self.hal.get_roll + hal_module.HAL.get_pitch = self.hal.get_pitch + hal_module.HAL.get_yaw = self.hal.get_yaw + hal_module.HAL.get_landed_state = self.hal.get_landed_state + hal_module.HAL.get_yaw_rate = self.hal.get_yaw_rate + hal_module.HAL.set_cmd_pick = self.hal.set_cmd_pick + hal_module.HAL.set_cmd_drop = self.hal.set_cmd_drop + hal_module.HAL.get_pkg_state = self.hal.get_pkg_state + + + + # Define GUI module + gui_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) + gui_module.GUI = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) + + # Add GUI functions + gui_module.GUI.showImage = self.gui.showImage + gui_module.GUI.showLeftImage = self.gui.showLeftImage + + # Adding modules to system + # Protip: The names should be different from + # other modules, otherwise some errors + sys.modules["HAL"] = hal_module + sys.modules["GUI"] = gui_module + + return gui_module, hal_module + + # Function to measure the frequency of iterations + def measure_frequency(self): + previous_time = datetime.now() + # An infinite loop + while not self.exit_signal.is_set(): + # Sleep for 2 seconds + time.sleep(2) + + # Measure the current time and subtract from the previous time to get real time interval + current_time = datetime.now() + dt = current_time - previous_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + previous_time = current_time + + # Get the time period + try: + # Division by zero + self.ideal_cycle.add(ms / self.iteration_counter) + except: + self.ideal_cycle.add(0) + + # Reset the counter + self.iteration_counter = 0 \ No newline at end of file diff --git a/exercises/static/exercises/package_delivery/web-template/exercise.py b/exercises/static/exercises/package_delivery/web-template/exercise.py index 09f49aa73..82bf0c72a 100755 --- a/exercises/static/exercises/package_delivery/web-template/exercise.py +++ b/exercises/static/exercises/package_delivery/web-template/exercise.py @@ -1,3 +1,5 @@ + + #!/usr/bin/env python from __future__ import print_function @@ -5,6 +7,7 @@ from websocket_server import WebsocketServer import time import threading +import multiprocessing import subprocess import sys from datetime import datetime @@ -14,8 +17,13 @@ import rospy from std_srvs.srv import Empty +import cv2 + +from shared.value import SharedValue +from brain import BrainProcess +import queue + -from gui import GUI, ThreadGUI from hal import HAL from console import start_console, close_console @@ -25,36 +33,65 @@ class Template: # self.ideal_cycle to run an execution for at least 1 second # self.process for the current running process def __init__(self): - self.measure_thread = None - self.thread = None - self.reload = False - self.stop_brain = False - self.user_code = "" + + self.brain_process = None + self.reload = multiprocessing.Event() # Time variables - self.ideal_cycle = 80 - self.measured_cycle = 80 - self.iteration_counter = 0 + self.brain_time_cycle = SharedValue('brain_time_cycle') + self.brain_ideal_cycle = SharedValue('brain_ideal_cycle') self.real_time_factor = 0 + self.frequency_message = {'brain': '', 'gui': '', 'rtf': ''} + # GUI variables + self.gui_time_cycle = SharedValue('gui_time_cycle') + self.gui_ideal_cycle = SharedValue('gui_ideal_cycle') + self.server = None self.client = None self.host = sys.argv[1] - # Initialize the GUI, HAL and Console behind the scenes self.hal = HAL() - self.gui = GUI(self.host) + self.paused = False + # Function to parse the code # A few assumptions: # 1. The user always passes sequential and iterative codes # 2. Only a single infinite loop def parse_code(self, source_code): - sequential_code, iterative_code = self.seperate_seq_iter(source_code) - return iterative_code, sequential_code + # Check for save/load + if(source_code[:5] == "#save"): + source_code = source_code[5:] + self.save_code(source_code) + + return "", "" - # Function to separate the iterative and sequential code + elif(source_code[:5] == "#load"): + source_code = source_code + self.load_code() + self.server.send_message(self.client, source_code) + + return "", "" + + else: + sequential_code, iterative_code = self.seperate_seq_iter(source_code[6:]) + return iterative_code, sequential_code + + + # Function for saving + def save_code(self, source_code): + with open('code/academy.py', 'w') as code_file: + code_file.write(source_code) + + # Function for loading + def load_code(self): + with open('code/academy.py', 'r') as code_file: + source_code = code_file.read() + + return source_code + + # Function to seperate the iterative and sequential code def seperate_seq_iter(self, source_code): if source_code == "": return "", "" @@ -62,8 +99,8 @@ def seperate_seq_iter(self, source_code): # Search for an instance of while True infinite_loop = re.search(r'[^ ]while\s*\(\s*True\s*\)\s*:|[^ ]while\s*True\s*:|[^ ]while\s*1\s*:|[^ ]while\s*\(\s*1\s*\)\s*:', source_code) - # Separate the content inside while True and the other - # (Separating the sequential and iterative part!) + # Seperate the content inside while True and the other + # (Seperating the sequential and iterative part!) try: start_index = infinite_loop.start() iterative_code = source_code[start_index:] @@ -72,6 +109,11 @@ def seperate_seq_iter(self, source_code): # Remove while True: syntax from the code # And remove the the 4 spaces indentation before each command iterative_code = re.sub(r'[^ ]while\s*\(\s*True\s*\)\s*:|[^ ]while\s*True\s*:|[^ ]while\s*1\s*:|[^ ]while\s*\(\s*1\s*\)\s*:', '', iterative_code) + # Add newlines to match line on bug report + extra_lines = sequential_code.count('\n') + while (extra_lines >= 0): + iterative_code = '\n' + iterative_code + extra_lines -= 1 iterative_code = re.sub(r'^[ ]{4}', '', iterative_code, flags=re.M) except: @@ -80,137 +122,16 @@ def seperate_seq_iter(self, source_code): return sequential_code, iterative_code - # The process function - def process_code(self, source_code): - # Redirect the information to console - start_console() - - iterative_code, sequential_code = self.parse_code(source_code) - - # print(sequential_code) - # print(iterative_code) - - # The Python exec function - # Run the sequential part - gui_module, hal_module = self.generate_modules() - reference_environment = {"GUI": gui_module, "HAL": hal_module} - exec(sequential_code, reference_environment) - - # Run the iterative part inside template - # and keep the check for flag - while self.reload == False: - while (self.stop_brain == True): - if (self.reload == True): - break - time.sleep(0.1) - - start_time = datetime.now() - - # Execute the iterative portion - exec(iterative_code, reference_environment) - - # Template specifics to run! - finish_time = datetime.now() - dt = finish_time - start_time - ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 - - # Keep updating the iteration counter - if (iterative_code == ""): - self.iteration_counter = 0 - else: - self.iteration_counter = self.iteration_counter + 1 - - # The code should be run for atleast the target time step - # If it's less put to sleep - if (ms < self.ideal_cycle): - time.sleep((self.ideal_cycle - ms) / 1000.0) - - close_console() - print("Current Thread Joined!") - - # Function to generate the modules for use in ACE Editor - def generate_modules(self): - # Define HAL module - hal_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) - hal_module.HAL = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) - # hal_module.drone = imp.new_module("drone") - # motors# hal_module.HAL.motors = imp.new_module("motors") - - # Add HAL functions - hal_module.HAL.get_frontal_image = self.hal.get_frontal_image - hal_module.HAL.get_ventral_image = self.hal.get_ventral_image - hal_module.HAL.get_position = self.hal.get_position - hal_module.HAL.get_velocity = self.hal.get_velocity - hal_module.HAL.get_yaw_rate = self.hal.get_yaw_rate - hal_module.HAL.get_orientation = self.hal.get_orientation - hal_module.HAL.get_roll = self.hal.get_roll - hal_module.HAL.get_pitch = self.hal.get_pitch - hal_module.HAL.get_yaw = self.hal.get_yaw - hal_module.HAL.get_landed_state = self.hal.get_landed_state - hal_module.HAL.set_cmd_pos = self.hal.set_cmd_pos - hal_module.HAL.set_cmd_vel = self.hal.set_cmd_vel - hal_module.HAL.set_cmd_mix = self.hal.set_cmd_mix - hal_module.HAL.takeoff = self.hal.takeoff - hal_module.HAL.land = self.hal.land - hal_module.HAL.set_cmd_pick = self.hal.set_cmd_pick - hal_module.HAL.set_cmd_drop = self.hal.set_cmd_drop - hal_module.HAL.get_pkg_state = self.hal.get_pkg_state - - - # Define GUI module - gui_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) - gui_module.GUI = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) - - # Add GUI functions - gui_module.GUI.showImage = self.gui.showImage - gui_module.GUI.showLeftImage = self.gui.showLeftImage - - # Adding modules to system - # Protip: The names should be different from - # other modules, otherwise some errors - sys.modules["HAL"] = hal_module - sys.modules["GUI"] = gui_module - - return gui_module, hal_module - - # Function to measure the frequency of iterations - def measure_frequency(self): - previous_time = datetime.now() - # An infinite loop - while True: - # Sleep for 2 seconds - time.sleep(2) - - # Measure the current time and subtract from the previous time to get real time interval - current_time = datetime.now() - dt = current_time - previous_time - ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 - previous_time = current_time - - # Get the time period - try: - # Division by zero - self.measured_cycle = ms / self.iteration_counter - except: - self.measured_cycle = 0 - - # Reset the counter - self.iteration_counter = 0 - - # Send to client - self.send_frequency_message() - - # Function to generate and send frequency messages def send_frequency_message(self): # This function generates and sends frequency measures of the brain and gui - brain_frequency = 0; gui_frequency = 0 + brain_frequency = 0;gui_frequency = 0 try: - brain_frequency = round(1000 / self.measured_cycle, 1) + brain_frequency = round(1000 / self.brain_ideal_cycle.get(), 1) except ZeroDivisionError: brain_frequency = 0 try: - gui_frequency = round(1000 / self.thread_gui.measured_cycle, 1) + gui_frequency = round(1000 / self.gui_ideal_cycle.get(), 1) except ZeroDivisionError: gui_frequency = 0 @@ -235,29 +156,32 @@ def track_stats(self): args = ["gz", "stats", "-p"] # Prints gz statistics. "-p": Output comma-separated values containing- # real-time factor (percent), simtime (sec), realtime (sec), paused (T or F) - stats_process = subprocess.Popen(args, stdout=subprocess.PIPE, bufsize=1, universal_newlines=True) + stats_process = subprocess.Popen(args, stdout=subprocess.PIPE, bufsize=0) # bufsize=1 enables line-bufferred mode (the input buffer is flushed # automatically on newlines if you would write to process.stdin ) with stats_process.stdout: - for line in iter(stats_process.stdout.readline, ''): - stats_list = [x.strip() for x in line.split(',')] - self.real_time_factor = stats_list[0] + for line in iter(stats_process.stdout.readline, b''): + stats_list = [x.strip() for x in line.split(b',')] + self.real_time_factor = stats_list[0].decode("utf-8") # Function to maintain thread execution def execute_thread(self, source_code): # Keep checking until the thread is alive # The thread will die when the coming iteration reads the flag - if self.thread is not None: - while self.thread.is_alive(): - time.sleep(0.2) + if(self.brain_process != None): + while self.brain_process.is_alive(): + pass # Turn the flag down, the iteration has successfully stopped! - self.reload = False + self.reload.clear() # New thread execution - self.thread = threading.Thread(target=self.process_code, args=[source_code]) - self.thread.start() + code = self.parse_code(source_code) + if code[0] == "" and code[1] == "": + return + + self.brain_process = BrainProcess(code, self.reload) + self.brain_process.start() self.send_code_message() - print("New Thread Started!") # Function to read and set frequency from incoming message def read_frequency_message(self, message): @@ -265,66 +189,58 @@ def read_frequency_message(self, message): # Set brain frequency frequency = float(frequency_message["brain"]) - self.ideal_cycle = 1000.0 / frequency + self.brain_time_cycle.add(1000.0 / frequency) # Set gui frequency frequency = float(frequency_message["gui"]) - self.thread_gui.ideal_cycle = 1000.0 / frequency + self.gui_time_cycle.add(1000.0 / frequency) return # The websocket function # Gets called when there is an incoming message from the client def handle(self, client, server, message): - if message[:5] == "#freq": + if(message[:5] == "#freq"): frequency_message = message[5:] self.read_frequency_message(frequency_message) time.sleep(1) + self.send_frequency_message() return - elif(message[:5] == "#ping"): time.sleep(1) self.send_ping_message() return - - elif (message[:5] == "#code"): + elif (message[:5] == "#code"): try: # Once received turn the reload flag up and send it to execute_thread function - self.user_code = message + code = message # print(repr(code)) - self.reload = True - self.execute_thread(self.user_code) + self.reload.set() + self.execute_thread(code) except: pass - - elif (message[:5] == "#rest"): + + elif (message[:5] == "#stop"): try: - self.reload = True - self.stop_brain = True - self.execute_thread(self.user_code) + self.reload.set() + self.execute_thread(code) except: pass - - elif (message[:5] == "#stop"): - self.stop_brain = True - - elif (message[:5] == "#play"): - self.stop_brain = False + self.server.send_message(self.client, "#stpd") # Function that gets called when the server is connected def connected(self, client, server): self.client = client - # Start the GUI update thread - self.thread_gui = ThreadGUI(self.gui) - self.thread_gui.start() + # Start the HAL update thread + self.hal.start_thread() - # Start the real time factor tracker thread + # Start real time factor tracker thread self.stats_thread = threading.Thread(target=self.track_stats) self.stats_thread.start() - # Start measure frequency - self.measure_thread = threading.Thread(target=self.measure_frequency) - self.measure_thread.start() + # Initialize the ping message + self.send_frequency_message() + print("After sneding freweq msg") print(client, 'connected') diff --git a/exercises/static/exercises/package_delivery/web-template/gui.py b/exercises/static/exercises/package_delivery/web-template/gui.py index f4fd34044..2b2e10176 100755 --- a/exercises/static/exercises/package_delivery/web-template/gui.py +++ b/exercises/static/exercises/package_delivery/web-template/gui.py @@ -6,15 +6,22 @@ from datetime import datetime from websocket_server import WebsocketServer import logging +import rospy +import cv2 +import sys +import numpy as np +import multiprocessing +from shared.image import SharedImage +from shared.value import SharedValue # Graphical User Interface Class class GUI: # Initialization function # The actual initialization def __init__(self, host): - t = threading.Thread(target=self.run_server) + rospy.init_node("GUI") self.payload = {'image': ''} self.left_payload = {'image': ''} self.server = None @@ -22,81 +29,47 @@ def __init__(self, host): self.host = host - # Image variables - self.image_to_be_shown = None - self.image_to_be_shown_updated = False - self.image_show_lock = threading.Lock() - - self.left_image_to_be_shown = None - self.left_image_to_be_shown_updated = False - self.left_image_show_lock = threading.Lock() - - self.acknowledge = False - self.acknowledge_lock = threading.Lock() + # Image variable host + self.shared_image = SharedImage("guifrontalimage") + self.shared_left_image = SharedImage("guiventralimage") - # Take the console object to set the same websocket and client + # Event objects for multiprocessing + self.ack_event = multiprocessing.Event() + self.cli_event = multiprocessing.Event() + + # Start server thread + t = threading.Thread(target=self.run_server) t.start() - # Explicit initialization function - # Class method, so user can call it without instantiation - @classmethod - def initGUI(cls, host): - # self.payload = {'image': '', 'shape': []} - new_instance = cls(host) - return new_instance # Function to prepare image payload # Encodes the image as a JSON string and sends through the WS def payloadImage(self): - self.image_show_lock.acquire() - image_to_be_shown_updated = self.image_to_be_shown_updated - image_to_be_shown = self.image_to_be_shown - self.image_show_lock.release() - - image = image_to_be_shown + image = self.shared_image.get() payload = {'image': '', 'shape': ''} - - if not image_to_be_shown_updated: - return payload - + shape = image.shape frame = cv2.imencode('.JPEG', image)[1] encoded_image = base64.b64encode(frame) - + payload['image'] = encoded_image.decode('utf-8') payload['shape'] = shape - - self.image_show_lock.acquire() - self.image_to_be_shown_updated = False - self.image_show_lock.release() - + return payload - + # Function to prepare image payload # Encodes the image as a JSON string and sends through the WS def payloadLeftImage(self): - self.left_image_show_lock.acquire() - left_image_to_be_shown_updated = self.left_image_to_be_shown_updated - left_image_to_be_shown = self.left_image_to_be_shown - self.left_image_show_lock.release() - - image = left_image_to_be_shown + image = self.shared_left_image.get() payload = {'image': '', 'shape': ''} - - if not left_image_to_be_shown_updated: - return payload - + shape = image.shape frame = cv2.imencode('.JPEG', image)[1] encoded_image = base64.b64encode(frame) - + payload['image'] = encoded_image.decode('utf-8') payload['shape'] = shape - - self.left_image_show_lock.acquire() - self.left_image_to_be_shown_updated = False - self.left_image_show_lock.release() - + return payload # Function for student to call @@ -117,20 +90,10 @@ def showLeftImage(self, image): # Called when a new client is received def get_client(self, client, server): self.client = client + self.cli_event.set() + + print(client, 'connected') - # Function to get value of Acknowledge - def get_acknowledge(self): - self.acknowledge_lock.acquire() - acknowledge = self.acknowledge - self.acknowledge_lock.release() - - return acknowledge - - # Function to get value of Acknowledge - def set_acknowledge(self, value): - self.acknowledge_lock.acquire() - self.acknowledge = value - self.acknowledge_lock.release() # Update the gui def update_gui(self): @@ -152,8 +115,15 @@ def update_gui(self): # Gets called when there is an incoming message from the client def get_message(self, client, server, message): # Acknowledge Message for GUI Thread - if message[:4] == "#ack": - self.set_acknowledge(True) + + if(message[:4] == "#ack"): + # Set acknowledgement flag + self.ack_event.set() + + # Function that gets called when the connected closes + def handle_close(self, client, server): + print(client, 'closed') + # Activate the server @@ -161,6 +131,7 @@ def run_server(self): self.server = WebsocketServer(port=2303, host=self.host) self.server.set_fn_new_client(self.get_client) self.server.set_fn_message_received(self.get_message) + self.server.set_fn_client_left(self.handle_close) logged = False while not logged: @@ -181,32 +152,45 @@ def reset_gui(self): # This class decouples the user thread # and the GUI update thread -class ThreadGUI: - def __init__(self, gui): - self.gui = gui +class ProcessGUI(multiprocessing.Process): + def __init__(self): + super(ProcessGUI, self).__init__() + self.host = sys.argv[1] # Time variables - self.ideal_cycle = 80 - self.measured_cycle = 80 + self.time_cycle = SharedValue("gui_time_cycle") + self.ideal_cycle = SharedValue("gui_ideal_cycle") self.iteration_counter = 0 + # Function to initialize events + def initialize_events(self): + # Events + self.ack_event = self.gui.ack_event + self.cli_event = self.gui.cli_event + self.exit_signal = multiprocessing.Event() + # Function to start the execution of threads - def start(self): + def run(self): + # Initialize GUI + self.gui = GUI(self.host) + self.initialize_events() + + # Wait for client before starting + self.cli_event.wait() self.measure_thread = threading.Thread(target=self.measure_thread) - self.thread = threading.Thread(target=self.run) + self.thread = threading.Thread(target=self.run_gui) self.measure_thread.start() self.thread.start() - print("GUI Thread Started!") + print("GUI Process Started!") + + self.exit_signal.wait() # The measuring thread to measure frequency def measure_thread(self): - while self.gui.client is None: - pass - previous_time = datetime.now() - while True: + while(True): # Sleep for 2 seconds time.sleep(2) @@ -219,32 +203,40 @@ def measure_thread(self): # Get the time period try: # Division by zero - self.measured_cycle = ms / self.iteration_counter + self.ideal_cycle.add(ms / self.iteration_counter) except: - self.measured_cycle = 0 + self.ideal_cycle.add(0) # Reset the counter self.iteration_counter = 0 # The main thread of execution - def run(self): - while self.gui.client is None: - pass - - while True: + def run_gui(self): + while(True): start_time = datetime.now() + # Send update signal self.gui.update_gui() - acknowledge_message = self.gui.get_acknowledge() - - while not acknowledge_message: - acknowledge_message = self.gui.get_acknowledge() - self.gui.set_acknowledge(False) + # Wait for acknowldege signal + self.ack_event.wait() + self.ack_event.clear() finish_time = datetime.now() self.iteration_counter = self.iteration_counter + 1 dt = finish_time - start_time ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 - if ms < self.ideal_cycle: - time.sleep((self.ideal_cycle-ms) / 1000.0) + time_cycle = self.time_cycle.get() + + if(ms < time_cycle): + time.sleep((time_cycle-ms) / 1000.0) + + self.exit_signal.set() + + # Functions to handle auxillary GUI functions + def reset_gui(self): + self.gui.reset_gui() + +if __name__ == "__main__": + gui = ProcessGUI() + gui.start() \ No newline at end of file diff --git a/exercises/static/exercises/package_delivery/web-template/hal.py b/exercises/static/exercises/package_delivery/web-template/hal.py old mode 100644 new mode 100755 index f6a0957d0..62d0b9e42 --- a/exercises/static/exercises/package_delivery/web-template/hal.py +++ b/exercises/static/exercises/package_delivery/web-template/hal.py @@ -5,7 +5,9 @@ from datetime import datetime from drone_wrapper import DroneWrapper -from magnet import Magnet +from shared.magnet import Magnet +from shared.image import SharedImage +from shared.value import SharedValue # Hardware Abstraction Layer class HAL: @@ -14,76 +16,115 @@ class HAL: def __init__(self): rospy.init_node("HAL") - + + self.shared_frontal_image = SharedImage("halfrontalimage") + self.shared_ventral_image = SharedImage("halventralimage") + self.shared_x = SharedValue("x") + self.shared_y = SharedValue("y") + self.shared_z = SharedValue("z") + self.shared_takeoff_z = SharedValue("sharedtakeoffz") + self.shared_az = SharedValue("az") + self.shared_azt = SharedValue("azt") + self.shared_vx = SharedValue("vx") + self.shared_vy = SharedValue("vy") + self.shared_vz = SharedValue("vz") + self.shared_landed_state = SharedValue("landedstate") + self.shared_position = SharedValue("position") + self.shared_velocity = SharedValue("velocity") + self.shared_orientation = SharedValue("orientation") + self.shared_roll = SharedValue("roll") + self.shared_pitch = SharedValue("pitch") + self.shared_yaw = SharedValue("yaw") + self.shared_yaw_rate = SharedValue("yawrate") + self.shared_package_state = SharedValue("packagestate") + self.image = None - self.drone = DroneWrapper(name="rqt") + self.drone = DroneWrapper(name="rqt",ns="/iris/") self.magnet = Magnet() + # Update thread + self.thread = ThreadHAL(self.update_hal) + # Explicit initialization functions # Class method, so user can call it without instantiation - @classmethod - def initRobot(cls): - new_instance = cls() - return new_instance + + + # Function to start the update thread + def start_thread(self): + self.thread.start() # Get Image from ROS Driver Camera def get_frontal_image(self): image = self.drone.get_frontal_image() image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) - return image_rgb + self.shared_frontal_image.add(image_rgb) def get_ventral_image(self): image = self.drone.get_ventral_image() image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) - return image_rgb + self.shared_ventral_image.add(image_rgb) def get_position(self): pos = self.drone.get_position() - return pos + self.shared_position.add(pos,type_name="list") def get_velocity(self): vel = self.drone.get_velocity() - return vel + self.shared_velocity.add(vel ,type_name="list") def get_yaw_rate(self): yaw_rate = self.drone.get_yaw_rate() - return yaw_rate + self.shared_yaw_rate.add(yaw_rate) def get_orientation(self): orientation = self.drone.get_orientation() - return orientation + self.shared_orientation.add(orientation ,type_name="list") def get_roll(self): roll = self.drone.get_roll() - return roll + self.shared_roll.add(roll) def get_pitch(self): pitch = self.drone.get_pitch() - return pitch + self.shared_pitch.add(pitch) def get_yaw(self): yaw = self.drone.get_yaw() - return yaw + self.shared_yaw.add(yaw) def get_landed_state(self): state = self.drone.get_landed_state() - return state + self.shared_landed_state.add(state) + + def set_cmd_pos(self): + x = self.shared_x.get() + y = self.shared_y.get() + z = self.shared_z.get() + az = self.shared_az.get() - def set_cmd_pos(self, x, y, z, az): self.drone.set_cmd_pos(x, y, z, az) - def set_cmd_vel(self, vx, vy, vz, az): + def set_cmd_vel(self): + vx = self.shared_vx.get() + vy = self.shared_vy.get() + vz = self.shared_vz.get() + az = self.shared_azt.get() self.drone.set_cmd_vel(vx, vy, vz, az) - def set_cmd_mix(self, vx, vy, z, az): + def set_cmd_mix(self): + vx = self.shared_vx.get() + vy = self.shared_vy.get() + z = self.shared_z.get() + az = self.shared_azt.get() self.drone.set_cmd_mix(vx, vy, z, az) - def takeoff(self, h=5): + def takeoff(self): + h = self.shared_takeoff_z.get() self.drone.takeoff(h) def land(self): self.drone.land() - + def set_cmd_pick(self): self.magnet.set_cmd_pick() @@ -92,4 +133,66 @@ def set_cmd_drop(self): def get_pkg_state(self): state = self.magnet.get_pkg_state() - return state + self.shared_package_state.add(state) + + + def update_hal(self): + self.get_frontal_image() + self.get_ventral_image() + self.get_position() + self.get_velocity() + self.get_yaw_rate() + self.get_orientation() + self.get_pitch() + self.get_roll() + self.get_yaw() + self.get_landed_state() + self.set_cmd_pos() + self.set_cmd_vel() + self.set_cmd_mix() + self.set_cmd_pick() + self.set_cmd_drop() + self.get_pkg_state() + + # Destructor function to close all fds + def __del__(self): + self.shared_frontal_image.close() + self.shared_ventral_image.close() + self.shared_x.close() + self.shared_y.close() + self.shared_z.close() + self.shared_takeoff_z.close() + self.shared_az.close() + self.shared_azt.close() + self.shared_vx.close() + self.shared_vy.close() + self.shared_vz.close() + self.shared_landed_state.close() + self.shared_position.close() + self.shared_velocity.close() + self.shared_orientation.close() + self.shared_roll.close() + self.shared_pitch.close() + self.shared_yaw.close() + self.shared_yaw_rate.close() + self.shared_package_state.close() + +class ThreadHAL(threading.Thread): + def __init__(self, update_function): + super(ThreadHAL, self).__init__() + self.time_cycle = 80 + self.update_function = update_function + + def run(self): + while(True): + start_time = datetime.now() + + self.update_function() + + finish_time = datetime.now() + + dt = finish_time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + + if(ms < self.time_cycle): + time.sleep((self.time_cycle - ms) / 1000.0) \ No newline at end of file diff --git a/exercises/static/exercises/package_delivery/web-template/launch/gazebo.launch b/exercises/static/exercises/package_delivery/web-template/launch/gazebo.launch deleted file mode 100644 index 23249178c..000000000 --- a/exercises/static/exercises/package_delivery/web-template/launch/gazebo.launch +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/exercises/static/exercises/package_delivery/web-template/launch/launch.py b/exercises/static/exercises/package_delivery/web-template/launch/launch.py deleted file mode 100644 index 0b2fc4e37..000000000 --- a/exercises/static/exercises/package_delivery/web-template/launch/launch.py +++ /dev/null @@ -1,131 +0,0 @@ -#!/usr/bin/env python3 - -import stat -import rospy -from os import lstat -from subprocess import Popen, PIPE - - -DRI_PATH = "/dev/dri/card0" -EXERCISE = "package_delivery" -TIMEOUT = 30 -MAX_ATTEMPT = 2 - - -# Check if acceleration can be enabled -def check_device(device_path): - try: - return stat.S_ISCHR(lstat(device_path)[stat.ST_MODE]) - except: - return False - - -# Spawn new process -def spawn_process(args, insert_vglrun=False): - if insert_vglrun: - args.insert(0, "vglrun") - process = Popen(args, stdout=PIPE, bufsize=1, universal_newlines=True) - return process - - -class Test(): - def gazebo(self): - rospy.logwarn("[GAZEBO] Launching") - try: - rospy.wait_for_service("/gazebo/get_model_properties", TIMEOUT) - return True - except rospy.ROSException: - return False - - def px4(self): - rospy.logwarn("[PX4-SITL] Launching") - start_time = rospy.get_time() - args = ["./PX4-Autopilot/build/px4_sitl_default/bin/px4-commander","--instance", "0", "check"] - while rospy.get_time() - start_time < TIMEOUT: - process = spawn_process(args, insert_vglrun=False) - with process.stdout: - for line in iter(process.stdout.readline, ''): - if ("Prearm check: OK" in line): - return True - rospy.sleep(2) - return False - - def mavros(self, ns=""): - rospy.logwarn("[MAVROS] Launching") - try: - rospy.wait_for_service(ns + "/mavros/cmd/arming", TIMEOUT) - return True - except rospy.ROSException: - return False - - -class Launch(): - def __init__(self): - self.test = Test() - self.acceleration_enabled = check_device(DRI_PATH) - - # Start roscore - args = ["/opt/ros/noetic/bin/roscore"] - spawn_process(args, insert_vglrun=False) - - rospy.init_node("launch", anonymous=True) - - def start(self): - ######## LAUNCH GAZEBO ######## - args = ["/opt/ros/noetic/bin/roslaunch", - "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/gazebo.launch", - "--wait", - "--log" - ] - - attempt = 1 - while True: - spawn_process(args, insert_vglrun=self.acceleration_enabled) - if self.test.gazebo() == True: - break - if attempt == MAX_ATTEMPT: - rospy.logerr("[GAZEBO] Launch Failed") - return - attempt = attempt + 1 - - - ######## LAUNCH PX4 ######## - args = ["/opt/ros/noetic/bin/roslaunch", - "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/px4.launch", - "--log" - ] - - attempt = 1 - while True: - spawn_process(args, insert_vglrun=self.acceleration_enabled) - if self.test.px4() == True: - break - if attempt == MAX_ATTEMPT: - rospy.logerr("[PX4] Launch Failed") - return - attempt = attempt + 1 - - - ######## LAUNCH MAVROS ######## - args = ["/opt/ros/noetic/bin/roslaunch", - "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/mavros.launch", - "--log" - ] - - attempt = 1 - while True: - spawn_process(args, insert_vglrun=self.acceleration_enabled) - if self.test.mavros() == True: - break - if attempt == MAX_ATTEMPT: - rospy.logerr("[MAVROS] Launch Failed") - return - attempt = attempt + 1 - - -if __name__ == "__main__": - launch = Launch() - launch.start() - - with open("/drones_launch.log", "w") as f: - f.write("success") diff --git a/exercises/static/exercises/package_delivery/web-template/launch/mavros.launch b/exercises/static/exercises/package_delivery/web-template/launch/mavros.launch deleted file mode 100644 index b899c0ec1..000000000 --- a/exercises/static/exercises/package_delivery/web-template/launch/mavros.launch +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/exercises/static/exercises/package_delivery/web-template/launch/package_delivery.launch b/exercises/static/exercises/package_delivery/web-template/launch/package_delivery.launch new file mode 100755 index 000000000..f1d383018 --- /dev/null +++ b/exercises/static/exercises/package_delivery/web-template/launch/package_delivery.launch @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/exercises/static/exercises/package_delivery/web-template/launch/px4.launch b/exercises/static/exercises/package_delivery/web-template/launch/px4.launch deleted file mode 100644 index 11f78d57b..000000000 --- a/exercises/static/exercises/package_delivery/web-template/launch/px4.launch +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/exercises/static/exercises/package_delivery/web-template/package_delivery.yaml b/exercises/static/exercises/package_delivery/web-template/package_delivery.yaml deleted file mode 100644 index e93828f54..000000000 --- a/exercises/static/exercises/package_delivery/web-template/package_delivery.yaml +++ /dev/null @@ -1 +0,0 @@ -drone_model: "typhoon_h480_dual_cam" \ No newline at end of file diff --git a/exercises/static/exercises/package_delivery/web-template/shared/__init__.py b/exercises/static/exercises/package_delivery/web-template/shared/__init__.py new file mode 100755 index 000000000..e69de29bb diff --git a/exercises/static/exercises/package_delivery/web-template/shared/image.py b/exercises/static/exercises/package_delivery/web-template/shared/image.py new file mode 100755 index 000000000..de5c9f9d6 --- /dev/null +++ b/exercises/static/exercises/package_delivery/web-template/shared/image.py @@ -0,0 +1,109 @@ +import numpy as np +import mmap +from posix_ipc import Semaphore, O_CREX, ExistentialError, O_CREAT, SharedMemory, unlink_shared_memory +from ctypes import sizeof, memmove, addressof, create_string_buffer +from shared.structure_img import MD + +# Probably, using self variables gives errors with memmove +# Therefore, a global variable for utility +md_buf = create_string_buffer(sizeof(MD)) + +class SharedImage: + def __init__(self, name): + # Initialize variables for memory regions and buffers and Semaphore + self.shm_buf = None; self.shm_region = None + self.md_buf = None; self.md_region = None + self.image_lock = None + + self.shm_name = name; self.md_name = name + "-meta" + self.image_lock_name = name + + # Initialize or retreive metadata memory region + try: + self.md_region = SharedMemory(self.md_name) + self.md_buf = mmap.mmap(self.md_region.fd, sizeof(MD)) + self.md_region.close_fd() + except ExistentialError: + self.md_region = SharedMemory(self.md_name, O_CREAT, size=sizeof(MD)) + self.md_buf = mmap.mmap(self.md_region.fd, self.md_region.size) + self.md_region.close_fd() + + # Initialize or retreive Semaphore + try: + self.image_lock = Semaphore(self.image_lock_name, O_CREX) + except ExistentialError: + image_lock = Semaphore(self.image_lock_name, O_CREAT) + image_lock.unlink() + self.image_lock = Semaphore(self.image_lock_name, O_CREX) + + self.image_lock.release() + + # Get the shared image + def get(self): + # Define metadata + metadata = MD() + + # Get metadata from the shared region + self.image_lock.acquire() + md_buf[:] = self.md_buf + memmove(addressof(metadata), md_buf, sizeof(metadata)) + self.image_lock.release() + + # Try to retreive the image from shm_buffer + # Otherwise return a zero image + try: + self.shm_region = SharedMemory(self.shm_name) + self.shm_buf = mmap.mmap(self.shm_region.fd, metadata.size) + self.shm_region.close_fd() + + self.image_lock.acquire() + image = np.ndarray(shape=(metadata.shape_0, metadata.shape_1, metadata.shape_2), + dtype='uint8', buffer=self.shm_buf) + self.image_lock.release() + + # Check for a None image + if(image.size == 0): + image = np.zeros((3, 3, 3), np.uint8) + + except ExistentialError: + image = np.zeros((3, 3, 3), np.uint8) + + return image + + # Add the shared image + def add(self, image): + try: + # Get byte size of the image + byte_size = image.nbytes + + # Get the shared memory buffer to read from + if not self.shm_region: + self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=byte_size) + self.shm_buf = mmap.mmap(self.shm_region.fd, byte_size) + self.shm_region.close_fd() + + # Generate meta data + metadata = MD(image.shape[0], image.shape[1], image.shape[2], byte_size) + + # Send the meta data and image to shared regions + self.image_lock.acquire() + memmove(md_buf, addressof(metadata), sizeof(metadata)) + self.md_buf[:] = md_buf[:] + self.shm_buf[:] = image.tobytes() + self.image_lock.release() + except: + pass + + # Destructor function to unlink and disconnect + def close(self): + self.image_lock.acquire() + self.md_buf.close() + + try: + unlink_shared_memory(self.md_name) + unlink_shared_memory(self.shm_name) + except ExistentialError: + pass + + self.image_lock.release() + self.image_lock.close() diff --git a/exercises/static/exercises/package_delivery/web-template/magnet.py b/exercises/static/exercises/package_delivery/web-template/shared/magnet.py similarity index 100% rename from exercises/static/exercises/package_delivery/web-template/magnet.py rename to exercises/static/exercises/package_delivery/web-template/shared/magnet.py diff --git a/exercises/static/exercises/package_delivery/web-template/shared/structure_img.py b/exercises/static/exercises/package_delivery/web-template/shared/structure_img.py new file mode 100755 index 000000000..ae40b3707 --- /dev/null +++ b/exercises/static/exercises/package_delivery/web-template/shared/structure_img.py @@ -0,0 +1,9 @@ +from ctypes import Structure, c_int32, c_int64 + +class MD(Structure): + _fields_ = [ + ('shape_0', c_int32), + ('shape_1', c_int32), + ('shape_2', c_int32), + ('size', c_int64) + ] \ No newline at end of file diff --git a/exercises/static/exercises/package_delivery/web-template/shared/value.py b/exercises/static/exercises/package_delivery/web-template/shared/value.py new file mode 100755 index 000000000..e7bfad8a2 --- /dev/null +++ b/exercises/static/exercises/package_delivery/web-template/shared/value.py @@ -0,0 +1,93 @@ +import numpy as np +import mmap +from posix_ipc import Semaphore, O_CREX, ExistentialError, O_CREAT, SharedMemory, unlink_shared_memory +from ctypes import sizeof, memmove, addressof, create_string_buffer, c_float +import struct + +class SharedValue: + def __init__(self, name): + # Initialize varaibles for memory regions and buffers and Semaphore + self.shm_buf = None; self.shm_region = None + self.value_lock = None + + self.shm_name = name; self.value_lock_name = name + + # Initialize or retreive Semaphore + try: + self.value_lock = Semaphore(self.value_lock_name, O_CREX) + except ExistentialError: + value_lock = Semaphore(self.value_lock_name, O_CREAT) + value_lock.unlink() + self.value_lock = Semaphore(self.value_lock_name, O_CREX) + + self.value_lock.release() + + # Get the shared value + def get(self, type_name= "value"): + # Retreive the data from buffer + if type_name=="value": + try: + self.shm_region = SharedMemory(self.shm_name) + self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) + self.shm_region.close_fd() + except ExistentialError: + self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=sizeof(c_float)) + self.shm_buf = mmap.mmap(self.shm_region.fd, self.shm_region.size) + self.shm_region.close_fd() + self.value_lock.acquire() + value = struct.unpack('f', self.shm_buf)[0] + self.value_lock.release() + + return value + elif type_name=="list": + self.shm_region = SharedMemory(self.shm_name) + self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) + self.shm_region.close_fd() + self.value_lock.acquire() + array_val = np.ndarray(shape=(3,), + dtype='float32', buffer=self.shm_buf) + self.value_lock.release() + + return array_val + + else: + print("missing argument for return type") + + + # Add the shared value + def add(self, value, type_name= "value"): + # Send the data to shared regions + if type_name=="value": + try: + self.shm_region = SharedMemory(self.shm_name) + self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) + self.shm_region.close_fd() + except ExistentialError: + self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=sizeof(c_float)) + self.shm_buf = mmap.mmap(self.shm_region.fd, self.shm_region.size) + self.shm_region.close_fd() + + self.value_lock.acquire() + self.shm_buf[:] = struct.pack('f', value) + self.value_lock.release() + elif type_name=="list": + byte_size = value.nbytes + self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=byte_size) + self.shm_buf = mmap.mmap(self.shm_region.fd, byte_size) + self.shm_region.close_fd() + self.value_lock.acquire() + self.shm_buf[:] = value.tobytes() + self.value_lock.release() + + # Destructor function to unlink and disconnect + def close(self): + self.value_lock.acquire() + self.shm_buf.close() + + try: + unlink_shared_memory(self.shm_name) + except ExistentialError: + pass + + self.value_lock.release() + self.value_lock.close() diff --git a/exercises/static/exercises/package_delivery/web-template/user_functions.py b/exercises/static/exercises/package_delivery/web-template/user_functions.py new file mode 100755 index 000000000..185690838 --- /dev/null +++ b/exercises/static/exercises/package_delivery/web-template/user_functions.py @@ -0,0 +1,128 @@ +from shared.image import SharedImage +from shared.value import SharedValue +import numpy as np +import cv2 + +# Define HAL functions +class HALFunctions: + def __init__(self): + # Initialize image variable + self.shared_frontal_image = SharedImage("halfrontalimage") + self.shared_ventral_image = SharedImage("halventralimage") + self.shared_x = SharedValue("x") + self.shared_y = SharedValue("y") + self.shared_z = SharedValue("z") + self.shared_takeoff_z = SharedValue("sharedtakeoffz") + self.shared_az = SharedValue("az") + self.shared_azt = SharedValue("azt") + self.shared_vx = SharedValue("vx") + self.shared_vy = SharedValue("vy") + self.shared_vz = SharedValue("vz") + self.shared_landed_state = SharedValue("landedstate") + self.shared_position = SharedValue("position") + self.shared_velocity = SharedValue("velocity") + self.shared_orientation = SharedValue("orientation") + self.shared_roll = SharedValue("roll") + self.shared_pitch = SharedValue("pitch") + self.shared_yaw = SharedValue("yaw") + self.shared_yaw_rate = SharedValue("yawrate") + self.shared_package_state = SharedValue("packagestate") + + + # Get image function + def get_frontal_image(self): + image = self.shared_frontal_image.get() + return image + + # Get left image function + def get_ventral_image(self): + image = self.shared_ventral_image.get() + return image + + def takeoff(self, height): + self.shared_takeoff_z.add(height) + + def land(self): + pass + + def set_cmd_pos(self, x, y , z, az): + self.shared_x.add(x) + self.shared_y.add(y) + self.shared_z.add(z) + self.shared_az.add(az) + + def set_cmd_vel(self, vx, vy, vz, az): + self.shared_vx.add(vx) + self.shared_vy.add(vy) + self.shared_vz.add(vz) + self.shared_azt.add(az) + + def set_cmd_mix(self, vx, vy, z, az): + self.shared_vx.add(vx) + self.shared_vy.add(vy) + self.shared_vz.add(z) + self.shared_azt.add(az) + + + def get_position(self): + position = self.shared_position.get(type_name = "list") + return position + + def get_velocity(self): + velocity = self.shared_velocity.get(type_name = "list") + return velocity + + def get_yaw_rate(self): + yaw_rate = self.shared_yaw_rate.get(type_name = "value") + return yaw_rate + + def get_orientation(self): + orientation = self.shared_orientation.get(type_name = "list") + return orientation + + def get_roll(self): + roll = self.shared_roll.get(type_name = "value") + return roll + + def get_pitch(self): + pitch = self.shared_pitch.get(type_name = "value") + return pitch + + def get_yaw(self): + yaw = self.shared_yaw.get(type_name = "value") + return yaw + + def get_landed_state(self): + landed_state = self.shared_landed_state.get(type_name = "value") + return landed_state + + def set_cmd_pick(self): + pass + + def set_cmd_drop(self): + pass + + def get_pkg_state(self): + package_state = self.shared_package_state.get(type_name = "value") + return package_state + +# Define GUI functions +class GUIFunctions: + def __init__(self): + # Initialize image variable + self.shared_image = SharedImage("guifrontalimage") + self.shared_left_image = SharedImage("guiventralimage") + + # Show image function + def showImage(self, image): + # Reshape to 3 channel if it has only 1 in order to display it + if (len(image.shape) < 3): + image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB) + self.shared_image.add(image) + + # Show left image function + def showLeftImage(self, image): + # Reshape to 3 channel if it has only 1 in order to display it + if (len(image.shape) < 3): + image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB) + self.shared_left_image.add(image) \ No newline at end of file diff --git a/scripts/instructions.json b/scripts/instructions.json index d163ebd76..52bb0a297 100644 --- a/scripts/instructions.json +++ b/scripts/instructions.json @@ -131,8 +131,9 @@ }, "package_delivery": { "gazebo_path": "/RoboticsAcademy/exercises/package_delivery/web-template/launch", - "instructions_ros": ["python3 ./RoboticsAcademy/exercises/package_delivery/web-template/launch/launch.py"], - "instructions_host": "python3 /RoboticsAcademy/exercises/package_delivery/web-template/exercise.py 0.0.0.0" + "instructions_ros": ["/opt/ros/noetic/bin/roslaunch ./RoboticsAcademy/exercises/package_delivery/web-template/launch/package_delivery.launch"], + "instructions_host": "python3 /RoboticsAcademy/exercises/package_delivery/web-template/exercise.py 0.0.0.0", + "instructions_gui": "python3 /RoboticsAcademy/exercises/power_tower_inspection/web-template/gui.py 0.0.0.0 {}" }, "power_tower_inspection": { "gazebo_path": "/RoboticsAcademy/exercises/power_tower_inspection/web-template/launch", From 4a5a2d1797c319ff384ff614d7217e725eefdc7c Mon Sep 17 00:00:00 2001 From: RUFFY-369 Date: Sun, 11 Sep 2022 06:41:55 +0530 Subject: [PATCH 16/42] frontend package del final --- .../web-template/package_delivery.world | 77 ++++++++++++++----- 1 file changed, 58 insertions(+), 19 deletions(-) diff --git a/exercises/static/exercises/package_delivery/web-template/package_delivery.world b/exercises/static/exercises/package_delivery/web-template/package_delivery.world index 6ec6bb43c..e1867ff4e 100644 --- a/exercises/static/exercises/package_delivery/web-template/package_delivery.world +++ b/exercises/static/exercises/package_delivery/web-template/package_delivery.world @@ -27,16 +27,23 @@ 30 -5 0 0 0 0 - + - model://package_box - -1 -1 0.061 0 0 0 + model://logoPadRed + -1 -1 0.05 0 0 + logoPadRed1 - model://logoPadRed - -1 -1 0.05 0 0 + -1 2.4 0.05 0 0 + logoPadRed2 + + + + model://logoPadRed + -1 5.5 0.05 0 0 + logoPadRed3 @@ -45,16 +52,58 @@ -1 -4 0.05 0 0 - + model://house_3 20 20 0 0 0 0 + house1 + + + + model://house_3 + 20 -20 0 0 0 0 + house2 + + + + model://house_3 + 40 0 0 0 0 0 + house3 + + + + + model://package_box_01 + -1 -1 0.061 0 0 0 + + + + model://package_box_02 + -1 2.4 0.061 0 0 0 + + + + model://package_box_03 + -1 5.5 0.061 0 0 0 - + model://logoBeacon 20 15 0 0 0 0 + logoBeacon1 + + + + model://logoBeacon + 20 -15 0 0 0 0 + logoBeacon2 + + + + model://logoBeacon + 40 -5 0 0 0 0 + logoBeacon3 @@ -141,12 +190,7 @@ 3.408638 8.616844 -0.017477 0 0 0 - - - model://aws_robomaker_warehouse_ClutteringA_01 - - -1.491287 5.222435 -0.017477 0 0 -1.583185 - + model://aws_robomaker_warehouse_ClutteringC_01 @@ -171,12 +215,7 @@ 3.236 6.137154 0 0 0 3.150000 - - - model://aws_robomaker_warehouse_ClutteringC_01 - - -1.573677 2.301994 -0.015663 0 0 -3.133191 - + model://aws_robomaker_warehouse_ClutteringC_01 From 3537b900fcc094d6761f73a5284866187036c960 Mon Sep 17 00:00:00 2001 From: RUFFY-369 Date: Sun, 11 Sep 2022 06:57:44 +0530 Subject: [PATCH 17/42] multiprocessing final for drone hangar --- .../drone_hangar/web-template/brain.py | 166 +++++++++++ .../drone_hangar/web-template/exercise.py | 265 ++++++------------ .../drone_hangar/web-template/gui.py | 182 ++++++------ .../drone_hangar/web-template/hal.py | 142 ++++++++-- .../web-template/launch/drone_hangar.launch | 60 ++++ .../web-template/launch/gazebo.launch | 24 -- .../web-template/launch/launch.py | 131 --------- .../web-template/launch/mavros.launch | 13 - .../web-template/launch/px4.launch | 20 -- .../web-template/shared/__init__.py | 0 .../drone_hangar/web-template/shared/image.py | 109 +++++++ .../web-template/shared/structure_img.py | 9 + .../drone_hangar/web-template/shared/value.py | 93 ++++++ .../web-template/user_functions.py | 117 ++++++++ scripts/instructions.json | 5 +- 15 files changed, 853 insertions(+), 483 deletions(-) create mode 100755 exercises/static/exercises/drone_hangar/web-template/brain.py mode change 100644 => 100755 exercises/static/exercises/drone_hangar/web-template/exercise.py mode change 100644 => 100755 exercises/static/exercises/drone_hangar/web-template/gui.py mode change 100644 => 100755 exercises/static/exercises/drone_hangar/web-template/hal.py create mode 100755 exercises/static/exercises/drone_hangar/web-template/launch/drone_hangar.launch delete mode 100644 exercises/static/exercises/drone_hangar/web-template/launch/gazebo.launch delete mode 100644 exercises/static/exercises/drone_hangar/web-template/launch/launch.py delete mode 100644 exercises/static/exercises/drone_hangar/web-template/launch/mavros.launch delete mode 100644 exercises/static/exercises/drone_hangar/web-template/launch/px4.launch create mode 100755 exercises/static/exercises/drone_hangar/web-template/shared/__init__.py create mode 100755 exercises/static/exercises/drone_hangar/web-template/shared/image.py create mode 100755 exercises/static/exercises/drone_hangar/web-template/shared/structure_img.py create mode 100755 exercises/static/exercises/drone_hangar/web-template/shared/value.py create mode 100755 exercises/static/exercises/drone_hangar/web-template/user_functions.py diff --git a/exercises/static/exercises/drone_hangar/web-template/brain.py b/exercises/static/exercises/drone_hangar/web-template/brain.py new file mode 100755 index 000000000..2330c04ba --- /dev/null +++ b/exercises/static/exercises/drone_hangar/web-template/brain.py @@ -0,0 +1,166 @@ +import time +import threading +import multiprocessing +import sys +from datetime import datetime +import re +import json +import importlib + +import rospy +from std_srvs.srv import Empty +import cv2 + +from user_functions import GUIFunctions, HALFunctions +from console import start_console, close_console + +from shared.value import SharedValue + +# The brain process class +class BrainProcess(multiprocessing.Process): + def __init__(self, code, exit_signal): + super(BrainProcess, self).__init__() + + # Initialize exit signal + self.exit_signal = exit_signal + + # Function definitions for users to use + self.hal = HALFunctions() + self.gui = GUIFunctions() + + # Time variables + self.time_cycle = SharedValue('brain_time_cycle') + self.ideal_cycle = SharedValue('brain_ideal_cycle') + self.iteration_counter = 0 + + # Get the sequential and iterative code + # Something wrong over here! The code is reversing + # Found a solution but could not find the reason for this (parse_code function's return line of exercise.py is the reason) + self.sequential_code = code[1] + self.iterative_code = code[0] + + # Function to run to start the process + def run(self): + # Two threads for running and measuring + self.measure_thread = threading.Thread(target=self.measure_frequency) + self.thread = threading.Thread(target=self.process_code) + + self.measure_thread.start() + self.thread.start() + + print("Brain Process Started!") + + self.exit_signal.wait() + + # The process function + def process_code(self): + # Redirect information to console + start_console() + + # Reference Environment for the exec() function + iterative_code, sequential_code = self.iterative_code, self.sequential_code + + # The Python exec function + # Run the sequential part + gui_module, hal_module = self.generate_modules() + if sequential_code != "": + reference_environment = {"GUI": gui_module, "HAL": hal_module} + exec(sequential_code, reference_environment) + + # Run the iterative part inside template + # and keep the check for flag + while not self.exit_signal.is_set(): + start_time = datetime.now() + + # Execute the iterative portion + if iterative_code != "": + exec(iterative_code, reference_environment) + + # Template specifics to run! + finish_time = datetime.now() + dt = finish_time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + + # Keep updating the iteration counter + if(iterative_code == ""): + self.iteration_counter = 0 + else: + self.iteration_counter = self.iteration_counter + 1 + + # The code should be run for atleast the target time step + # If it's less put to sleep + # If it's more no problem as such, but we can change it! + time_cycle = self.time_cycle.get() + + if(ms < time_cycle): + time.sleep((time_cycle - ms) / 1000.0) + + close_console() + print("Current Thread Joined!") + + + # Function to generate the modules for use in ACE Editor + def generate_modules(self): + # Define HAL module + hal_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) + hal_module.HAL = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) + hal_module.HAL.motors = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("motors", None)) + + # Add HAL functions + hal_module.HAL.get_frontal_image = self.hal.get_frontal_image + hal_module.HAL.get_ventral_image = self.hal.get_ventral_image + hal_module.HAL.takeoff = self.hal.takeoff + hal_module.HAL.land = self.hal.land + hal_module.HAL.set_cmd_pos = self.hal.set_cmd_pos + hal_module.HAL.set_cmd_vel = self.hal.set_cmd_vel + hal_module.HAL.set_cmd_mix = self.hal.set_cmd_mix + hal_module.HAL.get_position = self.hal.get_position + hal_module.HAL.get_velocity = self.hal.get_velocity + hal_module.HAL.get_orientation = self.hal.get_orientation + hal_module.HAL.get_roll = self.hal.get_roll + hal_module.HAL.get_pitch = self.hal.get_pitch + hal_module.HAL.get_yaw = self.hal.get_yaw + hal_module.HAL.get_landed_state = self.hal.get_landed_state + hal_module.HAL.get_yaw_rate = self.hal.get_yaw_rate + + + + # Define GUI module + gui_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) + gui_module.GUI = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) + + # Add GUI functions + gui_module.GUI.showImage = self.gui.showImage + gui_module.GUI.showLeftImage = self.gui.showLeftImage + + # Adding modules to system + # Protip: The names should be different from + # other modules, otherwise some errors + sys.modules["HAL"] = hal_module + sys.modules["GUI"] = gui_module + + return gui_module, hal_module + + # Function to measure the frequency of iterations + def measure_frequency(self): + previous_time = datetime.now() + # An infinite loop + while not self.exit_signal.is_set(): + # Sleep for 2 seconds + time.sleep(2) + + # Measure the current time and subtract from the previous time to get real time interval + current_time = datetime.now() + dt = current_time - previous_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + previous_time = current_time + + # Get the time period + try: + # Division by zero + self.ideal_cycle.add(ms / self.iteration_counter) + except: + self.ideal_cycle.add(0) + + # Reset the counter + self.iteration_counter = 0 \ No newline at end of file diff --git a/exercises/static/exercises/drone_hangar/web-template/exercise.py b/exercises/static/exercises/drone_hangar/web-template/exercise.py old mode 100644 new mode 100755 index 71d5934a8..82bf0c72a --- a/exercises/static/exercises/drone_hangar/web-template/exercise.py +++ b/exercises/static/exercises/drone_hangar/web-template/exercise.py @@ -1,3 +1,5 @@ + + #!/usr/bin/env python from __future__ import print_function @@ -5,6 +7,7 @@ from websocket_server import WebsocketServer import time import threading +import multiprocessing import subprocess import sys from datetime import datetime @@ -14,8 +17,13 @@ import rospy from std_srvs.srv import Empty +import cv2 + +from shared.value import SharedValue +from brain import BrainProcess +import queue + -from gui import GUI, ThreadGUI from hal import HAL from console import start_console, close_console @@ -25,36 +33,65 @@ class Template: # self.ideal_cycle to run an execution for at least 1 second # self.process for the current running process def __init__(self): - self.measure_thread = None - self.thread = None - self.reload = False - self.stop_brain = True - self.user_code = "" + + self.brain_process = None + self.reload = multiprocessing.Event() # Time variables - self.ideal_cycle = 80 - self.measured_cycle = 80 - self.iteration_counter = 0 + self.brain_time_cycle = SharedValue('brain_time_cycle') + self.brain_ideal_cycle = SharedValue('brain_ideal_cycle') self.real_time_factor = 0 + self.frequency_message = {'brain': '', 'gui': '', 'rtf': ''} + # GUI variables + self.gui_time_cycle = SharedValue('gui_time_cycle') + self.gui_ideal_cycle = SharedValue('gui_ideal_cycle') + self.server = None self.client = None self.host = sys.argv[1] - # Initialize the GUI, HAL and Console behind the scenes self.hal = HAL() - self.gui = GUI(self.host) + self.paused = False + # Function to parse the code # A few assumptions: # 1. The user always passes sequential and iterative codes # 2. Only a single infinite loop def parse_code(self, source_code): - sequential_code, iterative_code = self.seperate_seq_iter(source_code) - return iterative_code, sequential_code + # Check for save/load + if(source_code[:5] == "#save"): + source_code = source_code[5:] + self.save_code(source_code) + + return "", "" + + elif(source_code[:5] == "#load"): + source_code = source_code + self.load_code() + self.server.send_message(self.client, source_code) + + return "", "" + + else: + sequential_code, iterative_code = self.seperate_seq_iter(source_code[6:]) + return iterative_code, sequential_code + - # Function to separate the iterative and sequential code + # Function for saving + def save_code(self, source_code): + with open('code/academy.py', 'w') as code_file: + code_file.write(source_code) + + # Function for loading + def load_code(self): + with open('code/academy.py', 'r') as code_file: + source_code = code_file.read() + + return source_code + + # Function to seperate the iterative and sequential code def seperate_seq_iter(self, source_code): if source_code == "": return "", "" @@ -62,8 +99,8 @@ def seperate_seq_iter(self, source_code): # Search for an instance of while True infinite_loop = re.search(r'[^ ]while\s*\(\s*True\s*\)\s*:|[^ ]while\s*True\s*:|[^ ]while\s*1\s*:|[^ ]while\s*\(\s*1\s*\)\s*:', source_code) - # Separate the content inside while True and the other - # (Separating the sequential and iterative part!) + # Seperate the content inside while True and the other + # (Seperating the sequential and iterative part!) try: start_index = infinite_loop.start() iterative_code = source_code[start_index:] @@ -85,137 +122,16 @@ def seperate_seq_iter(self, source_code): return sequential_code, iterative_code - # The process function - def process_code(self, source_code): - # Redirect the information to console - start_console() - - iterative_code, sequential_code = self.parse_code(source_code) - - # print(sequential_code) - # print(iterative_code) - - # The Python exec function - # Run the sequential part - gui_module, hal_module = self.generate_modules() - reference_environment = {"GUI": gui_module, "HAL": hal_module} - while (self.stop_brain == True): - if (self.reload == True): - return - time.sleep(0.1) - exec(sequential_code, reference_environment) - - # Run the iterative part inside template - # and keep the check for flag - while self.reload == False: - while (self.stop_brain == True): - if (self.reload == True): - return - time.sleep(0.1) - - start_time = datetime.now() - - # Execute the iterative portion - exec(iterative_code, reference_environment) - - # Template specifics to run! - finish_time = datetime.now() - dt = finish_time - start_time - ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 - - # Keep updating the iteration counter - if (iterative_code == ""): - self.iteration_counter = 0 - else: - self.iteration_counter = self.iteration_counter + 1 - - # The code should be run for atleast the target time step - # If it's less put to sleep - if (ms < self.ideal_cycle): - time.sleep((self.ideal_cycle - ms) / 1000.0) - - close_console() - print("Current Thread Joined!") - - # Function to generate the modules for use in ACE Editor - def generate_modules(self): - # Define HAL module - hal_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) - hal_module.HAL = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) - # hal_module.drone = imp.new_module("drone") - # motors# hal_module.HAL.motors = imp.new_module("motors") - - # Add HAL functions - hal_module.HAL.get_frontal_image = self.hal.get_frontal_image - hal_module.HAL.get_ventral_image = self.hal.get_ventral_image - hal_module.HAL.get_position = self.hal.get_position - hal_module.HAL.get_velocity = self.hal.get_velocity - hal_module.HAL.get_yaw_rate = self.hal.get_yaw_rate - hal_module.HAL.get_orientation = self.hal.get_orientation - hal_module.HAL.get_roll = self.hal.get_roll - hal_module.HAL.get_pitch = self.hal.get_pitch - hal_module.HAL.get_yaw = self.hal.get_yaw - hal_module.HAL.get_landed_state = self.hal.get_landed_state - hal_module.HAL.set_cmd_pos = self.hal.set_cmd_pos - hal_module.HAL.set_cmd_vel = self.hal.set_cmd_vel - hal_module.HAL.set_cmd_mix = self.hal.set_cmd_mix - hal_module.HAL.takeoff = self.hal.takeoff - hal_module.HAL.land = self.hal.land - - # Define GUI module - gui_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) - gui_module.GUI = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) - - # Add GUI functions - gui_module.GUI.showImage = self.gui.showImage - gui_module.GUI.showLeftImage = self.gui.showLeftImage - - # Adding modules to system - # Protip: The names should be different from - # other modules, otherwise some errors - sys.modules["HAL"] = hal_module - sys.modules["GUI"] = gui_module - - return gui_module, hal_module - - # Function to measure the frequency of iterations - def measure_frequency(self): - previous_time = datetime.now() - # An infinite loop - while True: - # Sleep for 2 seconds - time.sleep(2) - - # Measure the current time and subtract from the previous time to get real time interval - current_time = datetime.now() - dt = current_time - previous_time - ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 - previous_time = current_time - - # Get the time period - try: - # Division by zero - self.measured_cycle = ms / self.iteration_counter - except: - self.measured_cycle = 0 - - # Reset the counter - self.iteration_counter = 0 - - # Send to client - self.send_frequency_message() - - # Function to generate and send frequency messages def send_frequency_message(self): # This function generates and sends frequency measures of the brain and gui - brain_frequency = 0; gui_frequency = 0 + brain_frequency = 0;gui_frequency = 0 try: - brain_frequency = round(1000 / self.measured_cycle, 1) + brain_frequency = round(1000 / self.brain_ideal_cycle.get(), 1) except ZeroDivisionError: brain_frequency = 0 try: - gui_frequency = round(1000 / self.thread_gui.measured_cycle, 1) + gui_frequency = round(1000 / self.gui_ideal_cycle.get(), 1) except ZeroDivisionError: gui_frequency = 0 @@ -240,29 +156,32 @@ def track_stats(self): args = ["gz", "stats", "-p"] # Prints gz statistics. "-p": Output comma-separated values containing- # real-time factor (percent), simtime (sec), realtime (sec), paused (T or F) - stats_process = subprocess.Popen(args, stdout=subprocess.PIPE, bufsize=1, universal_newlines=True) + stats_process = subprocess.Popen(args, stdout=subprocess.PIPE, bufsize=0) # bufsize=1 enables line-bufferred mode (the input buffer is flushed # automatically on newlines if you would write to process.stdin ) with stats_process.stdout: - for line in iter(stats_process.stdout.readline, ''): - stats_list = [x.strip() for x in line.split(',')] - self.real_time_factor = stats_list[0] + for line in iter(stats_process.stdout.readline, b''): + stats_list = [x.strip() for x in line.split(b',')] + self.real_time_factor = stats_list[0].decode("utf-8") # Function to maintain thread execution def execute_thread(self, source_code): # Keep checking until the thread is alive # The thread will die when the coming iteration reads the flag - if self.thread is not None: - while self.thread.is_alive(): - time.sleep(0.2) + if(self.brain_process != None): + while self.brain_process.is_alive(): + pass # Turn the flag down, the iteration has successfully stopped! - self.reload = False + self.reload.clear() # New thread execution - self.thread = threading.Thread(target=self.process_code, args=[source_code]) - self.thread.start() + code = self.parse_code(source_code) + if code[0] == "" and code[1] == "": + return + + self.brain_process = BrainProcess(code, self.reload) + self.brain_process.start() self.send_code_message() - print("New Thread Started!") # Function to read and set frequency from incoming message def read_frequency_message(self, message): @@ -270,66 +189,58 @@ def read_frequency_message(self, message): # Set brain frequency frequency = float(frequency_message["brain"]) - self.ideal_cycle = 1000.0 / frequency + self.brain_time_cycle.add(1000.0 / frequency) # Set gui frequency frequency = float(frequency_message["gui"]) - self.thread_gui.ideal_cycle = 1000.0 / frequency + self.gui_time_cycle.add(1000.0 / frequency) return # The websocket function # Gets called when there is an incoming message from the client def handle(self, client, server, message): - if message[:5] == "#freq": + if(message[:5] == "#freq"): frequency_message = message[5:] self.read_frequency_message(frequency_message) time.sleep(1) + self.send_frequency_message() return - elif(message[:5] == "#ping"): time.sleep(1) self.send_ping_message() return - - elif (message[:5] == "#code"): + elif (message[:5] == "#code"): try: # Once received turn the reload flag up and send it to execute_thread function - self.user_code = message[6:] + code = message # print(repr(code)) - self.reload = True - self.execute_thread(self.user_code) + self.reload.set() + self.execute_thread(code) except: pass - elif (message[:5] == "#rest"): + elif (message[:5] == "#stop"): try: - self.reload = True - self.stop_brain = True - self.execute_thread(self.user_code) + self.reload.set() + self.execute_thread(code) except: pass - - elif (message[:5] == "#stop"): - self.stop_brain = True - - elif (message[:5] == "#play"): - self.stop_brain = False + self.server.send_message(self.client, "#stpd") # Function that gets called when the server is connected def connected(self, client, server): self.client = client - # Start the GUI update thread - self.thread_gui = ThreadGUI(self.gui) - self.thread_gui.start() + # Start the HAL update thread + self.hal.start_thread() - # Start the real time factor tracker thread + # Start real time factor tracker thread self.stats_thread = threading.Thread(target=self.track_stats) self.stats_thread.start() - # Start measure frequency - self.measure_thread = threading.Thread(target=self.measure_frequency) - self.measure_thread.start() + # Initialize the ping message + self.send_frequency_message() + print("After sneding freweq msg") print(client, 'connected') diff --git a/exercises/static/exercises/drone_hangar/web-template/gui.py b/exercises/static/exercises/drone_hangar/web-template/gui.py old mode 100644 new mode 100755 index 51d327f1a..2b2e10176 --- a/exercises/static/exercises/drone_hangar/web-template/gui.py +++ b/exercises/static/exercises/drone_hangar/web-template/gui.py @@ -5,15 +5,23 @@ import time from datetime import datetime from websocket_server import WebsocketServer +import logging +import rospy +import cv2 +import sys +import numpy as np +import multiprocessing +from shared.image import SharedImage +from shared.value import SharedValue # Graphical User Interface Class class GUI: # Initialization function # The actual initialization def __init__(self, host): - t = threading.Thread(target=self.run_server) + rospy.init_node("GUI") self.payload = {'image': ''} self.left_payload = {'image': ''} self.server = None @@ -21,81 +29,47 @@ def __init__(self, host): self.host = host - # Image variables - self.image_to_be_shown = None - self.image_to_be_shown_updated = False - self.image_show_lock = threading.Lock() - - self.left_image_to_be_shown = None - self.left_image_to_be_shown_updated = False - self.left_image_show_lock = threading.Lock() - - self.acknowledge = False - self.acknowledge_lock = threading.Lock() + # Image variable host + self.shared_image = SharedImage("guifrontalimage") + self.shared_left_image = SharedImage("guiventralimage") - # Take the console object to set the same websocket and client + # Event objects for multiprocessing + self.ack_event = multiprocessing.Event() + self.cli_event = multiprocessing.Event() + + # Start server thread + t = threading.Thread(target=self.run_server) t.start() - # Explicit initialization function - # Class method, so user can call it without instantiation - @classmethod - def initGUI(cls, host): - # self.payload = {'image': '', 'shape': []} - new_instance = cls(host) - return new_instance # Function to prepare image payload # Encodes the image as a JSON string and sends through the WS def payloadImage(self): - self.image_show_lock.acquire() - image_to_be_shown_updated = self.image_to_be_shown_updated - image_to_be_shown = self.image_to_be_shown - self.image_show_lock.release() - - image = image_to_be_shown + image = self.shared_image.get() payload = {'image': '', 'shape': ''} - - if not image_to_be_shown_updated: - return payload - + shape = image.shape frame = cv2.imencode('.JPEG', image)[1] encoded_image = base64.b64encode(frame) - + payload['image'] = encoded_image.decode('utf-8') payload['shape'] = shape - - self.image_show_lock.acquire() - self.image_to_be_shown_updated = False - self.image_show_lock.release() - + return payload - + # Function to prepare image payload # Encodes the image as a JSON string and sends through the WS def payloadLeftImage(self): - self.left_image_show_lock.acquire() - left_image_to_be_shown_updated = self.left_image_to_be_shown_updated - left_image_to_be_shown = self.left_image_to_be_shown - self.left_image_show_lock.release() - - image = left_image_to_be_shown + image = self.shared_left_image.get() payload = {'image': '', 'shape': ''} - - if not left_image_to_be_shown_updated: - return payload - + shape = image.shape frame = cv2.imencode('.JPEG', image)[1] encoded_image = base64.b64encode(frame) - + payload['image'] = encoded_image.decode('utf-8') payload['shape'] = shape - - self.left_image_show_lock.acquire() - self.left_image_to_be_shown_updated = False - self.left_image_show_lock.release() - + return payload # Function for student to call @@ -116,20 +90,10 @@ def showLeftImage(self, image): # Called when a new client is received def get_client(self, client, server): self.client = client + self.cli_event.set() + + print(client, 'connected') - # Function to get value of Acknowledge - def get_acknowledge(self): - self.acknowledge_lock.acquire() - acknowledge = self.acknowledge - self.acknowledge_lock.release() - - return acknowledge - - # Function to get value of Acknowledge - def set_acknowledge(self, value): - self.acknowledge_lock.acquire() - self.acknowledge = value - self.acknowledge_lock.release() # Update the gui def update_gui(self): @@ -151,14 +115,23 @@ def update_gui(self): # Gets called when there is an incoming message from the client def get_message(self, client, server, message): # Acknowledge Message for GUI Thread - if message[:4] == "#ack": - self.set_acknowledge(True) + + if(message[:4] == "#ack"): + # Set acknowledgement flag + self.ack_event.set() + + # Function that gets called when the connected closes + def handle_close(self, client, server): + print(client, 'closed') + + # Activate the server def run_server(self): self.server = WebsocketServer(port=2303, host=self.host) self.server.set_fn_new_client(self.get_client) self.server.set_fn_message_received(self.get_message) + self.server.set_fn_client_left(self.handle_close) logged = False while not logged: @@ -179,32 +152,45 @@ def reset_gui(self): # This class decouples the user thread # and the GUI update thread -class ThreadGUI: - def __init__(self, gui): - self.gui = gui +class ProcessGUI(multiprocessing.Process): + def __init__(self): + super(ProcessGUI, self).__init__() + self.host = sys.argv[1] # Time variables - self.ideal_cycle = 80 - self.measured_cycle = 80 + self.time_cycle = SharedValue("gui_time_cycle") + self.ideal_cycle = SharedValue("gui_ideal_cycle") self.iteration_counter = 0 + # Function to initialize events + def initialize_events(self): + # Events + self.ack_event = self.gui.ack_event + self.cli_event = self.gui.cli_event + self.exit_signal = multiprocessing.Event() + # Function to start the execution of threads - def start(self): + def run(self): + # Initialize GUI + self.gui = GUI(self.host) + self.initialize_events() + + # Wait for client before starting + self.cli_event.wait() self.measure_thread = threading.Thread(target=self.measure_thread) - self.thread = threading.Thread(target=self.run) + self.thread = threading.Thread(target=self.run_gui) self.measure_thread.start() self.thread.start() - print("GUI Thread Started!") + print("GUI Process Started!") + + self.exit_signal.wait() # The measuring thread to measure frequency def measure_thread(self): - while self.gui.client is None: - pass - previous_time = datetime.now() - while True: + while(True): # Sleep for 2 seconds time.sleep(2) @@ -217,32 +203,40 @@ def measure_thread(self): # Get the time period try: # Division by zero - self.measured_cycle = ms / self.iteration_counter + self.ideal_cycle.add(ms / self.iteration_counter) except: - self.measured_cycle = 0 + self.ideal_cycle.add(0) # Reset the counter self.iteration_counter = 0 # The main thread of execution - def run(self): - while self.gui.client is None: - pass - - while True: + def run_gui(self): + while(True): start_time = datetime.now() + # Send update signal self.gui.update_gui() - acknowledge_message = self.gui.get_acknowledge() - - while not acknowledge_message: - acknowledge_message = self.gui.get_acknowledge() - self.gui.set_acknowledge(False) + # Wait for acknowldege signal + self.ack_event.wait() + self.ack_event.clear() finish_time = datetime.now() self.iteration_counter = self.iteration_counter + 1 dt = finish_time - start_time ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 - if ms < self.ideal_cycle: - time.sleep((self.ideal_cycle-ms) / 1000.0) + time_cycle = self.time_cycle.get() + + if(ms < time_cycle): + time.sleep((time_cycle-ms) / 1000.0) + + self.exit_signal.set() + + # Functions to handle auxillary GUI functions + def reset_gui(self): + self.gui.reset_gui() + +if __name__ == "__main__": + gui = ProcessGUI() + gui.start() \ No newline at end of file diff --git a/exercises/static/exercises/drone_hangar/web-template/hal.py b/exercises/static/exercises/drone_hangar/web-template/hal.py old mode 100644 new mode 100755 index 25635a119..17c39cd6b --- a/exercises/static/exercises/drone_hangar/web-template/hal.py +++ b/exercises/static/exercises/drone_hangar/web-template/hal.py @@ -1,9 +1,12 @@ -import numpy as np import rospy import cv2 +import threading +import time +from datetime import datetime from drone_wrapper import DroneWrapper - +from shared.image import SharedImage +from shared.value import SharedValue # Hardware Abstraction Layer class HAL: @@ -12,71 +15,166 @@ class HAL: def __init__(self): rospy.init_node("HAL") - + + self.shared_frontal_image = SharedImage("halfrontalimage") + self.shared_ventral_image = SharedImage("halventralimage") + self.shared_x = SharedValue("x") + self.shared_y = SharedValue("y") + self.shared_z = SharedValue("z") + self.shared_takeoff_z = SharedValue("sharedtakeoffz") + self.shared_az = SharedValue("az") + self.shared_azt = SharedValue("azt") + self.shared_vx = SharedValue("vx") + self.shared_vy = SharedValue("vy") + self.shared_vz = SharedValue("vz") + self.shared_landed_state = SharedValue("landedstate") + self.shared_position = SharedValue("position") + self.shared_velocity = SharedValue("velocity") + self.shared_orientation = SharedValue("orientation") + self.shared_roll = SharedValue("roll") + self.shared_pitch = SharedValue("pitch") + self.shared_yaw = SharedValue("yaw") + self.shared_yaw_rate = SharedValue("yawrate") + self.image = None - self.drone = DroneWrapper(name="rqt") + self.drone = DroneWrapper(name="rqt",ns="/iris/") + + # Update thread + self.thread = ThreadHAL(self.update_hal) # Explicit initialization functions # Class method, so user can call it without instantiation - @classmethod - def initRobot(cls): - new_instance = cls() - return new_instance + + + # Function to start the update thread + def start_thread(self): + self.thread.start() # Get Image from ROS Driver Camera def get_frontal_image(self): image = self.drone.get_frontal_image() image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) - return image_rgb + self.shared_frontal_image.add(image_rgb) def get_ventral_image(self): image = self.drone.get_ventral_image() image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) - return image_rgb + self.shared_ventral_image.add(image_rgb) def get_position(self): pos = self.drone.get_position() - return pos + self.shared_position.add(pos,type_name="list") def get_velocity(self): vel = self.drone.get_velocity() - return vel + self.shared_velocity.add(vel ,type_name="list") def get_yaw_rate(self): yaw_rate = self.drone.get_yaw_rate() - return yaw_rate + self.shared_yaw_rate.add(yaw_rate) def get_orientation(self): orientation = self.drone.get_orientation() - return orientation + self.shared_orientation.add(orientation ,type_name="list") def get_roll(self): roll = self.drone.get_roll() - return roll + self.shared_roll.add(roll) def get_pitch(self): pitch = self.drone.get_pitch() - return pitch + self.shared_pitch.add(pitch) def get_yaw(self): yaw = self.drone.get_yaw() - return yaw + self.shared_yaw.add(yaw) def get_landed_state(self): state = self.drone.get_landed_state() - return state + self.shared_landed_state.add(state) + + def set_cmd_pos(self): + x = self.shared_x.get() + y = self.shared_y.get() + z = self.shared_z.get() + az = self.shared_az.get() - def set_cmd_pos(self, x, y, z, az): self.drone.set_cmd_pos(x, y, z, az) - def set_cmd_vel(self, vx, vy, vz, az): + def set_cmd_vel(self): + vx = self.shared_vx.get() + vy = self.shared_vy.get() + vz = self.shared_vz.get() + az = self.shared_azt.get() self.drone.set_cmd_vel(vx, vy, vz, az) - def set_cmd_mix(self, vx, vy, z, az): + def set_cmd_mix(self): + vx = self.shared_vx.get() + vy = self.shared_vy.get() + z = self.shared_z.get() + az = self.shared_azt.get() self.drone.set_cmd_mix(vx, vy, z, az) - def takeoff(self, h=3): + def takeoff(self): + h = self.shared_takeoff_z.get() self.drone.takeoff(h) def land(self): self.drone.land() + + def update_hal(self): + self.get_frontal_image() + self.get_ventral_image() + self.get_position() + self.get_velocity() + self.get_yaw_rate() + self.get_orientation() + self.get_pitch() + self.get_roll() + self.get_yaw() + self.get_landed_state() + self.set_cmd_pos() + self.set_cmd_vel() + self.set_cmd_mix() + + # Destructor function to close all fds + def __del__(self): + self.shared_frontal_image.close() + self.shared_ventral_image.close() + self.shared_x.close() + self.shared_y.close() + self.shared_z.close() + self.shared_takeoff_z.close() + self.shared_az.close() + self.shared_azt.close() + self.shared_vx.close() + self.shared_vy.close() + self.shared_vz.close() + self.shared_landed_state.close() + self.shared_position.close() + self.shared_velocity.close() + self.shared_orientation.close() + self.shared_roll.close() + self.shared_pitch.close() + self.shared_yaw.close() + self.shared_yaw_rate.close() + +class ThreadHAL(threading.Thread): + def __init__(self, update_function): + super(ThreadHAL, self).__init__() + self.time_cycle = 80 + self.update_function = update_function + + def run(self): + while(True): + start_time = datetime.now() + + self.update_function() + + finish_time = datetime.now() + + dt = finish_time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + + if(ms < self.time_cycle): + time.sleep((self.time_cycle - ms) / 1000.0) \ No newline at end of file diff --git a/exercises/static/exercises/drone_hangar/web-template/launch/drone_hangar.launch b/exercises/static/exercises/drone_hangar/web-template/launch/drone_hangar.launch new file mode 100755 index 000000000..cbd0163c9 --- /dev/null +++ b/exercises/static/exercises/drone_hangar/web-template/launch/drone_hangar.launch @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/exercises/static/exercises/drone_hangar/web-template/launch/gazebo.launch b/exercises/static/exercises/drone_hangar/web-template/launch/gazebo.launch deleted file mode 100644 index 94c0670e1..000000000 --- a/exercises/static/exercises/drone_hangar/web-template/launch/gazebo.launch +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/exercises/static/exercises/drone_hangar/web-template/launch/launch.py b/exercises/static/exercises/drone_hangar/web-template/launch/launch.py deleted file mode 100644 index 4fc8f94d8..000000000 --- a/exercises/static/exercises/drone_hangar/web-template/launch/launch.py +++ /dev/null @@ -1,131 +0,0 @@ -#!/usr/bin/env python3 - -import stat -import rospy -from os import lstat -from subprocess import Popen, PIPE - - -DRI_PATH = "/dev/dri/card0" -EXERCISE = "drone_hangar" -TIMEOUT = 30 -MAX_ATTEMPT = 2 - - -# Check if acceleration can be enabled -def check_device(device_path): - try: - return stat.S_ISCHR(lstat(device_path)[stat.ST_MODE]) - except: - return False - - -# Spawn new process -def spawn_process(args, insert_vglrun=False): - if insert_vglrun: - args.insert(0, "vglrun") - process = Popen(args, stdout=PIPE, bufsize=1, universal_newlines=True) - return process - - -class Test(): - def gazebo(self): - rospy.logwarn("[GAZEBO] Launching") - try: - rospy.wait_for_service("/gazebo/get_model_properties", TIMEOUT) - return True - except rospy.ROSException: - return False - - def px4(self): - rospy.logwarn("[PX4-SITL] Launching") - start_time = rospy.get_time() - args = ["./PX4-Autopilot/build/px4_sitl_default/bin/px4-commander","--instance", "0", "check"] - while rospy.get_time() - start_time < TIMEOUT: - process = spawn_process(args, insert_vglrun=False) - with process.stdout: - for line in iter(process.stdout.readline, ''): - if ("Prearm check: OK" in line): - return True - rospy.sleep(2) - return False - - def mavros(self, ns=""): - rospy.logwarn("[MAVROS] Launching") - try: - rospy.wait_for_service(ns + "/mavros/cmd/arming", TIMEOUT) - return True - except rospy.ROSException: - return False - - -class Launch(): - def __init__(self): - self.test = Test() - self.acceleration_enabled = check_device(DRI_PATH) - - # Start roscore - args = ["/opt/ros/noetic/bin/roscore"] - spawn_process(args, insert_vglrun=False) - - rospy.init_node("launch", anonymous=True) - - def start(self): - ######## LAUNCH GAZEBO ######## - args = ["/opt/ros/noetic/bin/roslaunch", - "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/gazebo.launch", - "--wait", - "--log" - ] - - attempt = 1 - while True: - spawn_process(args, insert_vglrun=self.acceleration_enabled) - if self.test.gazebo() == True: - break - if attempt == MAX_ATTEMPT: - rospy.logerr("[GAZEBO] Launch Failed") - return - attempt = attempt + 1 - - - ######## LAUNCH PX4 ######## - args = ["/opt/ros/noetic/bin/roslaunch", - "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/px4.launch", - "--log" - ] - - attempt = 1 - while True: - spawn_process(args, insert_vglrun=self.acceleration_enabled) - if self.test.px4() == True: - break - if attempt == MAX_ATTEMPT: - rospy.logerr("[PX4] Launch Failed") - return - attempt = attempt + 1 - - - ######## LAUNCH MAVROS ######## - args = ["/opt/ros/noetic/bin/roslaunch", - "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/mavros.launch", - "--log" - ] - - attempt = 1 - while True: - spawn_process(args, insert_vglrun=self.acceleration_enabled) - if self.test.mavros() == True: - break - if attempt == MAX_ATTEMPT: - rospy.logerr("[MAVROS] Launch Failed") - return - attempt = attempt + 1 - - -if __name__ == "__main__": - launch = Launch() - launch.start() - - with open("/drones_launch.log", "w") as f: - f.write("success") diff --git a/exercises/static/exercises/drone_hangar/web-template/launch/mavros.launch b/exercises/static/exercises/drone_hangar/web-template/launch/mavros.launch deleted file mode 100644 index b899c0ec1..000000000 --- a/exercises/static/exercises/drone_hangar/web-template/launch/mavros.launch +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/exercises/static/exercises/drone_hangar/web-template/launch/px4.launch b/exercises/static/exercises/drone_hangar/web-template/launch/px4.launch deleted file mode 100644 index 0de35df37..000000000 --- a/exercises/static/exercises/drone_hangar/web-template/launch/px4.launch +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/exercises/static/exercises/drone_hangar/web-template/shared/__init__.py b/exercises/static/exercises/drone_hangar/web-template/shared/__init__.py new file mode 100755 index 000000000..e69de29bb diff --git a/exercises/static/exercises/drone_hangar/web-template/shared/image.py b/exercises/static/exercises/drone_hangar/web-template/shared/image.py new file mode 100755 index 000000000..de5c9f9d6 --- /dev/null +++ b/exercises/static/exercises/drone_hangar/web-template/shared/image.py @@ -0,0 +1,109 @@ +import numpy as np +import mmap +from posix_ipc import Semaphore, O_CREX, ExistentialError, O_CREAT, SharedMemory, unlink_shared_memory +from ctypes import sizeof, memmove, addressof, create_string_buffer +from shared.structure_img import MD + +# Probably, using self variables gives errors with memmove +# Therefore, a global variable for utility +md_buf = create_string_buffer(sizeof(MD)) + +class SharedImage: + def __init__(self, name): + # Initialize variables for memory regions and buffers and Semaphore + self.shm_buf = None; self.shm_region = None + self.md_buf = None; self.md_region = None + self.image_lock = None + + self.shm_name = name; self.md_name = name + "-meta" + self.image_lock_name = name + + # Initialize or retreive metadata memory region + try: + self.md_region = SharedMemory(self.md_name) + self.md_buf = mmap.mmap(self.md_region.fd, sizeof(MD)) + self.md_region.close_fd() + except ExistentialError: + self.md_region = SharedMemory(self.md_name, O_CREAT, size=sizeof(MD)) + self.md_buf = mmap.mmap(self.md_region.fd, self.md_region.size) + self.md_region.close_fd() + + # Initialize or retreive Semaphore + try: + self.image_lock = Semaphore(self.image_lock_name, O_CREX) + except ExistentialError: + image_lock = Semaphore(self.image_lock_name, O_CREAT) + image_lock.unlink() + self.image_lock = Semaphore(self.image_lock_name, O_CREX) + + self.image_lock.release() + + # Get the shared image + def get(self): + # Define metadata + metadata = MD() + + # Get metadata from the shared region + self.image_lock.acquire() + md_buf[:] = self.md_buf + memmove(addressof(metadata), md_buf, sizeof(metadata)) + self.image_lock.release() + + # Try to retreive the image from shm_buffer + # Otherwise return a zero image + try: + self.shm_region = SharedMemory(self.shm_name) + self.shm_buf = mmap.mmap(self.shm_region.fd, metadata.size) + self.shm_region.close_fd() + + self.image_lock.acquire() + image = np.ndarray(shape=(metadata.shape_0, metadata.shape_1, metadata.shape_2), + dtype='uint8', buffer=self.shm_buf) + self.image_lock.release() + + # Check for a None image + if(image.size == 0): + image = np.zeros((3, 3, 3), np.uint8) + + except ExistentialError: + image = np.zeros((3, 3, 3), np.uint8) + + return image + + # Add the shared image + def add(self, image): + try: + # Get byte size of the image + byte_size = image.nbytes + + # Get the shared memory buffer to read from + if not self.shm_region: + self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=byte_size) + self.shm_buf = mmap.mmap(self.shm_region.fd, byte_size) + self.shm_region.close_fd() + + # Generate meta data + metadata = MD(image.shape[0], image.shape[1], image.shape[2], byte_size) + + # Send the meta data and image to shared regions + self.image_lock.acquire() + memmove(md_buf, addressof(metadata), sizeof(metadata)) + self.md_buf[:] = md_buf[:] + self.shm_buf[:] = image.tobytes() + self.image_lock.release() + except: + pass + + # Destructor function to unlink and disconnect + def close(self): + self.image_lock.acquire() + self.md_buf.close() + + try: + unlink_shared_memory(self.md_name) + unlink_shared_memory(self.shm_name) + except ExistentialError: + pass + + self.image_lock.release() + self.image_lock.close() diff --git a/exercises/static/exercises/drone_hangar/web-template/shared/structure_img.py b/exercises/static/exercises/drone_hangar/web-template/shared/structure_img.py new file mode 100755 index 000000000..ae40b3707 --- /dev/null +++ b/exercises/static/exercises/drone_hangar/web-template/shared/structure_img.py @@ -0,0 +1,9 @@ +from ctypes import Structure, c_int32, c_int64 + +class MD(Structure): + _fields_ = [ + ('shape_0', c_int32), + ('shape_1', c_int32), + ('shape_2', c_int32), + ('size', c_int64) + ] \ No newline at end of file diff --git a/exercises/static/exercises/drone_hangar/web-template/shared/value.py b/exercises/static/exercises/drone_hangar/web-template/shared/value.py new file mode 100755 index 000000000..e7bfad8a2 --- /dev/null +++ b/exercises/static/exercises/drone_hangar/web-template/shared/value.py @@ -0,0 +1,93 @@ +import numpy as np +import mmap +from posix_ipc import Semaphore, O_CREX, ExistentialError, O_CREAT, SharedMemory, unlink_shared_memory +from ctypes import sizeof, memmove, addressof, create_string_buffer, c_float +import struct + +class SharedValue: + def __init__(self, name): + # Initialize varaibles for memory regions and buffers and Semaphore + self.shm_buf = None; self.shm_region = None + self.value_lock = None + + self.shm_name = name; self.value_lock_name = name + + # Initialize or retreive Semaphore + try: + self.value_lock = Semaphore(self.value_lock_name, O_CREX) + except ExistentialError: + value_lock = Semaphore(self.value_lock_name, O_CREAT) + value_lock.unlink() + self.value_lock = Semaphore(self.value_lock_name, O_CREX) + + self.value_lock.release() + + # Get the shared value + def get(self, type_name= "value"): + # Retreive the data from buffer + if type_name=="value": + try: + self.shm_region = SharedMemory(self.shm_name) + self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) + self.shm_region.close_fd() + except ExistentialError: + self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=sizeof(c_float)) + self.shm_buf = mmap.mmap(self.shm_region.fd, self.shm_region.size) + self.shm_region.close_fd() + self.value_lock.acquire() + value = struct.unpack('f', self.shm_buf)[0] + self.value_lock.release() + + return value + elif type_name=="list": + self.shm_region = SharedMemory(self.shm_name) + self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) + self.shm_region.close_fd() + self.value_lock.acquire() + array_val = np.ndarray(shape=(3,), + dtype='float32', buffer=self.shm_buf) + self.value_lock.release() + + return array_val + + else: + print("missing argument for return type") + + + # Add the shared value + def add(self, value, type_name= "value"): + # Send the data to shared regions + if type_name=="value": + try: + self.shm_region = SharedMemory(self.shm_name) + self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) + self.shm_region.close_fd() + except ExistentialError: + self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=sizeof(c_float)) + self.shm_buf = mmap.mmap(self.shm_region.fd, self.shm_region.size) + self.shm_region.close_fd() + + self.value_lock.acquire() + self.shm_buf[:] = struct.pack('f', value) + self.value_lock.release() + elif type_name=="list": + byte_size = value.nbytes + self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=byte_size) + self.shm_buf = mmap.mmap(self.shm_region.fd, byte_size) + self.shm_region.close_fd() + self.value_lock.acquire() + self.shm_buf[:] = value.tobytes() + self.value_lock.release() + + # Destructor function to unlink and disconnect + def close(self): + self.value_lock.acquire() + self.shm_buf.close() + + try: + unlink_shared_memory(self.shm_name) + except ExistentialError: + pass + + self.value_lock.release() + self.value_lock.close() diff --git a/exercises/static/exercises/drone_hangar/web-template/user_functions.py b/exercises/static/exercises/drone_hangar/web-template/user_functions.py new file mode 100755 index 000000000..d741601fc --- /dev/null +++ b/exercises/static/exercises/drone_hangar/web-template/user_functions.py @@ -0,0 +1,117 @@ +from shared.image import SharedImage +from shared.value import SharedValue +import numpy as np +import cv2 + +# Define HAL functions +class HALFunctions: + def __init__(self): + # Initialize image variable + self.shared_frontal_image = SharedImage("halfrontalimage") + self.shared_ventral_image = SharedImage("halventralimage") + self.shared_x = SharedValue("x") + self.shared_y = SharedValue("y") + self.shared_z = SharedValue("z") + self.shared_takeoff_z = SharedValue("sharedtakeoffz") + self.shared_az = SharedValue("az") + self.shared_azt = SharedValue("azt") + self.shared_vx = SharedValue("vx") + self.shared_vy = SharedValue("vy") + self.shared_vz = SharedValue("vz") + self.shared_landed_state = SharedValue("landedstate") + self.shared_position = SharedValue("position") + self.shared_velocity = SharedValue("velocity") + self.shared_orientation = SharedValue("orientation") + self.shared_roll = SharedValue("roll") + self.shared_pitch = SharedValue("pitch") + self.shared_yaw = SharedValue("yaw") + self.shared_yaw_rate = SharedValue("yawrate") + + + # Get image function + def get_frontal_image(self): + image = self.shared_frontal_image.get() + return image + + # Get left image function + def get_ventral_image(self): + image = self.shared_ventral_image.get() + return image + + def takeoff(self, height): + self.shared_takeoff_z.add(height) + + def land(self): + pass + + def set_cmd_pos(self, x, y , z, az): + self.shared_x.add(x) + self.shared_y.add(y) + self.shared_z.add(z) + self.shared_az.add(az) + + def set_cmd_vel(self, vx, vy, vz, az): + self.shared_vx.add(vx) + self.shared_vy.add(vy) + self.shared_vz.add(vz) + self.shared_azt.add(az) + + def set_cmd_mix(self, vx, vy, z, az): + self.shared_vx.add(vx) + self.shared_vy.add(vy) + self.shared_vz.add(z) + self.shared_azt.add(az) + + + def get_position(self): + position = self.shared_position.get(type_name = "list") + return position + + def get_velocity(self): + velocity = self.shared_velocity.get(type_name = "list") + return velocity + + def get_yaw_rate(self): + yaw_rate = self.shared_yaw_rate.get(type_name = "value") + return yaw_rate + + def get_orientation(self): + orientation = self.shared_orientation.get(type_name = "list") + return orientation + + def get_roll(self): + roll = self.shared_roll.get(type_name = "value") + return roll + + def get_pitch(self): + pitch = self.shared_pitch.get(type_name = "value") + return pitch + + def get_yaw(self): + yaw = self.shared_yaw.get(type_name = "value") + return yaw + + def get_landed_state(self): + landed_state = self.shared_landed_state.get(type_name = "value") + return landed_state + +# Define GUI functions +class GUIFunctions: + def __init__(self): + # Initialize image variable + self.shared_image = SharedImage("guifrontalimage") + self.shared_left_image = SharedImage("guiventralimage") + + # Show image function + def showImage(self, image): + # Reshape to 3 channel if it has only 1 in order to display it + if (len(image.shape) < 3): + image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB) + self.shared_image.add(image) + + # Show left image function + def showLeftImage(self, image): + # Reshape to 3 channel if it has only 1 in order to display it + if (len(image.shape) < 3): + image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB) + self.shared_left_image.add(image) \ No newline at end of file diff --git a/scripts/instructions.json b/scripts/instructions.json index 52bb0a297..ec42cb9b1 100644 --- a/scripts/instructions.json +++ b/scripts/instructions.json @@ -83,8 +83,9 @@ }, "drone_hangar": { "gazebo_path": "/RoboticsAcademy/exercises/drone_hangar/web-template/launch", - "instructions_ros": ["python3 ./RoboticsAcademy/exercises/drone_hangar/web-template/launch/launch.py"], - "instructions_host": "python3 /RoboticsAcademy/exercises/drone_hangar/web-template/exercise.py 0.0.0.0" + "instructions_ros": ["/opt/ros/noetic/bin/roslaunch ./RoboticsAcademy/exercises/drone_hangar/web-template/launch/drone_hangar.launch"], + "instructions_host": "python3 /RoboticsAcademy/exercises/drone_hangar/web-template/exercise.py 0.0.0.0", + "instructions_gui": "python3 /RoboticsAcademy/exercises/drone_hangar/web-template/gui.py 0.0.0.0 {}" }, "drone_gymkhana": { "gazebo_path": "/RoboticsAcademy/exercises/drone_gymkhana/web-template/launch", From 8302ddf3f17bce9e4955fe1b3bcf23d78a34cafe Mon Sep 17 00:00:00 2001 From: RUFFY-369 Date: Sun, 11 Sep 2022 22:12:59 +0530 Subject: [PATCH 18/42] drone hangar final frontend --- .../drone_hangar/web-template/drone_hangar.world | 14 +++++++++++++- .../web-template/launch/drone_hangar.launch | 2 +- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/exercises/static/exercises/drone_hangar/web-template/drone_hangar.world b/exercises/static/exercises/drone_hangar/web-template/drone_hangar.world index 48ff09858..b4f62b1a0 100644 --- a/exercises/static/exercises/drone_hangar/web-template/drone_hangar.world +++ b/exercises/static/exercises/drone_hangar/web-template/drone_hangar.world @@ -13,6 +13,7 @@ model://hangar 0 -2 0 0 0 0 + 4 4 4 model://wall1 @@ -26,11 +27,22 @@ model://wall3 0 -6 0 0 0 0 + - model://wall4 + model://cylinder1 0 -6 0 0 0 0 + + model://cylinder2 + 0 -6 2 0 0 0 + + + + model://cylinder3 + 0 -3 2 0 0 0 + + model://sun diff --git a/exercises/static/exercises/drone_hangar/web-template/launch/drone_hangar.launch b/exercises/static/exercises/drone_hangar/web-template/launch/drone_hangar.launch index cbd0163c9..a98173e56 100755 --- a/exercises/static/exercises/drone_hangar/web-template/launch/drone_hangar.launch +++ b/exercises/static/exercises/drone_hangar/web-template/launch/drone_hangar.launch @@ -16,7 +16,7 @@ - + From e5d83cc84af1bade1a4b9e770736ce5029fac4f1 Mon Sep 17 00:00:00 2001 From: RUFFY-369 Date: Sun, 11 Sep 2022 22:32:05 +0530 Subject: [PATCH 19/42] final multi processing for labyrinth escape --- .../labyrinth_escape/web-template/brain.py | 166 +++++++++++ .../labyrinth_escape/web-template/exercise.py | 267 ++++++------------ .../labyrinth_escape/web-template/gui.py | 182 ++++++------ .../labyrinth_escape/web-template/hal.py | 142 ++++++++-- .../web-template/launch/gazebo.launch | 24 -- .../launch/labyrinth_escape.launch | 60 ++++ .../web-template/launch/launch.py | 131 --------- .../web-template/launch/mavros.launch | 13 - .../web-template/launch/px4.launch | 20 -- .../web-template/shared/__init__.py | 0 .../web-template/shared/image.py | 109 +++++++ .../web-template/shared/structure_img.py | 9 + .../web-template/shared/value.py | 93 ++++++ .../web-template/user_functions.py | 117 ++++++++ scripts/instructions.json | 5 +- 15 files changed, 854 insertions(+), 484 deletions(-) create mode 100755 exercises/static/exercises/labyrinth_escape/web-template/brain.py mode change 100644 => 100755 exercises/static/exercises/labyrinth_escape/web-template/exercise.py mode change 100644 => 100755 exercises/static/exercises/labyrinth_escape/web-template/gui.py mode change 100644 => 100755 exercises/static/exercises/labyrinth_escape/web-template/hal.py delete mode 100644 exercises/static/exercises/labyrinth_escape/web-template/launch/gazebo.launch create mode 100755 exercises/static/exercises/labyrinth_escape/web-template/launch/labyrinth_escape.launch delete mode 100644 exercises/static/exercises/labyrinth_escape/web-template/launch/launch.py delete mode 100644 exercises/static/exercises/labyrinth_escape/web-template/launch/mavros.launch delete mode 100644 exercises/static/exercises/labyrinth_escape/web-template/launch/px4.launch create mode 100755 exercises/static/exercises/labyrinth_escape/web-template/shared/__init__.py create mode 100755 exercises/static/exercises/labyrinth_escape/web-template/shared/image.py create mode 100755 exercises/static/exercises/labyrinth_escape/web-template/shared/structure_img.py create mode 100755 exercises/static/exercises/labyrinth_escape/web-template/shared/value.py create mode 100755 exercises/static/exercises/labyrinth_escape/web-template/user_functions.py diff --git a/exercises/static/exercises/labyrinth_escape/web-template/brain.py b/exercises/static/exercises/labyrinth_escape/web-template/brain.py new file mode 100755 index 000000000..2330c04ba --- /dev/null +++ b/exercises/static/exercises/labyrinth_escape/web-template/brain.py @@ -0,0 +1,166 @@ +import time +import threading +import multiprocessing +import sys +from datetime import datetime +import re +import json +import importlib + +import rospy +from std_srvs.srv import Empty +import cv2 + +from user_functions import GUIFunctions, HALFunctions +from console import start_console, close_console + +from shared.value import SharedValue + +# The brain process class +class BrainProcess(multiprocessing.Process): + def __init__(self, code, exit_signal): + super(BrainProcess, self).__init__() + + # Initialize exit signal + self.exit_signal = exit_signal + + # Function definitions for users to use + self.hal = HALFunctions() + self.gui = GUIFunctions() + + # Time variables + self.time_cycle = SharedValue('brain_time_cycle') + self.ideal_cycle = SharedValue('brain_ideal_cycle') + self.iteration_counter = 0 + + # Get the sequential and iterative code + # Something wrong over here! The code is reversing + # Found a solution but could not find the reason for this (parse_code function's return line of exercise.py is the reason) + self.sequential_code = code[1] + self.iterative_code = code[0] + + # Function to run to start the process + def run(self): + # Two threads for running and measuring + self.measure_thread = threading.Thread(target=self.measure_frequency) + self.thread = threading.Thread(target=self.process_code) + + self.measure_thread.start() + self.thread.start() + + print("Brain Process Started!") + + self.exit_signal.wait() + + # The process function + def process_code(self): + # Redirect information to console + start_console() + + # Reference Environment for the exec() function + iterative_code, sequential_code = self.iterative_code, self.sequential_code + + # The Python exec function + # Run the sequential part + gui_module, hal_module = self.generate_modules() + if sequential_code != "": + reference_environment = {"GUI": gui_module, "HAL": hal_module} + exec(sequential_code, reference_environment) + + # Run the iterative part inside template + # and keep the check for flag + while not self.exit_signal.is_set(): + start_time = datetime.now() + + # Execute the iterative portion + if iterative_code != "": + exec(iterative_code, reference_environment) + + # Template specifics to run! + finish_time = datetime.now() + dt = finish_time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + + # Keep updating the iteration counter + if(iterative_code == ""): + self.iteration_counter = 0 + else: + self.iteration_counter = self.iteration_counter + 1 + + # The code should be run for atleast the target time step + # If it's less put to sleep + # If it's more no problem as such, but we can change it! + time_cycle = self.time_cycle.get() + + if(ms < time_cycle): + time.sleep((time_cycle - ms) / 1000.0) + + close_console() + print("Current Thread Joined!") + + + # Function to generate the modules for use in ACE Editor + def generate_modules(self): + # Define HAL module + hal_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) + hal_module.HAL = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) + hal_module.HAL.motors = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("motors", None)) + + # Add HAL functions + hal_module.HAL.get_frontal_image = self.hal.get_frontal_image + hal_module.HAL.get_ventral_image = self.hal.get_ventral_image + hal_module.HAL.takeoff = self.hal.takeoff + hal_module.HAL.land = self.hal.land + hal_module.HAL.set_cmd_pos = self.hal.set_cmd_pos + hal_module.HAL.set_cmd_vel = self.hal.set_cmd_vel + hal_module.HAL.set_cmd_mix = self.hal.set_cmd_mix + hal_module.HAL.get_position = self.hal.get_position + hal_module.HAL.get_velocity = self.hal.get_velocity + hal_module.HAL.get_orientation = self.hal.get_orientation + hal_module.HAL.get_roll = self.hal.get_roll + hal_module.HAL.get_pitch = self.hal.get_pitch + hal_module.HAL.get_yaw = self.hal.get_yaw + hal_module.HAL.get_landed_state = self.hal.get_landed_state + hal_module.HAL.get_yaw_rate = self.hal.get_yaw_rate + + + + # Define GUI module + gui_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) + gui_module.GUI = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) + + # Add GUI functions + gui_module.GUI.showImage = self.gui.showImage + gui_module.GUI.showLeftImage = self.gui.showLeftImage + + # Adding modules to system + # Protip: The names should be different from + # other modules, otherwise some errors + sys.modules["HAL"] = hal_module + sys.modules["GUI"] = gui_module + + return gui_module, hal_module + + # Function to measure the frequency of iterations + def measure_frequency(self): + previous_time = datetime.now() + # An infinite loop + while not self.exit_signal.is_set(): + # Sleep for 2 seconds + time.sleep(2) + + # Measure the current time and subtract from the previous time to get real time interval + current_time = datetime.now() + dt = current_time - previous_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + previous_time = current_time + + # Get the time period + try: + # Division by zero + self.ideal_cycle.add(ms / self.iteration_counter) + except: + self.ideal_cycle.add(0) + + # Reset the counter + self.iteration_counter = 0 \ No newline at end of file diff --git a/exercises/static/exercises/labyrinth_escape/web-template/exercise.py b/exercises/static/exercises/labyrinth_escape/web-template/exercise.py old mode 100644 new mode 100755 index a896ab64f..82bf0c72a --- a/exercises/static/exercises/labyrinth_escape/web-template/exercise.py +++ b/exercises/static/exercises/labyrinth_escape/web-template/exercise.py @@ -1,3 +1,5 @@ + + #!/usr/bin/env python from __future__ import print_function @@ -5,6 +7,7 @@ from websocket_server import WebsocketServer import time import threading +import multiprocessing import subprocess import sys from datetime import datetime @@ -14,8 +17,13 @@ import rospy from std_srvs.srv import Empty +import cv2 + +from shared.value import SharedValue +from brain import BrainProcess +import queue + -from gui import GUI, ThreadGUI from hal import HAL from console import start_console, close_console @@ -25,36 +33,65 @@ class Template: # self.ideal_cycle to run an execution for at least 1 second # self.process for the current running process def __init__(self): - self.measure_thread = None - self.thread = None - self.reload = False - self.stop_brain = True - self.user_code = "" + + self.brain_process = None + self.reload = multiprocessing.Event() # Time variables - self.ideal_cycle = 80 - self.measured_cycle = 80 - self.iteration_counter = 0 + self.brain_time_cycle = SharedValue('brain_time_cycle') + self.brain_ideal_cycle = SharedValue('brain_ideal_cycle') self.real_time_factor = 0 + self.frequency_message = {'brain': '', 'gui': '', 'rtf': ''} + # GUI variables + self.gui_time_cycle = SharedValue('gui_time_cycle') + self.gui_ideal_cycle = SharedValue('gui_ideal_cycle') + self.server = None self.client = None self.host = sys.argv[1] - # Initialize the GUI, HAL and Console behind the scenes self.hal = HAL() - self.gui = GUI(self.host) + self.paused = False + # Function to parse the code # A few assumptions: # 1. The user always passes sequential and iterative codes # 2. Only a single infinite loop def parse_code(self, source_code): - sequential_code, iterative_code = self.seperate_seq_iter(source_code) - return iterative_code, sequential_code + # Check for save/load + if(source_code[:5] == "#save"): + source_code = source_code[5:] + self.save_code(source_code) - # Function to separate the iterative and sequential code + return "", "" + + elif(source_code[:5] == "#load"): + source_code = source_code + self.load_code() + self.server.send_message(self.client, source_code) + + return "", "" + + else: + sequential_code, iterative_code = self.seperate_seq_iter(source_code[6:]) + return iterative_code, sequential_code + + + # Function for saving + def save_code(self, source_code): + with open('code/academy.py', 'w') as code_file: + code_file.write(source_code) + + # Function for loading + def load_code(self): + with open('code/academy.py', 'r') as code_file: + source_code = code_file.read() + + return source_code + + # Function to seperate the iterative and sequential code def seperate_seq_iter(self, source_code): if source_code == "": return "", "" @@ -62,8 +99,8 @@ def seperate_seq_iter(self, source_code): # Search for an instance of while True infinite_loop = re.search(r'[^ ]while\s*\(\s*True\s*\)\s*:|[^ ]while\s*True\s*:|[^ ]while\s*1\s*:|[^ ]while\s*\(\s*1\s*\)\s*:', source_code) - # Separate the content inside while True and the other - # (Separating the sequential and iterative part!) + # Seperate the content inside while True and the other + # (Seperating the sequential and iterative part!) try: start_index = infinite_loop.start() iterative_code = source_code[start_index:] @@ -85,137 +122,16 @@ def seperate_seq_iter(self, source_code): return sequential_code, iterative_code - # The process function - def process_code(self, source_code): - # Redirect the information to console - start_console() - - iterative_code, sequential_code = self.parse_code(source_code) - - # print(sequential_code) - # print(iterative_code) - - # The Python exec function - # Run the sequential part - gui_module, hal_module = self.generate_modules() - reference_environment = {"GUI": gui_module, "HAL": hal_module} - while (self.stop_brain == True): - if (self.reload == True): - return - time.sleep(0.1) - exec(sequential_code, reference_environment) - - # Run the iterative part inside template - # and keep the check for flag - while self.reload == False: - while (self.stop_brain == True): - if (self.reload == True): - return - time.sleep(0.1) - - start_time = datetime.now() - - # Execute the iterative portion - exec(iterative_code, reference_environment) - - # Template specifics to run! - finish_time = datetime.now() - dt = finish_time - start_time - ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 - - # Keep updating the iteration counter - if (iterative_code == ""): - self.iteration_counter = 0 - else: - self.iteration_counter = self.iteration_counter + 1 - - # The code should be run for atleast the target time step - # If it's less put to sleep - if (ms < self.ideal_cycle): - time.sleep((self.ideal_cycle - ms) / 1000.0) - - close_console() - print("Current Thread Joined!") - - # Function to generate the modules for use in ACE Editor - def generate_modules(self): - # Define HAL module - hal_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) - hal_module.HAL = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) - # hal_module.drone = imp.new_module("drone") - # motors# hal_module.HAL.motors = imp.new_module("motors") - - # Add HAL functions - hal_module.HAL.get_frontal_image = self.hal.get_frontal_image - hal_module.HAL.get_ventral_image = self.hal.get_ventral_image - hal_module.HAL.get_position = self.hal.get_position - hal_module.HAL.get_velocity = self.hal.get_velocity - hal_module.HAL.get_yaw_rate = self.hal.get_yaw_rate - hal_module.HAL.get_orientation = self.hal.get_orientation - hal_module.HAL.get_roll = self.hal.get_roll - hal_module.HAL.get_pitch = self.hal.get_pitch - hal_module.HAL.get_yaw = self.hal.get_yaw - hal_module.HAL.get_landed_state = self.hal.get_landed_state - hal_module.HAL.set_cmd_pos = self.hal.set_cmd_pos - hal_module.HAL.set_cmd_vel = self.hal.set_cmd_vel - hal_module.HAL.set_cmd_mix = self.hal.set_cmd_mix - hal_module.HAL.takeoff = self.hal.takeoff - hal_module.HAL.land = self.hal.land - - # Define GUI module - gui_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) - gui_module.GUI = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) - - # Add GUI functions - gui_module.GUI.showImage = self.gui.showImage - gui_module.GUI.showLeftImage = self.gui.showLeftImage - - # Adding modules to system - # Protip: The names should be different from - # other modules, otherwise some errors - sys.modules["HAL"] = hal_module - sys.modules["GUI"] = gui_module - - return gui_module, hal_module - - # Function to measure the frequency of iterations - def measure_frequency(self): - previous_time = datetime.now() - # An infinite loop - while True: - # Sleep for 2 seconds - time.sleep(2) - - # Measure the current time and subtract from the previous time to get real time interval - current_time = datetime.now() - dt = current_time - previous_time - ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 - previous_time = current_time - - # Get the time period - try: - # Division by zero - self.measured_cycle = ms / self.iteration_counter - except: - self.measured_cycle = 0 - - # Reset the counter - self.iteration_counter = 0 - - # Send to client - self.send_frequency_message() - - # Function to generate and send frequency messages def send_frequency_message(self): # This function generates and sends frequency measures of the brain and gui - brain_frequency = 0; gui_frequency = 0 + brain_frequency = 0;gui_frequency = 0 try: - brain_frequency = round(1000 / self.measured_cycle, 1) + brain_frequency = round(1000 / self.brain_ideal_cycle.get(), 1) except ZeroDivisionError: brain_frequency = 0 try: - gui_frequency = round(1000 / self.thread_gui.measured_cycle, 1) + gui_frequency = round(1000 / self.gui_ideal_cycle.get(), 1) except ZeroDivisionError: gui_frequency = 0 @@ -240,29 +156,32 @@ def track_stats(self): args = ["gz", "stats", "-p"] # Prints gz statistics. "-p": Output comma-separated values containing- # real-time factor (percent), simtime (sec), realtime (sec), paused (T or F) - stats_process = subprocess.Popen(args, stdout=subprocess.PIPE, bufsize=1, universal_newlines=True) + stats_process = subprocess.Popen(args, stdout=subprocess.PIPE, bufsize=0) # bufsize=1 enables line-bufferred mode (the input buffer is flushed # automatically on newlines if you would write to process.stdin ) with stats_process.stdout: - for line in iter(stats_process.stdout.readline, ''): - stats_list = [x.strip() for x in line.split(',')] - self.real_time_factor = stats_list[0] + for line in iter(stats_process.stdout.readline, b''): + stats_list = [x.strip() for x in line.split(b',')] + self.real_time_factor = stats_list[0].decode("utf-8") # Function to maintain thread execution def execute_thread(self, source_code): # Keep checking until the thread is alive # The thread will die when the coming iteration reads the flag - if self.thread is not None: - while self.thread.is_alive(): - time.sleep(0.2) + if(self.brain_process != None): + while self.brain_process.is_alive(): + pass # Turn the flag down, the iteration has successfully stopped! - self.reload = False + self.reload.clear() # New thread execution - self.thread = threading.Thread(target=self.process_code, args=[source_code]) - self.thread.start() + code = self.parse_code(source_code) + if code[0] == "" and code[1] == "": + return + + self.brain_process = BrainProcess(code, self.reload) + self.brain_process.start() self.send_code_message() - print("New Thread Started!") # Function to read and set frequency from incoming message def read_frequency_message(self, message): @@ -270,66 +189,58 @@ def read_frequency_message(self, message): # Set brain frequency frequency = float(frequency_message["brain"]) - self.ideal_cycle = 1000.0 / frequency + self.brain_time_cycle.add(1000.0 / frequency) # Set gui frequency frequency = float(frequency_message["gui"]) - self.thread_gui.ideal_cycle = 1000.0 / frequency + self.gui_time_cycle.add(1000.0 / frequency) return # The websocket function # Gets called when there is an incoming message from the client def handle(self, client, server, message): - if message[:5] == "#freq": + if(message[:5] == "#freq"): frequency_message = message[5:] self.read_frequency_message(frequency_message) time.sleep(1) + self.send_frequency_message() return - elif(message[:5] == "#ping"): time.sleep(1) self.send_ping_message() return - - elif (message[:5] == "#code"): + elif (message[:5] == "#code"): try: # Once received turn the reload flag up and send it to execute_thread function - self.user_code = message[6:] + code = message # print(repr(code)) - self.reload = True - self.execute_thread(self.user_code) + self.reload.set() + self.execute_thread(code) except: pass - - elif (message[:5] == "#rest"): + + elif (message[:5] == "#stop"): try: - self.reload = True - self.stop_brain = True - self.execute_thread(self.user_code) + self.reload.set() + self.execute_thread(code) except: pass - - elif (message[:5] == "#stop"): - self.stop_brain = True - - elif (message[:5] == "#play"): - self.stop_brain = False + self.server.send_message(self.client, "#stpd") # Function that gets called when the server is connected def connected(self, client, server): self.client = client - # Start the GUI update thread - self.thread_gui = ThreadGUI(self.gui) - self.thread_gui.start() + # Start the HAL update thread + self.hal.start_thread() - # Start the real time factor tracker thread + # Start real time factor tracker thread self.stats_thread = threading.Thread(target=self.track_stats) self.stats_thread.start() - # Start measure frequency - self.measure_thread = threading.Thread(target=self.measure_frequency) - self.measure_thread.start() + # Initialize the ping message + self.send_frequency_message() + print("After sneding freweq msg") print(client, 'connected') diff --git a/exercises/static/exercises/labyrinth_escape/web-template/gui.py b/exercises/static/exercises/labyrinth_escape/web-template/gui.py old mode 100644 new mode 100755 index 51d327f1a..2b2e10176 --- a/exercises/static/exercises/labyrinth_escape/web-template/gui.py +++ b/exercises/static/exercises/labyrinth_escape/web-template/gui.py @@ -5,15 +5,23 @@ import time from datetime import datetime from websocket_server import WebsocketServer +import logging +import rospy +import cv2 +import sys +import numpy as np +import multiprocessing +from shared.image import SharedImage +from shared.value import SharedValue # Graphical User Interface Class class GUI: # Initialization function # The actual initialization def __init__(self, host): - t = threading.Thread(target=self.run_server) + rospy.init_node("GUI") self.payload = {'image': ''} self.left_payload = {'image': ''} self.server = None @@ -21,81 +29,47 @@ def __init__(self, host): self.host = host - # Image variables - self.image_to_be_shown = None - self.image_to_be_shown_updated = False - self.image_show_lock = threading.Lock() - - self.left_image_to_be_shown = None - self.left_image_to_be_shown_updated = False - self.left_image_show_lock = threading.Lock() - - self.acknowledge = False - self.acknowledge_lock = threading.Lock() + # Image variable host + self.shared_image = SharedImage("guifrontalimage") + self.shared_left_image = SharedImage("guiventralimage") - # Take the console object to set the same websocket and client + # Event objects for multiprocessing + self.ack_event = multiprocessing.Event() + self.cli_event = multiprocessing.Event() + + # Start server thread + t = threading.Thread(target=self.run_server) t.start() - # Explicit initialization function - # Class method, so user can call it without instantiation - @classmethod - def initGUI(cls, host): - # self.payload = {'image': '', 'shape': []} - new_instance = cls(host) - return new_instance # Function to prepare image payload # Encodes the image as a JSON string and sends through the WS def payloadImage(self): - self.image_show_lock.acquire() - image_to_be_shown_updated = self.image_to_be_shown_updated - image_to_be_shown = self.image_to_be_shown - self.image_show_lock.release() - - image = image_to_be_shown + image = self.shared_image.get() payload = {'image': '', 'shape': ''} - - if not image_to_be_shown_updated: - return payload - + shape = image.shape frame = cv2.imencode('.JPEG', image)[1] encoded_image = base64.b64encode(frame) - + payload['image'] = encoded_image.decode('utf-8') payload['shape'] = shape - - self.image_show_lock.acquire() - self.image_to_be_shown_updated = False - self.image_show_lock.release() - + return payload - + # Function to prepare image payload # Encodes the image as a JSON string and sends through the WS def payloadLeftImage(self): - self.left_image_show_lock.acquire() - left_image_to_be_shown_updated = self.left_image_to_be_shown_updated - left_image_to_be_shown = self.left_image_to_be_shown - self.left_image_show_lock.release() - - image = left_image_to_be_shown + image = self.shared_left_image.get() payload = {'image': '', 'shape': ''} - - if not left_image_to_be_shown_updated: - return payload - + shape = image.shape frame = cv2.imencode('.JPEG', image)[1] encoded_image = base64.b64encode(frame) - + payload['image'] = encoded_image.decode('utf-8') payload['shape'] = shape - - self.left_image_show_lock.acquire() - self.left_image_to_be_shown_updated = False - self.left_image_show_lock.release() - + return payload # Function for student to call @@ -116,20 +90,10 @@ def showLeftImage(self, image): # Called when a new client is received def get_client(self, client, server): self.client = client + self.cli_event.set() + + print(client, 'connected') - # Function to get value of Acknowledge - def get_acknowledge(self): - self.acknowledge_lock.acquire() - acknowledge = self.acknowledge - self.acknowledge_lock.release() - - return acknowledge - - # Function to get value of Acknowledge - def set_acknowledge(self, value): - self.acknowledge_lock.acquire() - self.acknowledge = value - self.acknowledge_lock.release() # Update the gui def update_gui(self): @@ -151,14 +115,23 @@ def update_gui(self): # Gets called when there is an incoming message from the client def get_message(self, client, server, message): # Acknowledge Message for GUI Thread - if message[:4] == "#ack": - self.set_acknowledge(True) + + if(message[:4] == "#ack"): + # Set acknowledgement flag + self.ack_event.set() + + # Function that gets called when the connected closes + def handle_close(self, client, server): + print(client, 'closed') + + # Activate the server def run_server(self): self.server = WebsocketServer(port=2303, host=self.host) self.server.set_fn_new_client(self.get_client) self.server.set_fn_message_received(self.get_message) + self.server.set_fn_client_left(self.handle_close) logged = False while not logged: @@ -179,32 +152,45 @@ def reset_gui(self): # This class decouples the user thread # and the GUI update thread -class ThreadGUI: - def __init__(self, gui): - self.gui = gui +class ProcessGUI(multiprocessing.Process): + def __init__(self): + super(ProcessGUI, self).__init__() + self.host = sys.argv[1] # Time variables - self.ideal_cycle = 80 - self.measured_cycle = 80 + self.time_cycle = SharedValue("gui_time_cycle") + self.ideal_cycle = SharedValue("gui_ideal_cycle") self.iteration_counter = 0 + # Function to initialize events + def initialize_events(self): + # Events + self.ack_event = self.gui.ack_event + self.cli_event = self.gui.cli_event + self.exit_signal = multiprocessing.Event() + # Function to start the execution of threads - def start(self): + def run(self): + # Initialize GUI + self.gui = GUI(self.host) + self.initialize_events() + + # Wait for client before starting + self.cli_event.wait() self.measure_thread = threading.Thread(target=self.measure_thread) - self.thread = threading.Thread(target=self.run) + self.thread = threading.Thread(target=self.run_gui) self.measure_thread.start() self.thread.start() - print("GUI Thread Started!") + print("GUI Process Started!") + + self.exit_signal.wait() # The measuring thread to measure frequency def measure_thread(self): - while self.gui.client is None: - pass - previous_time = datetime.now() - while True: + while(True): # Sleep for 2 seconds time.sleep(2) @@ -217,32 +203,40 @@ def measure_thread(self): # Get the time period try: # Division by zero - self.measured_cycle = ms / self.iteration_counter + self.ideal_cycle.add(ms / self.iteration_counter) except: - self.measured_cycle = 0 + self.ideal_cycle.add(0) # Reset the counter self.iteration_counter = 0 # The main thread of execution - def run(self): - while self.gui.client is None: - pass - - while True: + def run_gui(self): + while(True): start_time = datetime.now() + # Send update signal self.gui.update_gui() - acknowledge_message = self.gui.get_acknowledge() - - while not acknowledge_message: - acknowledge_message = self.gui.get_acknowledge() - self.gui.set_acknowledge(False) + # Wait for acknowldege signal + self.ack_event.wait() + self.ack_event.clear() finish_time = datetime.now() self.iteration_counter = self.iteration_counter + 1 dt = finish_time - start_time ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 - if ms < self.ideal_cycle: - time.sleep((self.ideal_cycle-ms) / 1000.0) + time_cycle = self.time_cycle.get() + + if(ms < time_cycle): + time.sleep((time_cycle-ms) / 1000.0) + + self.exit_signal.set() + + # Functions to handle auxillary GUI functions + def reset_gui(self): + self.gui.reset_gui() + +if __name__ == "__main__": + gui = ProcessGUI() + gui.start() \ No newline at end of file diff --git a/exercises/static/exercises/labyrinth_escape/web-template/hal.py b/exercises/static/exercises/labyrinth_escape/web-template/hal.py old mode 100644 new mode 100755 index 25635a119..17c39cd6b --- a/exercises/static/exercises/labyrinth_escape/web-template/hal.py +++ b/exercises/static/exercises/labyrinth_escape/web-template/hal.py @@ -1,9 +1,12 @@ -import numpy as np import rospy import cv2 +import threading +import time +from datetime import datetime from drone_wrapper import DroneWrapper - +from shared.image import SharedImage +from shared.value import SharedValue # Hardware Abstraction Layer class HAL: @@ -12,71 +15,166 @@ class HAL: def __init__(self): rospy.init_node("HAL") - + + self.shared_frontal_image = SharedImage("halfrontalimage") + self.shared_ventral_image = SharedImage("halventralimage") + self.shared_x = SharedValue("x") + self.shared_y = SharedValue("y") + self.shared_z = SharedValue("z") + self.shared_takeoff_z = SharedValue("sharedtakeoffz") + self.shared_az = SharedValue("az") + self.shared_azt = SharedValue("azt") + self.shared_vx = SharedValue("vx") + self.shared_vy = SharedValue("vy") + self.shared_vz = SharedValue("vz") + self.shared_landed_state = SharedValue("landedstate") + self.shared_position = SharedValue("position") + self.shared_velocity = SharedValue("velocity") + self.shared_orientation = SharedValue("orientation") + self.shared_roll = SharedValue("roll") + self.shared_pitch = SharedValue("pitch") + self.shared_yaw = SharedValue("yaw") + self.shared_yaw_rate = SharedValue("yawrate") + self.image = None - self.drone = DroneWrapper(name="rqt") + self.drone = DroneWrapper(name="rqt",ns="/iris/") + + # Update thread + self.thread = ThreadHAL(self.update_hal) # Explicit initialization functions # Class method, so user can call it without instantiation - @classmethod - def initRobot(cls): - new_instance = cls() - return new_instance + + + # Function to start the update thread + def start_thread(self): + self.thread.start() # Get Image from ROS Driver Camera def get_frontal_image(self): image = self.drone.get_frontal_image() image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) - return image_rgb + self.shared_frontal_image.add(image_rgb) def get_ventral_image(self): image = self.drone.get_ventral_image() image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) - return image_rgb + self.shared_ventral_image.add(image_rgb) def get_position(self): pos = self.drone.get_position() - return pos + self.shared_position.add(pos,type_name="list") def get_velocity(self): vel = self.drone.get_velocity() - return vel + self.shared_velocity.add(vel ,type_name="list") def get_yaw_rate(self): yaw_rate = self.drone.get_yaw_rate() - return yaw_rate + self.shared_yaw_rate.add(yaw_rate) def get_orientation(self): orientation = self.drone.get_orientation() - return orientation + self.shared_orientation.add(orientation ,type_name="list") def get_roll(self): roll = self.drone.get_roll() - return roll + self.shared_roll.add(roll) def get_pitch(self): pitch = self.drone.get_pitch() - return pitch + self.shared_pitch.add(pitch) def get_yaw(self): yaw = self.drone.get_yaw() - return yaw + self.shared_yaw.add(yaw) def get_landed_state(self): state = self.drone.get_landed_state() - return state + self.shared_landed_state.add(state) + + def set_cmd_pos(self): + x = self.shared_x.get() + y = self.shared_y.get() + z = self.shared_z.get() + az = self.shared_az.get() - def set_cmd_pos(self, x, y, z, az): self.drone.set_cmd_pos(x, y, z, az) - def set_cmd_vel(self, vx, vy, vz, az): + def set_cmd_vel(self): + vx = self.shared_vx.get() + vy = self.shared_vy.get() + vz = self.shared_vz.get() + az = self.shared_azt.get() self.drone.set_cmd_vel(vx, vy, vz, az) - def set_cmd_mix(self, vx, vy, z, az): + def set_cmd_mix(self): + vx = self.shared_vx.get() + vy = self.shared_vy.get() + z = self.shared_z.get() + az = self.shared_azt.get() self.drone.set_cmd_mix(vx, vy, z, az) - def takeoff(self, h=3): + def takeoff(self): + h = self.shared_takeoff_z.get() self.drone.takeoff(h) def land(self): self.drone.land() + + def update_hal(self): + self.get_frontal_image() + self.get_ventral_image() + self.get_position() + self.get_velocity() + self.get_yaw_rate() + self.get_orientation() + self.get_pitch() + self.get_roll() + self.get_yaw() + self.get_landed_state() + self.set_cmd_pos() + self.set_cmd_vel() + self.set_cmd_mix() + + # Destructor function to close all fds + def __del__(self): + self.shared_frontal_image.close() + self.shared_ventral_image.close() + self.shared_x.close() + self.shared_y.close() + self.shared_z.close() + self.shared_takeoff_z.close() + self.shared_az.close() + self.shared_azt.close() + self.shared_vx.close() + self.shared_vy.close() + self.shared_vz.close() + self.shared_landed_state.close() + self.shared_position.close() + self.shared_velocity.close() + self.shared_orientation.close() + self.shared_roll.close() + self.shared_pitch.close() + self.shared_yaw.close() + self.shared_yaw_rate.close() + +class ThreadHAL(threading.Thread): + def __init__(self, update_function): + super(ThreadHAL, self).__init__() + self.time_cycle = 80 + self.update_function = update_function + + def run(self): + while(True): + start_time = datetime.now() + + self.update_function() + + finish_time = datetime.now() + + dt = finish_time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + + if(ms < self.time_cycle): + time.sleep((self.time_cycle - ms) / 1000.0) \ No newline at end of file diff --git a/exercises/static/exercises/labyrinth_escape/web-template/launch/gazebo.launch b/exercises/static/exercises/labyrinth_escape/web-template/launch/gazebo.launch deleted file mode 100644 index 021500295..000000000 --- a/exercises/static/exercises/labyrinth_escape/web-template/launch/gazebo.launch +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/exercises/static/exercises/labyrinth_escape/web-template/launch/labyrinth_escape.launch b/exercises/static/exercises/labyrinth_escape/web-template/launch/labyrinth_escape.launch new file mode 100755 index 000000000..810617533 --- /dev/null +++ b/exercises/static/exercises/labyrinth_escape/web-template/launch/labyrinth_escape.launch @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/exercises/static/exercises/labyrinth_escape/web-template/launch/launch.py b/exercises/static/exercises/labyrinth_escape/web-template/launch/launch.py deleted file mode 100644 index 2abb93395..000000000 --- a/exercises/static/exercises/labyrinth_escape/web-template/launch/launch.py +++ /dev/null @@ -1,131 +0,0 @@ -#!/usr/bin/env python3 - -import stat -import rospy -from os import lstat -from subprocess import Popen, PIPE - - -DRI_PATH = "/dev/dri/card0" -EXERCISE = "labyrinth_escape" -TIMEOUT = 30 -MAX_ATTEMPT = 2 - - -# Check if acceleration can be enabled -def check_device(device_path): - try: - return stat.S_ISCHR(lstat(device_path)[stat.ST_MODE]) - except: - return False - - -# Spawn new process -def spawn_process(args, insert_vglrun=False): - if insert_vglrun: - args.insert(0, "vglrun") - process = Popen(args, stdout=PIPE, bufsize=1, universal_newlines=True) - return process - - -class Test(): - def gazebo(self): - rospy.logwarn("[GAZEBO] Launching") - try: - rospy.wait_for_service("/gazebo/get_model_properties", TIMEOUT) - return True - except rospy.ROSException: - return False - - def px4(self): - rospy.logwarn("[PX4-SITL] Launching") - start_time = rospy.get_time() - args = ["./PX4-Autopilot/build/px4_sitl_default/bin/px4-commander","--instance", "0", "check"] - while rospy.get_time() - start_time < TIMEOUT: - process = spawn_process(args, insert_vglrun=False) - with process.stdout: - for line in iter(process.stdout.readline, ''): - if ("Prearm check: OK" in line): - return True - rospy.sleep(2) - return False - - def mavros(self, ns=""): - rospy.logwarn("[MAVROS] Launching") - try: - rospy.wait_for_service(ns + "/mavros/cmd/arming", TIMEOUT) - return True - except rospy.ROSException: - return False - - -class Launch(): - def __init__(self): - self.test = Test() - self.acceleration_enabled = check_device(DRI_PATH) - - # Start roscore - args = ["/opt/ros/noetic/bin/roscore"] - spawn_process(args, insert_vglrun=False) - - rospy.init_node("launch", anonymous=True) - - def start(self): - ######## LAUNCH GAZEBO ######## - args = ["/opt/ros/noetic/bin/roslaunch", - "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/gazebo.launch", - "--wait", - "--log" - ] - - attempt = 1 - while True: - spawn_process(args, insert_vglrun=self.acceleration_enabled) - if self.test.gazebo() == True: - break - if attempt == MAX_ATTEMPT: - rospy.logerr("[GAZEBO] Launch Failed") - return - attempt = attempt + 1 - - - ######## LAUNCH PX4 ######## - args = ["/opt/ros/noetic/bin/roslaunch", - "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/px4.launch", - "--log" - ] - - attempt = 1 - while True: - spawn_process(args, insert_vglrun=self.acceleration_enabled) - if self.test.px4() == True: - break - if attempt == MAX_ATTEMPT: - rospy.logerr("[PX4] Launch Failed") - return - attempt = attempt + 1 - - - ######## LAUNCH MAVROS ######## - args = ["/opt/ros/noetic/bin/roslaunch", - "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/mavros.launch", - "--log" - ] - - attempt = 1 - while True: - spawn_process(args, insert_vglrun=self.acceleration_enabled) - if self.test.mavros() == True: - break - if attempt == MAX_ATTEMPT: - rospy.logerr("[MAVROS] Launch Failed") - return - attempt = attempt + 1 - - -if __name__ == "__main__": - launch = Launch() - launch.start() - - with open("/drones_launch.log", "w") as f: - f.write("success") diff --git a/exercises/static/exercises/labyrinth_escape/web-template/launch/mavros.launch b/exercises/static/exercises/labyrinth_escape/web-template/launch/mavros.launch deleted file mode 100644 index b899c0ec1..000000000 --- a/exercises/static/exercises/labyrinth_escape/web-template/launch/mavros.launch +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/exercises/static/exercises/labyrinth_escape/web-template/launch/px4.launch b/exercises/static/exercises/labyrinth_escape/web-template/launch/px4.launch deleted file mode 100644 index 43ed14611..000000000 --- a/exercises/static/exercises/labyrinth_escape/web-template/launch/px4.launch +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/exercises/static/exercises/labyrinth_escape/web-template/shared/__init__.py b/exercises/static/exercises/labyrinth_escape/web-template/shared/__init__.py new file mode 100755 index 000000000..e69de29bb diff --git a/exercises/static/exercises/labyrinth_escape/web-template/shared/image.py b/exercises/static/exercises/labyrinth_escape/web-template/shared/image.py new file mode 100755 index 000000000..de5c9f9d6 --- /dev/null +++ b/exercises/static/exercises/labyrinth_escape/web-template/shared/image.py @@ -0,0 +1,109 @@ +import numpy as np +import mmap +from posix_ipc import Semaphore, O_CREX, ExistentialError, O_CREAT, SharedMemory, unlink_shared_memory +from ctypes import sizeof, memmove, addressof, create_string_buffer +from shared.structure_img import MD + +# Probably, using self variables gives errors with memmove +# Therefore, a global variable for utility +md_buf = create_string_buffer(sizeof(MD)) + +class SharedImage: + def __init__(self, name): + # Initialize variables for memory regions and buffers and Semaphore + self.shm_buf = None; self.shm_region = None + self.md_buf = None; self.md_region = None + self.image_lock = None + + self.shm_name = name; self.md_name = name + "-meta" + self.image_lock_name = name + + # Initialize or retreive metadata memory region + try: + self.md_region = SharedMemory(self.md_name) + self.md_buf = mmap.mmap(self.md_region.fd, sizeof(MD)) + self.md_region.close_fd() + except ExistentialError: + self.md_region = SharedMemory(self.md_name, O_CREAT, size=sizeof(MD)) + self.md_buf = mmap.mmap(self.md_region.fd, self.md_region.size) + self.md_region.close_fd() + + # Initialize or retreive Semaphore + try: + self.image_lock = Semaphore(self.image_lock_name, O_CREX) + except ExistentialError: + image_lock = Semaphore(self.image_lock_name, O_CREAT) + image_lock.unlink() + self.image_lock = Semaphore(self.image_lock_name, O_CREX) + + self.image_lock.release() + + # Get the shared image + def get(self): + # Define metadata + metadata = MD() + + # Get metadata from the shared region + self.image_lock.acquire() + md_buf[:] = self.md_buf + memmove(addressof(metadata), md_buf, sizeof(metadata)) + self.image_lock.release() + + # Try to retreive the image from shm_buffer + # Otherwise return a zero image + try: + self.shm_region = SharedMemory(self.shm_name) + self.shm_buf = mmap.mmap(self.shm_region.fd, metadata.size) + self.shm_region.close_fd() + + self.image_lock.acquire() + image = np.ndarray(shape=(metadata.shape_0, metadata.shape_1, metadata.shape_2), + dtype='uint8', buffer=self.shm_buf) + self.image_lock.release() + + # Check for a None image + if(image.size == 0): + image = np.zeros((3, 3, 3), np.uint8) + + except ExistentialError: + image = np.zeros((3, 3, 3), np.uint8) + + return image + + # Add the shared image + def add(self, image): + try: + # Get byte size of the image + byte_size = image.nbytes + + # Get the shared memory buffer to read from + if not self.shm_region: + self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=byte_size) + self.shm_buf = mmap.mmap(self.shm_region.fd, byte_size) + self.shm_region.close_fd() + + # Generate meta data + metadata = MD(image.shape[0], image.shape[1], image.shape[2], byte_size) + + # Send the meta data and image to shared regions + self.image_lock.acquire() + memmove(md_buf, addressof(metadata), sizeof(metadata)) + self.md_buf[:] = md_buf[:] + self.shm_buf[:] = image.tobytes() + self.image_lock.release() + except: + pass + + # Destructor function to unlink and disconnect + def close(self): + self.image_lock.acquire() + self.md_buf.close() + + try: + unlink_shared_memory(self.md_name) + unlink_shared_memory(self.shm_name) + except ExistentialError: + pass + + self.image_lock.release() + self.image_lock.close() diff --git a/exercises/static/exercises/labyrinth_escape/web-template/shared/structure_img.py b/exercises/static/exercises/labyrinth_escape/web-template/shared/structure_img.py new file mode 100755 index 000000000..ae40b3707 --- /dev/null +++ b/exercises/static/exercises/labyrinth_escape/web-template/shared/structure_img.py @@ -0,0 +1,9 @@ +from ctypes import Structure, c_int32, c_int64 + +class MD(Structure): + _fields_ = [ + ('shape_0', c_int32), + ('shape_1', c_int32), + ('shape_2', c_int32), + ('size', c_int64) + ] \ No newline at end of file diff --git a/exercises/static/exercises/labyrinth_escape/web-template/shared/value.py b/exercises/static/exercises/labyrinth_escape/web-template/shared/value.py new file mode 100755 index 000000000..e7bfad8a2 --- /dev/null +++ b/exercises/static/exercises/labyrinth_escape/web-template/shared/value.py @@ -0,0 +1,93 @@ +import numpy as np +import mmap +from posix_ipc import Semaphore, O_CREX, ExistentialError, O_CREAT, SharedMemory, unlink_shared_memory +from ctypes import sizeof, memmove, addressof, create_string_buffer, c_float +import struct + +class SharedValue: + def __init__(self, name): + # Initialize varaibles for memory regions and buffers and Semaphore + self.shm_buf = None; self.shm_region = None + self.value_lock = None + + self.shm_name = name; self.value_lock_name = name + + # Initialize or retreive Semaphore + try: + self.value_lock = Semaphore(self.value_lock_name, O_CREX) + except ExistentialError: + value_lock = Semaphore(self.value_lock_name, O_CREAT) + value_lock.unlink() + self.value_lock = Semaphore(self.value_lock_name, O_CREX) + + self.value_lock.release() + + # Get the shared value + def get(self, type_name= "value"): + # Retreive the data from buffer + if type_name=="value": + try: + self.shm_region = SharedMemory(self.shm_name) + self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) + self.shm_region.close_fd() + except ExistentialError: + self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=sizeof(c_float)) + self.shm_buf = mmap.mmap(self.shm_region.fd, self.shm_region.size) + self.shm_region.close_fd() + self.value_lock.acquire() + value = struct.unpack('f', self.shm_buf)[0] + self.value_lock.release() + + return value + elif type_name=="list": + self.shm_region = SharedMemory(self.shm_name) + self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) + self.shm_region.close_fd() + self.value_lock.acquire() + array_val = np.ndarray(shape=(3,), + dtype='float32', buffer=self.shm_buf) + self.value_lock.release() + + return array_val + + else: + print("missing argument for return type") + + + # Add the shared value + def add(self, value, type_name= "value"): + # Send the data to shared regions + if type_name=="value": + try: + self.shm_region = SharedMemory(self.shm_name) + self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) + self.shm_region.close_fd() + except ExistentialError: + self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=sizeof(c_float)) + self.shm_buf = mmap.mmap(self.shm_region.fd, self.shm_region.size) + self.shm_region.close_fd() + + self.value_lock.acquire() + self.shm_buf[:] = struct.pack('f', value) + self.value_lock.release() + elif type_name=="list": + byte_size = value.nbytes + self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=byte_size) + self.shm_buf = mmap.mmap(self.shm_region.fd, byte_size) + self.shm_region.close_fd() + self.value_lock.acquire() + self.shm_buf[:] = value.tobytes() + self.value_lock.release() + + # Destructor function to unlink and disconnect + def close(self): + self.value_lock.acquire() + self.shm_buf.close() + + try: + unlink_shared_memory(self.shm_name) + except ExistentialError: + pass + + self.value_lock.release() + self.value_lock.close() diff --git a/exercises/static/exercises/labyrinth_escape/web-template/user_functions.py b/exercises/static/exercises/labyrinth_escape/web-template/user_functions.py new file mode 100755 index 000000000..d741601fc --- /dev/null +++ b/exercises/static/exercises/labyrinth_escape/web-template/user_functions.py @@ -0,0 +1,117 @@ +from shared.image import SharedImage +from shared.value import SharedValue +import numpy as np +import cv2 + +# Define HAL functions +class HALFunctions: + def __init__(self): + # Initialize image variable + self.shared_frontal_image = SharedImage("halfrontalimage") + self.shared_ventral_image = SharedImage("halventralimage") + self.shared_x = SharedValue("x") + self.shared_y = SharedValue("y") + self.shared_z = SharedValue("z") + self.shared_takeoff_z = SharedValue("sharedtakeoffz") + self.shared_az = SharedValue("az") + self.shared_azt = SharedValue("azt") + self.shared_vx = SharedValue("vx") + self.shared_vy = SharedValue("vy") + self.shared_vz = SharedValue("vz") + self.shared_landed_state = SharedValue("landedstate") + self.shared_position = SharedValue("position") + self.shared_velocity = SharedValue("velocity") + self.shared_orientation = SharedValue("orientation") + self.shared_roll = SharedValue("roll") + self.shared_pitch = SharedValue("pitch") + self.shared_yaw = SharedValue("yaw") + self.shared_yaw_rate = SharedValue("yawrate") + + + # Get image function + def get_frontal_image(self): + image = self.shared_frontal_image.get() + return image + + # Get left image function + def get_ventral_image(self): + image = self.shared_ventral_image.get() + return image + + def takeoff(self, height): + self.shared_takeoff_z.add(height) + + def land(self): + pass + + def set_cmd_pos(self, x, y , z, az): + self.shared_x.add(x) + self.shared_y.add(y) + self.shared_z.add(z) + self.shared_az.add(az) + + def set_cmd_vel(self, vx, vy, vz, az): + self.shared_vx.add(vx) + self.shared_vy.add(vy) + self.shared_vz.add(vz) + self.shared_azt.add(az) + + def set_cmd_mix(self, vx, vy, z, az): + self.shared_vx.add(vx) + self.shared_vy.add(vy) + self.shared_vz.add(z) + self.shared_azt.add(az) + + + def get_position(self): + position = self.shared_position.get(type_name = "list") + return position + + def get_velocity(self): + velocity = self.shared_velocity.get(type_name = "list") + return velocity + + def get_yaw_rate(self): + yaw_rate = self.shared_yaw_rate.get(type_name = "value") + return yaw_rate + + def get_orientation(self): + orientation = self.shared_orientation.get(type_name = "list") + return orientation + + def get_roll(self): + roll = self.shared_roll.get(type_name = "value") + return roll + + def get_pitch(self): + pitch = self.shared_pitch.get(type_name = "value") + return pitch + + def get_yaw(self): + yaw = self.shared_yaw.get(type_name = "value") + return yaw + + def get_landed_state(self): + landed_state = self.shared_landed_state.get(type_name = "value") + return landed_state + +# Define GUI functions +class GUIFunctions: + def __init__(self): + # Initialize image variable + self.shared_image = SharedImage("guifrontalimage") + self.shared_left_image = SharedImage("guiventralimage") + + # Show image function + def showImage(self, image): + # Reshape to 3 channel if it has only 1 in order to display it + if (len(image.shape) < 3): + image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB) + self.shared_image.add(image) + + # Show left image function + def showLeftImage(self, image): + # Reshape to 3 channel if it has only 1 in order to display it + if (len(image.shape) < 3): + image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB) + self.shared_left_image.add(image) \ No newline at end of file diff --git a/scripts/instructions.json b/scripts/instructions.json index ec42cb9b1..149b0c0b3 100644 --- a/scripts/instructions.json +++ b/scripts/instructions.json @@ -62,8 +62,9 @@ }, "labyrinth_escape": { "gazebo_path": "/RoboticsAcademy/exercises/labyrinth_escape/web-template/launch", - "instructions_ros": ["python3 ./RoboticsAcademy/exercises/labyrinth_escape/web-template/launch/launch.py"], - "instructions_host": "python3 /RoboticsAcademy/exercises/labyrinth_escape/web-template/exercise.py 0.0.0.0" + "instructions_ros": ["/opt/ros/noetic/bin/roslaunch ./RoboticsAcademy/exercises/labyrinth_escape/web-template/launch/labyrinth_escape.launch"], + "instructions_host": "python3 /RoboticsAcademy/exercises/labyrinth_escape/web-template/exercise.py 0.0.0.0", + "instructions_gui": "python3 /RoboticsAcademy/exercises/labyrinth_escape/web-template/gui.py 0.0.0.0 {}" }, "position_control": { "gazebo_path": "/RoboticsAcademy/exercises/position_control/web-template/launch", From 5da56ffea6f07e50215369e96ff83af8ecf95de9 Mon Sep 17 00:00:00 2001 From: RUFFY-369 Date: Mon, 12 Sep 2022 12:32:05 +0530 Subject: [PATCH 20/42] final frontend for labyrinth escape --- .../web-template/labyrinth_escape.world | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/exercises/static/exercises/labyrinth_escape/web-template/labyrinth_escape.world b/exercises/static/exercises/labyrinth_escape/web-template/labyrinth_escape.world index c78d5586d..8712adcf2 100644 --- a/exercises/static/exercises/labyrinth_escape/web-template/labyrinth_escape.world +++ b/exercises/static/exercises/labyrinth_escape/web-template/labyrinth_escape.world @@ -7,61 +7,61 @@ model://arrow arrow_1 - -18 -8.5 0.01 0 0 0 + -18 -8.5 0.1 0 0 0 model://arrow arrow_2 - -5.5 -8.5 0.01 0 0 0 + -5.5 -8.5 0.1 0 0 0 model://arrow arrow_3 - 7 -8.5 0.01 0 0 1.57 + 7 -8.5 0.1 0 0 1.57 model://arrow arrow_4 - 7 3 0.01 0 0 1.57 + 7 3 0.1 0 0 1.57 model://arrow arrow_5 - 7 14.5 0.01 0 0 3.14 + 7 14.5 0.1 0 0 3.14 model://arrow arrow_6 - 2.5 14.5 0.01 0 0 3.14 + 2.5 14.5 0.1 0 0 3.14 model://arrow arrow_7 - 2.5 7.5.01 0 0 3.14 + 2.5 7.5.1 0 0 3.14 model://arrow arrow_8 - -3 7.5 0.01 0 0 1.57 + -3 7.5 0.1 0 0 1.57 model://arrow arrow_9 - -3 20.5 0.01 0 0 3.14 + -3 20.5 0.1 0 0 3.14 model://arrow arrow_10 - -13 20.5 0.01 0 0 3.14 + -13 20.5 0.1 0 0 3.14 model://simple_labyrinth_green - + - model://grass_plane + model://asphalt_plane From fe7d52c9b8c0708d4e42b89cc0eaef10473a676c Mon Sep 17 00:00:00 2001 From: RUFFY-369 Date: Mon, 12 Sep 2022 18:57:44 +0530 Subject: [PATCH 21/42] multiprocessing final for visual lander --- .../visual_lander/web-template/brain.py | 166 +++++++++++ .../visual_lander/web-template/exercise.py | 266 ++++++------------ .../visual_lander/web-template/gui.py | 206 +++++++------- .../visual_lander/web-template/hal.py | 142 ++++++++-- .../web-template/launch/gazebo.launch | 24 -- .../web-template/launch/launch.py | 131 --------- .../web-template/launch/mavros.launch | 13 - .../web-template/launch/px4.launch | 18 -- .../web-template/launch/visual_lander.launch | 60 ++++ .../web-template/shared/__init__.py | 0 .../web-template/{ => shared}/car.py | 0 .../web-template/shared/image.py | 109 +++++++ .../web-template/shared/structure_img.py | 9 + .../web-template/shared/value.py | 93 ++++++ .../web-template/user_functions.py | 117 ++++++++ scripts/Dockerfile | 4 +- scripts/instructions.json | 5 +- 17 files changed, 870 insertions(+), 493 deletions(-) create mode 100755 exercises/static/exercises/visual_lander/web-template/brain.py mode change 100644 => 100755 exercises/static/exercises/visual_lander/web-template/exercise.py mode change 100644 => 100755 exercises/static/exercises/visual_lander/web-template/gui.py mode change 100644 => 100755 exercises/static/exercises/visual_lander/web-template/hal.py delete mode 100644 exercises/static/exercises/visual_lander/web-template/launch/gazebo.launch delete mode 100644 exercises/static/exercises/visual_lander/web-template/launch/launch.py delete mode 100644 exercises/static/exercises/visual_lander/web-template/launch/mavros.launch delete mode 100644 exercises/static/exercises/visual_lander/web-template/launch/px4.launch create mode 100755 exercises/static/exercises/visual_lander/web-template/launch/visual_lander.launch create mode 100755 exercises/static/exercises/visual_lander/web-template/shared/__init__.py rename exercises/static/exercises/visual_lander/web-template/{ => shared}/car.py (100%) create mode 100755 exercises/static/exercises/visual_lander/web-template/shared/image.py create mode 100755 exercises/static/exercises/visual_lander/web-template/shared/structure_img.py create mode 100755 exercises/static/exercises/visual_lander/web-template/shared/value.py create mode 100755 exercises/static/exercises/visual_lander/web-template/user_functions.py diff --git a/exercises/static/exercises/visual_lander/web-template/brain.py b/exercises/static/exercises/visual_lander/web-template/brain.py new file mode 100755 index 000000000..2330c04ba --- /dev/null +++ b/exercises/static/exercises/visual_lander/web-template/brain.py @@ -0,0 +1,166 @@ +import time +import threading +import multiprocessing +import sys +from datetime import datetime +import re +import json +import importlib + +import rospy +from std_srvs.srv import Empty +import cv2 + +from user_functions import GUIFunctions, HALFunctions +from console import start_console, close_console + +from shared.value import SharedValue + +# The brain process class +class BrainProcess(multiprocessing.Process): + def __init__(self, code, exit_signal): + super(BrainProcess, self).__init__() + + # Initialize exit signal + self.exit_signal = exit_signal + + # Function definitions for users to use + self.hal = HALFunctions() + self.gui = GUIFunctions() + + # Time variables + self.time_cycle = SharedValue('brain_time_cycle') + self.ideal_cycle = SharedValue('brain_ideal_cycle') + self.iteration_counter = 0 + + # Get the sequential and iterative code + # Something wrong over here! The code is reversing + # Found a solution but could not find the reason for this (parse_code function's return line of exercise.py is the reason) + self.sequential_code = code[1] + self.iterative_code = code[0] + + # Function to run to start the process + def run(self): + # Two threads for running and measuring + self.measure_thread = threading.Thread(target=self.measure_frequency) + self.thread = threading.Thread(target=self.process_code) + + self.measure_thread.start() + self.thread.start() + + print("Brain Process Started!") + + self.exit_signal.wait() + + # The process function + def process_code(self): + # Redirect information to console + start_console() + + # Reference Environment for the exec() function + iterative_code, sequential_code = self.iterative_code, self.sequential_code + + # The Python exec function + # Run the sequential part + gui_module, hal_module = self.generate_modules() + if sequential_code != "": + reference_environment = {"GUI": gui_module, "HAL": hal_module} + exec(sequential_code, reference_environment) + + # Run the iterative part inside template + # and keep the check for flag + while not self.exit_signal.is_set(): + start_time = datetime.now() + + # Execute the iterative portion + if iterative_code != "": + exec(iterative_code, reference_environment) + + # Template specifics to run! + finish_time = datetime.now() + dt = finish_time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + + # Keep updating the iteration counter + if(iterative_code == ""): + self.iteration_counter = 0 + else: + self.iteration_counter = self.iteration_counter + 1 + + # The code should be run for atleast the target time step + # If it's less put to sleep + # If it's more no problem as such, but we can change it! + time_cycle = self.time_cycle.get() + + if(ms < time_cycle): + time.sleep((time_cycle - ms) / 1000.0) + + close_console() + print("Current Thread Joined!") + + + # Function to generate the modules for use in ACE Editor + def generate_modules(self): + # Define HAL module + hal_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) + hal_module.HAL = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) + hal_module.HAL.motors = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("motors", None)) + + # Add HAL functions + hal_module.HAL.get_frontal_image = self.hal.get_frontal_image + hal_module.HAL.get_ventral_image = self.hal.get_ventral_image + hal_module.HAL.takeoff = self.hal.takeoff + hal_module.HAL.land = self.hal.land + hal_module.HAL.set_cmd_pos = self.hal.set_cmd_pos + hal_module.HAL.set_cmd_vel = self.hal.set_cmd_vel + hal_module.HAL.set_cmd_mix = self.hal.set_cmd_mix + hal_module.HAL.get_position = self.hal.get_position + hal_module.HAL.get_velocity = self.hal.get_velocity + hal_module.HAL.get_orientation = self.hal.get_orientation + hal_module.HAL.get_roll = self.hal.get_roll + hal_module.HAL.get_pitch = self.hal.get_pitch + hal_module.HAL.get_yaw = self.hal.get_yaw + hal_module.HAL.get_landed_state = self.hal.get_landed_state + hal_module.HAL.get_yaw_rate = self.hal.get_yaw_rate + + + + # Define GUI module + gui_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) + gui_module.GUI = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) + + # Add GUI functions + gui_module.GUI.showImage = self.gui.showImage + gui_module.GUI.showLeftImage = self.gui.showLeftImage + + # Adding modules to system + # Protip: The names should be different from + # other modules, otherwise some errors + sys.modules["HAL"] = hal_module + sys.modules["GUI"] = gui_module + + return gui_module, hal_module + + # Function to measure the frequency of iterations + def measure_frequency(self): + previous_time = datetime.now() + # An infinite loop + while not self.exit_signal.is_set(): + # Sleep for 2 seconds + time.sleep(2) + + # Measure the current time and subtract from the previous time to get real time interval + current_time = datetime.now() + dt = current_time - previous_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + previous_time = current_time + + # Get the time period + try: + # Division by zero + self.ideal_cycle.add(ms / self.iteration_counter) + except: + self.ideal_cycle.add(0) + + # Reset the counter + self.iteration_counter = 0 \ No newline at end of file diff --git a/exercises/static/exercises/visual_lander/web-template/exercise.py b/exercises/static/exercises/visual_lander/web-template/exercise.py old mode 100644 new mode 100755 index 3874651bc..82bf0c72a --- a/exercises/static/exercises/visual_lander/web-template/exercise.py +++ b/exercises/static/exercises/visual_lander/web-template/exercise.py @@ -1,3 +1,5 @@ + + #!/usr/bin/env python from __future__ import print_function @@ -5,6 +7,7 @@ from websocket_server import WebsocketServer import time import threading +import multiprocessing import subprocess import sys from datetime import datetime @@ -14,10 +17,14 @@ import rospy from std_srvs.srv import Empty +import cv2 + +from shared.value import SharedValue +from brain import BrainProcess +import queue + -from gui import GUI, ThreadGUI from hal import HAL -from car import Car from console import start_console, close_console @@ -26,37 +33,65 @@ class Template: # self.ideal_cycle to run an execution for at least 1 second # self.process for the current running process def __init__(self): - self.measure_thread = None - self.thread = None - self.reload = False - self.stop_brain = True - self.user_code = "" + + self.brain_process = None + self.reload = multiprocessing.Event() # Time variables - self.ideal_cycle = 80 - self.measured_cycle = 80 - self.iteration_counter = 0 + self.brain_time_cycle = SharedValue('brain_time_cycle') + self.brain_ideal_cycle = SharedValue('brain_ideal_cycle') self.real_time_factor = 0 + self.frequency_message = {'brain': '', 'gui': '', 'rtf': ''} + # GUI variables + self.gui_time_cycle = SharedValue('gui_time_cycle') + self.gui_ideal_cycle = SharedValue('gui_ideal_cycle') + self.server = None self.client = None self.host = sys.argv[1] - # Initialize the GUI, HAL and Console behind the scenes self.hal = HAL() - self.car = Car() - self.gui = GUI(self.host, self.car) + self.paused = False + # Function to parse the code # A few assumptions: # 1. The user always passes sequential and iterative codes # 2. Only a single infinite loop def parse_code(self, source_code): - sequential_code, iterative_code = self.seperate_seq_iter(source_code) - return iterative_code, sequential_code + # Check for save/load + if(source_code[:5] == "#save"): + source_code = source_code[5:] + self.save_code(source_code) + + return "", "" + + elif(source_code[:5] == "#load"): + source_code = source_code + self.load_code() + self.server.send_message(self.client, source_code) + + return "", "" + + else: + sequential_code, iterative_code = self.seperate_seq_iter(source_code[6:]) + return iterative_code, sequential_code + + + # Function for saving + def save_code(self, source_code): + with open('code/academy.py', 'w') as code_file: + code_file.write(source_code) + + # Function for loading + def load_code(self): + with open('code/academy.py', 'r') as code_file: + source_code = code_file.read() + + return source_code - # Function to separate the iterative and sequential code + # Function to seperate the iterative and sequential code def seperate_seq_iter(self, source_code): if source_code == "": return "", "" @@ -64,8 +99,8 @@ def seperate_seq_iter(self, source_code): # Search for an instance of while True infinite_loop = re.search(r'[^ ]while\s*\(\s*True\s*\)\s*:|[^ ]while\s*True\s*:|[^ ]while\s*1\s*:|[^ ]while\s*\(\s*1\s*\)\s*:', source_code) - # Separate the content inside while True and the other - # (Separating the sequential and iterative part!) + # Seperate the content inside while True and the other + # (Seperating the sequential and iterative part!) try: start_index = infinite_loop.start() iterative_code = source_code[start_index:] @@ -87,137 +122,16 @@ def seperate_seq_iter(self, source_code): return sequential_code, iterative_code - # The process function - def process_code(self, source_code): - # Redirect the information to console - start_console() - - iterative_code, sequential_code = self.parse_code(source_code) - - # print(sequential_code) - # print(iterative_code) - - # The Python exec function - # Run the sequential part - gui_module, hal_module = self.generate_modules() - reference_environment = {"GUI": gui_module, "HAL": hal_module} - while (self.stop_brain == True): - if (self.reload == True): - return - time.sleep(0.1) - exec(sequential_code, reference_environment) - - # Run the iterative part inside template - # and keep the check for flag - while self.reload == False: - while (self.stop_brain == True): - if (self.reload == True): - return - time.sleep(0.1) - - start_time = datetime.now() - - # Execute the iterative portion - exec(iterative_code, reference_environment) - - # Template specifics to run! - finish_time = datetime.now() - dt = finish_time - start_time - ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 - - # Keep updating the iteration counter - if (iterative_code == ""): - self.iteration_counter = 0 - else: - self.iteration_counter = self.iteration_counter + 1 - - # The code should be run for atleast the target time step - # If it's less put to sleep - if (ms < self.ideal_cycle): - time.sleep((self.ideal_cycle - ms) / 1000.0) - - close_console() - print("Current Thread Joined!") - - # Function to generate the modules for use in ACE Editor - def generate_modules(self): - # Define HAL module - hal_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) - hal_module.HAL = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) - # hal_module.drone = imp.new_module("drone") - # motors# hal_module.HAL.motors = imp.new_module("motors") - - # Add HAL functions - hal_module.HAL.get_frontal_image = self.hal.get_frontal_image - hal_module.HAL.get_ventral_image = self.hal.get_ventral_image - hal_module.HAL.get_position = self.hal.get_position - hal_module.HAL.get_velocity = self.hal.get_velocity - hal_module.HAL.get_yaw_rate = self.hal.get_yaw_rate - hal_module.HAL.get_orientation = self.hal.get_orientation - hal_module.HAL.get_roll = self.hal.get_roll - hal_module.HAL.get_pitch = self.hal.get_pitch - hal_module.HAL.get_yaw = self.hal.get_yaw - hal_module.HAL.get_landed_state = self.hal.get_landed_state - hal_module.HAL.set_cmd_pos = self.hal.set_cmd_pos - hal_module.HAL.set_cmd_vel = self.hal.set_cmd_vel - hal_module.HAL.set_cmd_mix = self.hal.set_cmd_mix - hal_module.HAL.takeoff = self.hal.takeoff - hal_module.HAL.land = self.hal.land - - # Define GUI module - gui_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) - gui_module.GUI = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) - - # Add GUI functions - gui_module.GUI.showImage = self.gui.showImage - gui_module.GUI.showLeftImage = self.gui.showLeftImage - - # Adding modules to system - # Protip: The names should be different from - # other modules, otherwise some errors - sys.modules["HAL"] = hal_module - sys.modules["GUI"] = gui_module - - return gui_module, hal_module - - # Function to measure the frequency of iterations - def measure_frequency(self): - previous_time = datetime.now() - # An infinite loop - while True: - # Sleep for 2 seconds - time.sleep(2) - - # Measure the current time and subtract from the previous time to get real time interval - current_time = datetime.now() - dt = current_time - previous_time - ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 - previous_time = current_time - - # Get the time period - try: - # Division by zero - self.measured_cycle = ms / self.iteration_counter - except: - self.measured_cycle = 0 - - # Reset the counter - self.iteration_counter = 0 - - # Send to client - self.send_frequency_message() - - # Function to generate and send frequency messages def send_frequency_message(self): # This function generates and sends frequency measures of the brain and gui - brain_frequency = 0; gui_frequency = 0 + brain_frequency = 0;gui_frequency = 0 try: - brain_frequency = round(1000 / self.measured_cycle, 1) + brain_frequency = round(1000 / self.brain_ideal_cycle.get(), 1) except ZeroDivisionError: brain_frequency = 0 try: - gui_frequency = round(1000 / self.thread_gui.measured_cycle, 1) + gui_frequency = round(1000 / self.gui_ideal_cycle.get(), 1) except ZeroDivisionError: gui_frequency = 0 @@ -242,29 +156,32 @@ def track_stats(self): args = ["gz", "stats", "-p"] # Prints gz statistics. "-p": Output comma-separated values containing- # real-time factor (percent), simtime (sec), realtime (sec), paused (T or F) - stats_process = subprocess.Popen(args, stdout=subprocess.PIPE, bufsize=1, universal_newlines=True) + stats_process = subprocess.Popen(args, stdout=subprocess.PIPE, bufsize=0) # bufsize=1 enables line-bufferred mode (the input buffer is flushed # automatically on newlines if you would write to process.stdin ) with stats_process.stdout: - for line in iter(stats_process.stdout.readline, ''): - stats_list = [x.strip() for x in line.split(',')] - self.real_time_factor = stats_list[0] + for line in iter(stats_process.stdout.readline, b''): + stats_list = [x.strip() for x in line.split(b',')] + self.real_time_factor = stats_list[0].decode("utf-8") # Function to maintain thread execution def execute_thread(self, source_code): # Keep checking until the thread is alive # The thread will die when the coming iteration reads the flag - if self.thread is not None: - while self.thread.is_alive(): - time.sleep(0.2) + if(self.brain_process != None): + while self.brain_process.is_alive(): + pass # Turn the flag down, the iteration has successfully stopped! - self.reload = False + self.reload.clear() # New thread execution - self.thread = threading.Thread(target=self.process_code, args=[source_code]) - self.thread.start() + code = self.parse_code(source_code) + if code[0] == "" and code[1] == "": + return + + self.brain_process = BrainProcess(code, self.reload) + self.brain_process.start() self.send_code_message() - print("New Thread Started!") # Function to read and set frequency from incoming message def read_frequency_message(self, message): @@ -272,65 +189,58 @@ def read_frequency_message(self, message): # Set brain frequency frequency = float(frequency_message["brain"]) - self.ideal_cycle = 1000.0 / frequency + self.brain_time_cycle.add(1000.0 / frequency) # Set gui frequency frequency = float(frequency_message["gui"]) - self.thread_gui.ideal_cycle = 1000.0 / frequency + self.gui_time_cycle.add(1000.0 / frequency) return # The websocket function # Gets called when there is an incoming message from the client def handle(self, client, server, message): - if message[:5] == "#freq": + if(message[:5] == "#freq"): frequency_message = message[5:] self.read_frequency_message(frequency_message) time.sleep(1) + self.send_frequency_message() return - elif(message[:5] == "#ping"): time.sleep(1) self.send_ping_message() return - elif (message[:5] == "#code"): + elif (message[:5] == "#code"): try: # Once received turn the reload flag up and send it to execute_thread function - self.user_code = message[6:] + code = message # print(repr(code)) - self.reload = True - self.execute_thread(self.user_code) + self.reload.set() + self.execute_thread(code) except: pass - elif (message[:5] == "#rest"): + elif (message[:5] == "#stop"): try: - self.reload = True - self.stop_brain = True - self.execute_thread(self.user_code) + self.reload.set() + self.execute_thread(code) except: pass - - elif (message[:5] == "#stop"): - self.stop_brain = True - - elif (message[:5] == "#play"): - self.stop_brain = False + self.server.send_message(self.client, "#stpd") # Function that gets called when the server is connected def connected(self, client, server): self.client = client - # Start the GUI update thread - self.thread_gui = ThreadGUI(self.gui) - self.thread_gui.start() + # Start the HAL update thread + self.hal.start_thread() - # Start the real time factor tracker thread + # Start real time factor tracker thread self.stats_thread = threading.Thread(target=self.track_stats) self.stats_thread.start() - # Start measure frequency - self.measure_thread = threading.Thread(target=self.measure_frequency) - self.measure_thread.start() + # Initialize the ping message + self.send_frequency_message() + print("After sneding freweq msg") print(client, 'connected') diff --git a/exercises/static/exercises/visual_lander/web-template/gui.py b/exercises/static/exercises/visual_lander/web-template/gui.py old mode 100644 new mode 100755 index 2973e6cb9..330a66cb0 --- a/exercises/static/exercises/visual_lander/web-template/gui.py +++ b/exercises/static/exercises/visual_lander/web-template/gui.py @@ -5,98 +5,76 @@ import time from datetime import datetime from websocket_server import WebsocketServer +import logging +import rospy +import cv2 +import sys +import numpy as np +import multiprocessing + +from interfaces.pose3d import ListenerPose3d +from shared.image import SharedImage +from shared.value import SharedValue +from shared.car import Car # Graphical User Interface Class class GUI: # Initialization function # The actual initialization def __init__(self, host, car): - t = threading.Thread(target=self.run_server) - + + rospy.init_node("GUI") self.payload = {'image': ''} self.left_payload = {'image': ''} self.server = None self.client = None - + self.host = host - # Image variables - self.image_to_be_shown = None - self.image_to_be_shown_updated = False - self.image_show_lock = threading.Lock() - - self.left_image_to_be_shown = None - self.left_image_to_be_shown_updated = False - self.left_image_show_lock = threading.Lock() - - self.acknowledge = False - self.acknowledge_lock = threading.Lock() - + # Image variable host + self.shared_image = SharedImage("guifrontalimage") + self.shared_left_image = SharedImage("guiventralimage") + + # Event objects for multiprocessing + self.ack_event = multiprocessing.Event() + self.cli_event = multiprocessing.Event() # Take the console object to set the same websocket and client self.car = car + + # Start server thread + t = threading.Thread(target=self.run_server) t.start() - # Explicit initialization function - # Class method, so user can call it without instantiation - @classmethod - def initGUI(cls, host): - # self.payload = {'image': '', 'shape': []} - new_instance = cls(host) - return new_instance # Function to prepare image payload # Encodes the image as a JSON string and sends through the WS def payloadImage(self): - self.image_show_lock.acquire() - image_to_be_shown_updated = self.image_to_be_shown_updated - image_to_be_shown = self.image_to_be_shown - self.image_show_lock.release() - - image = image_to_be_shown + image = self.shared_image.get() payload = {'image': '', 'shape': ''} - - if not image_to_be_shown_updated: - return payload - + shape = image.shape frame = cv2.imencode('.JPEG', image)[1] encoded_image = base64.b64encode(frame) - + payload['image'] = encoded_image.decode('utf-8') payload['shape'] = shape - - self.image_show_lock.acquire() - self.image_to_be_shown_updated = False - self.image_show_lock.release() - + return payload - + # Function to prepare image payload # Encodes the image as a JSON string and sends through the WS def payloadLeftImage(self): - self.left_image_show_lock.acquire() - left_image_to_be_shown_updated = self.left_image_to_be_shown_updated - left_image_to_be_shown = self.left_image_to_be_shown - self.left_image_show_lock.release() - - image = left_image_to_be_shown + image = self.shared_left_image.get() payload = {'image': '', 'shape': ''} - - if not left_image_to_be_shown_updated: - return payload - + shape = image.shape frame = cv2.imencode('.JPEG', image)[1] encoded_image = base64.b64encode(frame) - + payload['image'] = encoded_image.decode('utf-8') payload['shape'] = shape - - self.left_image_show_lock.acquire() - self.left_image_to_be_shown_updated = False - self.left_image_show_lock.release() - + return payload # Function for student to call @@ -117,27 +95,17 @@ def showLeftImage(self, image): # Called when a new client is received def get_client(self, client, server): self.client = client + self.cli_event.set() - # Function to get value of Acknowledge - def get_acknowledge(self): - self.acknowledge_lock.acquire() - acknowledge = self.acknowledge - self.acknowledge_lock.release() - - return acknowledge - - # Function to get value of Acknowledge - def set_acknowledge(self, value): - self.acknowledge_lock.acquire() - self.acknowledge = value - self.acknowledge_lock.release() - + print(client, 'connected') + + # Update the gui def update_gui(self): # Payload Image Message payload = self.payloadImage() self.payload["image"] = json.dumps(payload) - + message = "#gui" + json.dumps(self.payload) self.server.send_message(self.client, message) @@ -147,24 +115,34 @@ def update_gui(self): message = "#gul" + json.dumps(self.left_payload) self.server.send_message(self.client, message) - + # Function to read the message from websocket # Gets called when there is an incoming message from the client def get_message(self, client, server, message): # Acknowledge Message for GUI Thread - if message[:4] == "#ack": - self.set_acknowledge(True) + + if(message[:4] == "#ack"): + # Set acknowledgement flag + self.ack_event.set() elif message[:4] == "#car": - self.car.start_car(int(message[4:5])) + self.car.start_car() elif message[:4] == "#stp": self.car.stop_car() + # Reset message elif message[:4] == "#rst": self.car.reset_car() + # Function that gets called when the connected closes + def handle_close(self, client, server): + print(client, 'closed') + + + # Activate the server def run_server(self): self.server = WebsocketServer(port=2303, host=self.host) self.server.set_fn_new_client(self.get_client) self.server.set_fn_message_received(self.get_message) + self.server.set_fn_client_left(self.handle_close) logged = False while not logged: @@ -181,36 +159,50 @@ def run_server(self): # Function to reset def reset_gui(self): pass - + # This class decouples the user thread # and the GUI update thread -class ThreadGUI: - def __init__(self, gui): - self.gui = gui +class ProcessGUI(multiprocessing.Process): + def __init__(self): + super(ProcessGUI, self).__init__() + self.host = sys.argv[1] + self.car = Car() # Time variables - self.ideal_cycle = 80 - self.measured_cycle = 80 + self.time_cycle = SharedValue("gui_time_cycle") + self.ideal_cycle = SharedValue("gui_ideal_cycle") self.iteration_counter = 0 + # Function to initialize events + def initialize_events(self): + # Events + self.ack_event = self.gui.ack_event + self.cli_event = self.gui.cli_event + self.exit_signal = multiprocessing.Event() + # Function to start the execution of threads - def start(self): + def run(self): + # Initialize GUI + self.gui = GUI(self.host, self.car) + self.initialize_events() + + # Wait for client before starting + self.cli_event.wait() self.measure_thread = threading.Thread(target=self.measure_thread) - self.thread = threading.Thread(target=self.run) + self.thread = threading.Thread(target=self.run_gui) self.measure_thread.start() self.thread.start() - print("GUI Thread Started!") + print("GUI Process Started!") + + self.exit_signal.wait() # The measuring thread to measure frequency def measure_thread(self): - while self.gui.client is None: - pass - previous_time = datetime.now() - while True: + while(True): # Sleep for 2 seconds time.sleep(2) @@ -223,32 +215,40 @@ def measure_thread(self): # Get the time period try: # Division by zero - self.measured_cycle = ms / self.iteration_counter + self.ideal_cycle.add(ms / self.iteration_counter) except: - self.measured_cycle = 0 + self.ideal_cycle.add(0) # Reset the counter self.iteration_counter = 0 # The main thread of execution - def run(self): - while self.gui.client is None: - pass - - while True: + def run_gui(self): + while(True): start_time = datetime.now() + # Send update signal self.gui.update_gui() - acknowledge_message = self.gui.get_acknowledge() - - while not acknowledge_message: - acknowledge_message = self.gui.get_acknowledge() - - self.gui.set_acknowledge(False) + # Wait for acknowldege signal + self.ack_event.wait() + self.ack_event.clear() + finish_time = datetime.now() self.iteration_counter = self.iteration_counter + 1 - + dt = finish_time - start_time ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 - if ms < self.ideal_cycle: - time.sleep((self.ideal_cycle-ms) / 1000.0) + time_cycle = self.time_cycle.get() + + if(ms < time_cycle): + time.sleep((time_cycle-ms) / 1000.0) + + self.exit_signal.set() + + # Functions to handle auxillary GUI functions + def reset_gui(self): + self.gui.reset_gui() + +if __name__ == "__main__": + gui = ProcessGUI() + gui.start() \ No newline at end of file diff --git a/exercises/static/exercises/visual_lander/web-template/hal.py b/exercises/static/exercises/visual_lander/web-template/hal.py old mode 100644 new mode 100755 index 25635a119..17c39cd6b --- a/exercises/static/exercises/visual_lander/web-template/hal.py +++ b/exercises/static/exercises/visual_lander/web-template/hal.py @@ -1,9 +1,12 @@ -import numpy as np import rospy import cv2 +import threading +import time +from datetime import datetime from drone_wrapper import DroneWrapper - +from shared.image import SharedImage +from shared.value import SharedValue # Hardware Abstraction Layer class HAL: @@ -12,71 +15,166 @@ class HAL: def __init__(self): rospy.init_node("HAL") - + + self.shared_frontal_image = SharedImage("halfrontalimage") + self.shared_ventral_image = SharedImage("halventralimage") + self.shared_x = SharedValue("x") + self.shared_y = SharedValue("y") + self.shared_z = SharedValue("z") + self.shared_takeoff_z = SharedValue("sharedtakeoffz") + self.shared_az = SharedValue("az") + self.shared_azt = SharedValue("azt") + self.shared_vx = SharedValue("vx") + self.shared_vy = SharedValue("vy") + self.shared_vz = SharedValue("vz") + self.shared_landed_state = SharedValue("landedstate") + self.shared_position = SharedValue("position") + self.shared_velocity = SharedValue("velocity") + self.shared_orientation = SharedValue("orientation") + self.shared_roll = SharedValue("roll") + self.shared_pitch = SharedValue("pitch") + self.shared_yaw = SharedValue("yaw") + self.shared_yaw_rate = SharedValue("yawrate") + self.image = None - self.drone = DroneWrapper(name="rqt") + self.drone = DroneWrapper(name="rqt",ns="/iris/") + + # Update thread + self.thread = ThreadHAL(self.update_hal) # Explicit initialization functions # Class method, so user can call it without instantiation - @classmethod - def initRobot(cls): - new_instance = cls() - return new_instance + + + # Function to start the update thread + def start_thread(self): + self.thread.start() # Get Image from ROS Driver Camera def get_frontal_image(self): image = self.drone.get_frontal_image() image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) - return image_rgb + self.shared_frontal_image.add(image_rgb) def get_ventral_image(self): image = self.drone.get_ventral_image() image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) - return image_rgb + self.shared_ventral_image.add(image_rgb) def get_position(self): pos = self.drone.get_position() - return pos + self.shared_position.add(pos,type_name="list") def get_velocity(self): vel = self.drone.get_velocity() - return vel + self.shared_velocity.add(vel ,type_name="list") def get_yaw_rate(self): yaw_rate = self.drone.get_yaw_rate() - return yaw_rate + self.shared_yaw_rate.add(yaw_rate) def get_orientation(self): orientation = self.drone.get_orientation() - return orientation + self.shared_orientation.add(orientation ,type_name="list") def get_roll(self): roll = self.drone.get_roll() - return roll + self.shared_roll.add(roll) def get_pitch(self): pitch = self.drone.get_pitch() - return pitch + self.shared_pitch.add(pitch) def get_yaw(self): yaw = self.drone.get_yaw() - return yaw + self.shared_yaw.add(yaw) def get_landed_state(self): state = self.drone.get_landed_state() - return state + self.shared_landed_state.add(state) + + def set_cmd_pos(self): + x = self.shared_x.get() + y = self.shared_y.get() + z = self.shared_z.get() + az = self.shared_az.get() - def set_cmd_pos(self, x, y, z, az): self.drone.set_cmd_pos(x, y, z, az) - def set_cmd_vel(self, vx, vy, vz, az): + def set_cmd_vel(self): + vx = self.shared_vx.get() + vy = self.shared_vy.get() + vz = self.shared_vz.get() + az = self.shared_azt.get() self.drone.set_cmd_vel(vx, vy, vz, az) - def set_cmd_mix(self, vx, vy, z, az): + def set_cmd_mix(self): + vx = self.shared_vx.get() + vy = self.shared_vy.get() + z = self.shared_z.get() + az = self.shared_azt.get() self.drone.set_cmd_mix(vx, vy, z, az) - def takeoff(self, h=3): + def takeoff(self): + h = self.shared_takeoff_z.get() self.drone.takeoff(h) def land(self): self.drone.land() + + def update_hal(self): + self.get_frontal_image() + self.get_ventral_image() + self.get_position() + self.get_velocity() + self.get_yaw_rate() + self.get_orientation() + self.get_pitch() + self.get_roll() + self.get_yaw() + self.get_landed_state() + self.set_cmd_pos() + self.set_cmd_vel() + self.set_cmd_mix() + + # Destructor function to close all fds + def __del__(self): + self.shared_frontal_image.close() + self.shared_ventral_image.close() + self.shared_x.close() + self.shared_y.close() + self.shared_z.close() + self.shared_takeoff_z.close() + self.shared_az.close() + self.shared_azt.close() + self.shared_vx.close() + self.shared_vy.close() + self.shared_vz.close() + self.shared_landed_state.close() + self.shared_position.close() + self.shared_velocity.close() + self.shared_orientation.close() + self.shared_roll.close() + self.shared_pitch.close() + self.shared_yaw.close() + self.shared_yaw_rate.close() + +class ThreadHAL(threading.Thread): + def __init__(self, update_function): + super(ThreadHAL, self).__init__() + self.time_cycle = 80 + self.update_function = update_function + + def run(self): + while(True): + start_time = datetime.now() + + self.update_function() + + finish_time = datetime.now() + + dt = finish_time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + + if(ms < self.time_cycle): + time.sleep((self.time_cycle - ms) / 1000.0) \ No newline at end of file diff --git a/exercises/static/exercises/visual_lander/web-template/launch/gazebo.launch b/exercises/static/exercises/visual_lander/web-template/launch/gazebo.launch deleted file mode 100644 index 7768d03f8..000000000 --- a/exercises/static/exercises/visual_lander/web-template/launch/gazebo.launch +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/exercises/static/exercises/visual_lander/web-template/launch/launch.py b/exercises/static/exercises/visual_lander/web-template/launch/launch.py deleted file mode 100644 index d22afda8e..000000000 --- a/exercises/static/exercises/visual_lander/web-template/launch/launch.py +++ /dev/null @@ -1,131 +0,0 @@ -#!/usr/bin/env python3 - -import stat -import rospy -from os import lstat -from subprocess import Popen, PIPE - - -DRI_PATH = "/dev/dri/card0" -EXERCISE = "visual_lander" -TIMEOUT = 30 -MAX_ATTEMPT = 2 - - -# Check if acceleration can be enabled -def check_device(device_path): - try: - return stat.S_ISCHR(lstat(device_path)[stat.ST_MODE]) - except: - return False - - -# Spawn new process -def spawn_process(args, insert_vglrun=False): - if insert_vglrun: - args.insert(0, "vglrun") - process = Popen(args, stdout=PIPE, bufsize=1, universal_newlines=True) - return process - - -class Test(): - def gazebo(self): - rospy.logwarn("[GAZEBO] Launching") - try: - rospy.wait_for_service("/gazebo/get_model_properties", TIMEOUT) - return True - except rospy.ROSException: - return False - - def px4(self): - rospy.logwarn("[PX4-SITL] Launching") - start_time = rospy.get_time() - args = ["./PX4-Autopilot/build/px4_sitl_default/bin/px4-commander","--instance", "0", "check"] - while rospy.get_time() - start_time < TIMEOUT: - process = spawn_process(args, insert_vglrun=False) - with process.stdout: - for line in iter(process.stdout.readline, ''): - if ("Prearm check: OK" in line): - return True - rospy.sleep(2) - return False - - def mavros(self, ns=""): - rospy.logwarn("[MAVROS] Launching") - try: - rospy.wait_for_service(ns + "/mavros/cmd/arming", TIMEOUT) - return True - except rospy.ROSException: - return False - - -class Launch(): - def __init__(self): - self.test = Test() - self.acceleration_enabled = check_device(DRI_PATH) - - # Start roscore - args = ["/opt/ros/noetic/bin/roscore"] - spawn_process(args, insert_vglrun=False) - - rospy.init_node("launch", anonymous=True) - - def start(self): - ######## LAUNCH GAZEBO ######## - args = ["/opt/ros/noetic/bin/roslaunch", - "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/gazebo.launch", - "--wait", - "--log" - ] - - attempt = 1 - while True: - spawn_process(args, insert_vglrun=self.acceleration_enabled) - if self.test.gazebo() == True: - break - if attempt == MAX_ATTEMPT: - rospy.logerr("[GAZEBO] Launch Failed") - return - attempt = attempt + 1 - - - ######## LAUNCH PX4 ######## - args = ["/opt/ros/noetic/bin/roslaunch", - "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/px4.launch", - "--log" - ] - - attempt = 1 - while True: - spawn_process(args, insert_vglrun=self.acceleration_enabled) - if self.test.px4() == True: - break - if attempt == MAX_ATTEMPT: - rospy.logerr("[PX4] Launch Failed") - return - attempt = attempt + 1 - - - ######## LAUNCH MAVROS ######## - args = ["/opt/ros/noetic/bin/roslaunch", - "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/mavros.launch", - "--log" - ] - - attempt = 1 - while True: - spawn_process(args, insert_vglrun=self.acceleration_enabled) - if self.test.mavros() == True: - break - if attempt == MAX_ATTEMPT: - rospy.logerr("[MAVROS] Launch Failed") - return - attempt = attempt + 1 - - -if __name__ == "__main__": - launch = Launch() - launch.start() - - with open("/drones_launch.log", "w") as f: - f.write("success") diff --git a/exercises/static/exercises/visual_lander/web-template/launch/mavros.launch b/exercises/static/exercises/visual_lander/web-template/launch/mavros.launch deleted file mode 100644 index b899c0ec1..000000000 --- a/exercises/static/exercises/visual_lander/web-template/launch/mavros.launch +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/exercises/static/exercises/visual_lander/web-template/launch/px4.launch b/exercises/static/exercises/visual_lander/web-template/launch/px4.launch deleted file mode 100644 index 43f1f66a9..000000000 --- a/exercises/static/exercises/visual_lander/web-template/launch/px4.launch +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/exercises/static/exercises/visual_lander/web-template/launch/visual_lander.launch b/exercises/static/exercises/visual_lander/web-template/launch/visual_lander.launch new file mode 100755 index 000000000..9b4962255 --- /dev/null +++ b/exercises/static/exercises/visual_lander/web-template/launch/visual_lander.launch @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/exercises/static/exercises/visual_lander/web-template/shared/__init__.py b/exercises/static/exercises/visual_lander/web-template/shared/__init__.py new file mode 100755 index 000000000..e69de29bb diff --git a/exercises/static/exercises/visual_lander/web-template/car.py b/exercises/static/exercises/visual_lander/web-template/shared/car.py similarity index 100% rename from exercises/static/exercises/visual_lander/web-template/car.py rename to exercises/static/exercises/visual_lander/web-template/shared/car.py diff --git a/exercises/static/exercises/visual_lander/web-template/shared/image.py b/exercises/static/exercises/visual_lander/web-template/shared/image.py new file mode 100755 index 000000000..de5c9f9d6 --- /dev/null +++ b/exercises/static/exercises/visual_lander/web-template/shared/image.py @@ -0,0 +1,109 @@ +import numpy as np +import mmap +from posix_ipc import Semaphore, O_CREX, ExistentialError, O_CREAT, SharedMemory, unlink_shared_memory +from ctypes import sizeof, memmove, addressof, create_string_buffer +from shared.structure_img import MD + +# Probably, using self variables gives errors with memmove +# Therefore, a global variable for utility +md_buf = create_string_buffer(sizeof(MD)) + +class SharedImage: + def __init__(self, name): + # Initialize variables for memory regions and buffers and Semaphore + self.shm_buf = None; self.shm_region = None + self.md_buf = None; self.md_region = None + self.image_lock = None + + self.shm_name = name; self.md_name = name + "-meta" + self.image_lock_name = name + + # Initialize or retreive metadata memory region + try: + self.md_region = SharedMemory(self.md_name) + self.md_buf = mmap.mmap(self.md_region.fd, sizeof(MD)) + self.md_region.close_fd() + except ExistentialError: + self.md_region = SharedMemory(self.md_name, O_CREAT, size=sizeof(MD)) + self.md_buf = mmap.mmap(self.md_region.fd, self.md_region.size) + self.md_region.close_fd() + + # Initialize or retreive Semaphore + try: + self.image_lock = Semaphore(self.image_lock_name, O_CREX) + except ExistentialError: + image_lock = Semaphore(self.image_lock_name, O_CREAT) + image_lock.unlink() + self.image_lock = Semaphore(self.image_lock_name, O_CREX) + + self.image_lock.release() + + # Get the shared image + def get(self): + # Define metadata + metadata = MD() + + # Get metadata from the shared region + self.image_lock.acquire() + md_buf[:] = self.md_buf + memmove(addressof(metadata), md_buf, sizeof(metadata)) + self.image_lock.release() + + # Try to retreive the image from shm_buffer + # Otherwise return a zero image + try: + self.shm_region = SharedMemory(self.shm_name) + self.shm_buf = mmap.mmap(self.shm_region.fd, metadata.size) + self.shm_region.close_fd() + + self.image_lock.acquire() + image = np.ndarray(shape=(metadata.shape_0, metadata.shape_1, metadata.shape_2), + dtype='uint8', buffer=self.shm_buf) + self.image_lock.release() + + # Check for a None image + if(image.size == 0): + image = np.zeros((3, 3, 3), np.uint8) + + except ExistentialError: + image = np.zeros((3, 3, 3), np.uint8) + + return image + + # Add the shared image + def add(self, image): + try: + # Get byte size of the image + byte_size = image.nbytes + + # Get the shared memory buffer to read from + if not self.shm_region: + self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=byte_size) + self.shm_buf = mmap.mmap(self.shm_region.fd, byte_size) + self.shm_region.close_fd() + + # Generate meta data + metadata = MD(image.shape[0], image.shape[1], image.shape[2], byte_size) + + # Send the meta data and image to shared regions + self.image_lock.acquire() + memmove(md_buf, addressof(metadata), sizeof(metadata)) + self.md_buf[:] = md_buf[:] + self.shm_buf[:] = image.tobytes() + self.image_lock.release() + except: + pass + + # Destructor function to unlink and disconnect + def close(self): + self.image_lock.acquire() + self.md_buf.close() + + try: + unlink_shared_memory(self.md_name) + unlink_shared_memory(self.shm_name) + except ExistentialError: + pass + + self.image_lock.release() + self.image_lock.close() diff --git a/exercises/static/exercises/visual_lander/web-template/shared/structure_img.py b/exercises/static/exercises/visual_lander/web-template/shared/structure_img.py new file mode 100755 index 000000000..ae40b3707 --- /dev/null +++ b/exercises/static/exercises/visual_lander/web-template/shared/structure_img.py @@ -0,0 +1,9 @@ +from ctypes import Structure, c_int32, c_int64 + +class MD(Structure): + _fields_ = [ + ('shape_0', c_int32), + ('shape_1', c_int32), + ('shape_2', c_int32), + ('size', c_int64) + ] \ No newline at end of file diff --git a/exercises/static/exercises/visual_lander/web-template/shared/value.py b/exercises/static/exercises/visual_lander/web-template/shared/value.py new file mode 100755 index 000000000..e7bfad8a2 --- /dev/null +++ b/exercises/static/exercises/visual_lander/web-template/shared/value.py @@ -0,0 +1,93 @@ +import numpy as np +import mmap +from posix_ipc import Semaphore, O_CREX, ExistentialError, O_CREAT, SharedMemory, unlink_shared_memory +from ctypes import sizeof, memmove, addressof, create_string_buffer, c_float +import struct + +class SharedValue: + def __init__(self, name): + # Initialize varaibles for memory regions and buffers and Semaphore + self.shm_buf = None; self.shm_region = None + self.value_lock = None + + self.shm_name = name; self.value_lock_name = name + + # Initialize or retreive Semaphore + try: + self.value_lock = Semaphore(self.value_lock_name, O_CREX) + except ExistentialError: + value_lock = Semaphore(self.value_lock_name, O_CREAT) + value_lock.unlink() + self.value_lock = Semaphore(self.value_lock_name, O_CREX) + + self.value_lock.release() + + # Get the shared value + def get(self, type_name= "value"): + # Retreive the data from buffer + if type_name=="value": + try: + self.shm_region = SharedMemory(self.shm_name) + self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) + self.shm_region.close_fd() + except ExistentialError: + self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=sizeof(c_float)) + self.shm_buf = mmap.mmap(self.shm_region.fd, self.shm_region.size) + self.shm_region.close_fd() + self.value_lock.acquire() + value = struct.unpack('f', self.shm_buf)[0] + self.value_lock.release() + + return value + elif type_name=="list": + self.shm_region = SharedMemory(self.shm_name) + self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) + self.shm_region.close_fd() + self.value_lock.acquire() + array_val = np.ndarray(shape=(3,), + dtype='float32', buffer=self.shm_buf) + self.value_lock.release() + + return array_val + + else: + print("missing argument for return type") + + + # Add the shared value + def add(self, value, type_name= "value"): + # Send the data to shared regions + if type_name=="value": + try: + self.shm_region = SharedMemory(self.shm_name) + self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) + self.shm_region.close_fd() + except ExistentialError: + self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=sizeof(c_float)) + self.shm_buf = mmap.mmap(self.shm_region.fd, self.shm_region.size) + self.shm_region.close_fd() + + self.value_lock.acquire() + self.shm_buf[:] = struct.pack('f', value) + self.value_lock.release() + elif type_name=="list": + byte_size = value.nbytes + self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=byte_size) + self.shm_buf = mmap.mmap(self.shm_region.fd, byte_size) + self.shm_region.close_fd() + self.value_lock.acquire() + self.shm_buf[:] = value.tobytes() + self.value_lock.release() + + # Destructor function to unlink and disconnect + def close(self): + self.value_lock.acquire() + self.shm_buf.close() + + try: + unlink_shared_memory(self.shm_name) + except ExistentialError: + pass + + self.value_lock.release() + self.value_lock.close() diff --git a/exercises/static/exercises/visual_lander/web-template/user_functions.py b/exercises/static/exercises/visual_lander/web-template/user_functions.py new file mode 100755 index 000000000..d741601fc --- /dev/null +++ b/exercises/static/exercises/visual_lander/web-template/user_functions.py @@ -0,0 +1,117 @@ +from shared.image import SharedImage +from shared.value import SharedValue +import numpy as np +import cv2 + +# Define HAL functions +class HALFunctions: + def __init__(self): + # Initialize image variable + self.shared_frontal_image = SharedImage("halfrontalimage") + self.shared_ventral_image = SharedImage("halventralimage") + self.shared_x = SharedValue("x") + self.shared_y = SharedValue("y") + self.shared_z = SharedValue("z") + self.shared_takeoff_z = SharedValue("sharedtakeoffz") + self.shared_az = SharedValue("az") + self.shared_azt = SharedValue("azt") + self.shared_vx = SharedValue("vx") + self.shared_vy = SharedValue("vy") + self.shared_vz = SharedValue("vz") + self.shared_landed_state = SharedValue("landedstate") + self.shared_position = SharedValue("position") + self.shared_velocity = SharedValue("velocity") + self.shared_orientation = SharedValue("orientation") + self.shared_roll = SharedValue("roll") + self.shared_pitch = SharedValue("pitch") + self.shared_yaw = SharedValue("yaw") + self.shared_yaw_rate = SharedValue("yawrate") + + + # Get image function + def get_frontal_image(self): + image = self.shared_frontal_image.get() + return image + + # Get left image function + def get_ventral_image(self): + image = self.shared_ventral_image.get() + return image + + def takeoff(self, height): + self.shared_takeoff_z.add(height) + + def land(self): + pass + + def set_cmd_pos(self, x, y , z, az): + self.shared_x.add(x) + self.shared_y.add(y) + self.shared_z.add(z) + self.shared_az.add(az) + + def set_cmd_vel(self, vx, vy, vz, az): + self.shared_vx.add(vx) + self.shared_vy.add(vy) + self.shared_vz.add(vz) + self.shared_azt.add(az) + + def set_cmd_mix(self, vx, vy, z, az): + self.shared_vx.add(vx) + self.shared_vy.add(vy) + self.shared_vz.add(z) + self.shared_azt.add(az) + + + def get_position(self): + position = self.shared_position.get(type_name = "list") + return position + + def get_velocity(self): + velocity = self.shared_velocity.get(type_name = "list") + return velocity + + def get_yaw_rate(self): + yaw_rate = self.shared_yaw_rate.get(type_name = "value") + return yaw_rate + + def get_orientation(self): + orientation = self.shared_orientation.get(type_name = "list") + return orientation + + def get_roll(self): + roll = self.shared_roll.get(type_name = "value") + return roll + + def get_pitch(self): + pitch = self.shared_pitch.get(type_name = "value") + return pitch + + def get_yaw(self): + yaw = self.shared_yaw.get(type_name = "value") + return yaw + + def get_landed_state(self): + landed_state = self.shared_landed_state.get(type_name = "value") + return landed_state + +# Define GUI functions +class GUIFunctions: + def __init__(self): + # Initialize image variable + self.shared_image = SharedImage("guifrontalimage") + self.shared_left_image = SharedImage("guiventralimage") + + # Show image function + def showImage(self, image): + # Reshape to 3 channel if it has only 1 in order to display it + if (len(image.shape) < 3): + image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB) + self.shared_image.add(image) + + # Show left image function + def showLeftImage(self, image): + # Reshape to 3 channel if it has only 1 in order to display it + if (len(image.shape) < 3): + image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB) + self.shared_left_image.add(image) \ No newline at end of file diff --git a/scripts/Dockerfile b/scripts/Dockerfile index 07c457ea6..782bb8937 100644 --- a/scripts/Dockerfile +++ b/scripts/Dockerfile @@ -33,8 +33,8 @@ RUN cd /catkin_ws && rosdep update && rosdep install --from-paths . src --ignore RUN catkin config --cmake-args -DCMAKE_BUILD_TYPE=Release -DCATKIN_ENABLE_TESTING=False RUN /bin/bash -c '. /opt/ros/$ROS_DISTRO/setup.bash; cd /catkin_ws; catkin build -j1' -# This solves the plugin error that occurred while running rotors simulation for drone based exercises -RUN cp /catkin_ws/build/rotors_gazebo_plugins/Actuators.pb.cc /catkin_ws/build/rotors_gazebo_plugins/Actuators.pb.h /catkin_ws/src/CrazyS/rotors_gazebo_plugins/src/ ; cp /catkin_ws/build/rotors_gazebo_plugins/libmav_msgs.so /catkin_ws/devel/lib/ +# This solves the plugin error that occurred while running rotors simulation for drone based exercises and for follow_turtlebot texture on turtlebot +RUN cp /catkin_ws/build/rotors_gazebo_plugins/Actuators.pb.cc /catkin_ws/build/rotors_gazebo_plugins/Actuators.pb.h /catkin_ws/src/CrazyS/rotors_gazebo_plugins/src/ ; cp /catkin_ws/build/rotors_gazebo_plugins/libmav_msgs.so /catkin_ws/devel/lib/ ; cp /catkin_ws/src/drones/drone_assets/urdf/marker0.png /usr/share/gazebo-11/media/materials/textures/ ; cp /catkin_ws/src/drones/drone_assets/urdf/marker0.material /usr/share/gazebo-11/media/materials/scripts/ # RoboticsAcademy diff --git a/scripts/instructions.json b/scripts/instructions.json index 149b0c0b3..b87b5e2c6 100644 --- a/scripts/instructions.json +++ b/scripts/instructions.json @@ -95,8 +95,9 @@ }, "visual_lander": { "gazebo_path": "/RoboticsAcademy/exercises/visual_lander/web-template/launch", - "instructions_ros": ["python3 ./RoboticsAcademy/exercises/visual_lander/web-template/launch/launch.py"], - "instructions_host": "python3 /RoboticsAcademy/exercises/visual_lander/web-template/exercise.py 0.0.0.0" + "instructions_ros": ["/opt/ros/noetic/bin/roslaunch ./RoboticsAcademy/exercises/visual_lander/web-template/launch/visual_lander.launch"], + "instructions_host": "python3 /RoboticsAcademy/exercises/visual_lander/web-template/exercise.py 0.0.0.0", + "instructions_gui": "python3 /RoboticsAcademy/exercises/visual_lander/web-template/gui.py 0.0.0.0 {}" }, "opticalflow_teleop": { "gazebo_path": "/RoboticsAcademy/exercises/opticalflow_teleop/web-template/launch", From 9acb0317c5cb850c045561ab8e76031a72b699e1 Mon Sep 17 00:00:00 2001 From: RUFFY-369 Date: Mon, 12 Sep 2022 19:06:23 +0530 Subject: [PATCH 22/42] frontend final for visual lander --- .../web-template/visual_lander.world | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/exercises/static/exercises/visual_lander/web-template/visual_lander.world b/exercises/static/exercises/visual_lander/web-template/visual_lander.world index f6b208469..d191d33a6 100644 --- a/exercises/static/exercises/visual_lander/web-template/visual_lander.world +++ b/exercises/static/exercises/visual_lander/web-template/visual_lander.world @@ -8,6 +8,54 @@ 5 0 0.12 0 0 0 + + 3 + 5 0 0.01 + 10 0 0.01 + 15 0 0.01 + 25 0 0.01 + 30 0 0.01 + 35 0 0.01 + 40 0 0.01 + 45 0 0.01 + 50 0 0.01 + 55 0 0.01 + 60 0 0.01 + 65 0 0.01 + 70 0 0.01 + 75 0 0.01 + 80 0 0.01 + 85 0 0.01 + 90 0 0.01 + 95 0 0.01 + 100 0 0.01 + + + + model://gas_station + 5 36.0 0 0 0 0 + + + + + model://house_3 + 35 16 0 0 0 0 + house1 + + + + model://house_3 + 60 -16 0 0 0 0 + house2 + + + + model://house_3 + 80 16 0 0 0 0 + house3 + + + model://grass_plane From 6124f15386e34c092f453da445f3dbf837d176c5 Mon Sep 17 00:00:00 2001 From: RUFFY-369 Date: Mon, 12 Sep 2022 19:32:23 +0530 Subject: [PATCH 23/42] multiprocessing final for pos control --- .../position_control/web-template/brain.py | 169 +++++++++++ .../position_control/web-template/exercise.py | 269 ++++++------------ .../position_control/web-template/gui.py | 184 ++++++------ .../position_control/web-template/hal.py | 155 ++++++++-- .../web-template/launch/gazebo.launch | 24 -- .../web-template/launch/launch.py | 131 --------- .../web-template/launch/mavros.launch | 13 - .../launch/position_control.launch | 60 ++++ .../web-template/launch/px4.launch | 18 -- .../web-template/{ => shared}/Beacon.py | 0 .../web-template/shared/__init__.py | 0 .../web-template/shared/image.py | 109 +++++++ .../web-template/shared/structure_img.py | 9 + .../web-template/shared/value.py | 103 +++++++ .../web-template/user_functions.py | 136 +++++++++ scripts/instructions.json | 5 +- 16 files changed, 899 insertions(+), 486 deletions(-) create mode 100755 exercises/static/exercises/position_control/web-template/brain.py mode change 100644 => 100755 exercises/static/exercises/position_control/web-template/exercise.py mode change 100644 => 100755 exercises/static/exercises/position_control/web-template/gui.py mode change 100644 => 100755 exercises/static/exercises/position_control/web-template/hal.py delete mode 100644 exercises/static/exercises/position_control/web-template/launch/gazebo.launch delete mode 100644 exercises/static/exercises/position_control/web-template/launch/launch.py delete mode 100644 exercises/static/exercises/position_control/web-template/launch/mavros.launch create mode 100755 exercises/static/exercises/position_control/web-template/launch/position_control.launch delete mode 100644 exercises/static/exercises/position_control/web-template/launch/px4.launch rename exercises/static/exercises/position_control/web-template/{ => shared}/Beacon.py (100%) create mode 100755 exercises/static/exercises/position_control/web-template/shared/__init__.py create mode 100755 exercises/static/exercises/position_control/web-template/shared/image.py create mode 100755 exercises/static/exercises/position_control/web-template/shared/structure_img.py create mode 100755 exercises/static/exercises/position_control/web-template/shared/value.py create mode 100755 exercises/static/exercises/position_control/web-template/user_functions.py diff --git a/exercises/static/exercises/position_control/web-template/brain.py b/exercises/static/exercises/position_control/web-template/brain.py new file mode 100755 index 000000000..3076f1ae1 --- /dev/null +++ b/exercises/static/exercises/position_control/web-template/brain.py @@ -0,0 +1,169 @@ +import time +import threading +import multiprocessing +import sys +from datetime import datetime +import re +import json +import importlib + +import rospy +from std_srvs.srv import Empty +import cv2 + +from user_functions import GUIFunctions, HALFunctions +from console import start_console, close_console + +from shared.value import SharedValue + +# The brain process class +class BrainProcess(multiprocessing.Process): + def __init__(self, code, exit_signal): + super(BrainProcess, self).__init__() + + # Initialize exit signal + self.exit_signal = exit_signal + + # Function definitions for users to use + self.hal = HALFunctions() + self.gui = GUIFunctions() + + # Time variables + self.time_cycle = SharedValue('brain_time_cycle') + self.ideal_cycle = SharedValue('brain_ideal_cycle') + self.iteration_counter = 0 + + # Get the sequential and iterative code + # Something wrong over here! The code is reversing + # Found a solution but could not find the reason for this (parse_code function's return line of exercise.py is the reason) + self.sequential_code = code[1] + self.iterative_code = code[0] + + # Function to run to start the process + def run(self): + # Two threads for running and measuring + self.measure_thread = threading.Thread(target=self.measure_frequency) + self.thread = threading.Thread(target=self.process_code) + + self.measure_thread.start() + self.thread.start() + + print("Brain Process Started!") + + self.exit_signal.wait() + + # The process function + def process_code(self): + # Redirect information to console + start_console() + + # Reference Environment for the exec() function + iterative_code, sequential_code = self.iterative_code, self.sequential_code + + # The Python exec function + # Run the sequential part + gui_module, hal_module = self.generate_modules() + if sequential_code != "": + reference_environment = {"GUI": gui_module, "HAL": hal_module} + exec(sequential_code, reference_environment) + + # Run the iterative part inside template + # and keep the check for flag + while not self.exit_signal.is_set(): + start_time = datetime.now() + + # Execute the iterative portion + if iterative_code != "": + exec(iterative_code, reference_environment) + + # Template specifics to run! + finish_time = datetime.now() + dt = finish_time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + + # Keep updating the iteration counter + if(iterative_code == ""): + self.iteration_counter = 0 + else: + self.iteration_counter = self.iteration_counter + 1 + + # The code should be run for atleast the target time step + # If it's less put to sleep + # If it's more no problem as such, but we can change it! + time_cycle = self.time_cycle.get() + + if(ms < time_cycle): + time.sleep((time_cycle - ms) / 1000.0) + + close_console() + print("Current Thread Joined!") + + + # Function to generate the modules for use in ACE Editor + def generate_modules(self): + # Define HAL module + hal_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) + hal_module.HAL = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) + hal_module.HAL.motors = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("motors", None)) + + # Add HAL functions + hal_module.HAL.get_frontal_image = self.hal.get_frontal_image + hal_module.HAL.get_ventral_image = self.hal.get_ventral_image + hal_module.HAL.takeoff = self.hal.takeoff + hal_module.HAL.land = self.hal.land + hal_module.HAL.set_cmd_pos = self.hal.set_cmd_pos + hal_module.HAL.set_cmd_vel = self.hal.set_cmd_vel + hal_module.HAL.set_cmd_mix = self.hal.set_cmd_mix + hal_module.HAL.get_position = self.hal.get_position + hal_module.HAL.get_velocity = self.hal.get_velocity + hal_module.HAL.get_orientation = self.hal.get_orientation + hal_module.HAL.get_roll = self.hal.get_roll + hal_module.HAL.get_pitch = self.hal.get_pitch + hal_module.HAL.get_yaw = self.hal.get_yaw + hal_module.HAL.get_landed_state = self.hal.get_landed_state + hal_module.HAL.get_yaw_rate = self.hal.get_yaw_rate + hal_module.HAL.init_beacons = self.hal.init_beacons + hal_module.HAL.get_next_beacon = self.hal.get_next_beacon + + + + + # Define GUI module + gui_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) + gui_module.GUI = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) + + # Add GUI functions + gui_module.GUI.showImage = self.gui.showImage + gui_module.GUI.showLeftImage = self.gui.showLeftImage + + # Adding modules to system + # Protip: The names should be different from + # other modules, otherwise some errors + sys.modules["HAL"] = hal_module + sys.modules["GUI"] = gui_module + + return gui_module, hal_module + + # Function to measure the frequency of iterations + def measure_frequency(self): + previous_time = datetime.now() + # An infinite loop + while not self.exit_signal.is_set(): + # Sleep for 2 seconds + time.sleep(2) + + # Measure the current time and subtract from the previous time to get real time interval + current_time = datetime.now() + dt = current_time - previous_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + previous_time = current_time + + # Get the time period + try: + # Division by zero + self.ideal_cycle.add(ms / self.iteration_counter) + except: + self.ideal_cycle.add(0) + + # Reset the counter + self.iteration_counter = 0 \ No newline at end of file diff --git a/exercises/static/exercises/position_control/web-template/exercise.py b/exercises/static/exercises/position_control/web-template/exercise.py old mode 100644 new mode 100755 index 42c559a6a..82bf0c72a --- a/exercises/static/exercises/position_control/web-template/exercise.py +++ b/exercises/static/exercises/position_control/web-template/exercise.py @@ -1,3 +1,5 @@ + + #!/usr/bin/env python from __future__ import print_function @@ -5,6 +7,7 @@ from websocket_server import WebsocketServer import time import threading +import multiprocessing import subprocess import sys from datetime import datetime @@ -14,8 +17,13 @@ import rospy from std_srvs.srv import Empty +import cv2 + +from shared.value import SharedValue +from brain import BrainProcess +import queue + -from gui import GUI, ThreadGUI from hal import HAL from console import start_console, close_console @@ -25,36 +33,65 @@ class Template: # self.ideal_cycle to run an execution for at least 1 second # self.process for the current running process def __init__(self): - self.measure_thread = None - self.thread = None - self.reload = False - self.stop_brain = True - self.user_code = "" + + self.brain_process = None + self.reload = multiprocessing.Event() # Time variables - self.ideal_cycle = 80 - self.measured_cycle = 80 - self.iteration_counter = 0 + self.brain_time_cycle = SharedValue('brain_time_cycle') + self.brain_ideal_cycle = SharedValue('brain_ideal_cycle') self.real_time_factor = 0 + self.frequency_message = {'brain': '', 'gui': '', 'rtf': ''} + # GUI variables + self.gui_time_cycle = SharedValue('gui_time_cycle') + self.gui_ideal_cycle = SharedValue('gui_ideal_cycle') + self.server = None self.client = None self.host = sys.argv[1] - # Initialize the GUI, HAL and Console behind the scenes self.hal = HAL() - self.gui = GUI(self.host) + self.paused = False + # Function to parse the code # A few assumptions: # 1. The user always passes sequential and iterative codes # 2. Only a single infinite loop def parse_code(self, source_code): - sequential_code, iterative_code = self.seperate_seq_iter(source_code) - return iterative_code, sequential_code + # Check for save/load + if(source_code[:5] == "#save"): + source_code = source_code[5:] + self.save_code(source_code) - # Function to separate the iterative and sequential code + return "", "" + + elif(source_code[:5] == "#load"): + source_code = source_code + self.load_code() + self.server.send_message(self.client, source_code) + + return "", "" + + else: + sequential_code, iterative_code = self.seperate_seq_iter(source_code[6:]) + return iterative_code, sequential_code + + + # Function for saving + def save_code(self, source_code): + with open('code/academy.py', 'w') as code_file: + code_file.write(source_code) + + # Function for loading + def load_code(self): + with open('code/academy.py', 'r') as code_file: + source_code = code_file.read() + + return source_code + + # Function to seperate the iterative and sequential code def seperate_seq_iter(self, source_code): if source_code == "": return "", "" @@ -62,8 +99,8 @@ def seperate_seq_iter(self, source_code): # Search for an instance of while True infinite_loop = re.search(r'[^ ]while\s*\(\s*True\s*\)\s*:|[^ ]while\s*True\s*:|[^ ]while\s*1\s*:|[^ ]while\s*\(\s*1\s*\)\s*:', source_code) - # Separate the content inside while True and the other - # (Separating the sequential and iterative part!) + # Seperate the content inside while True and the other + # (Seperating the sequential and iterative part!) try: start_index = infinite_loop.start() iterative_code = source_code[start_index:] @@ -85,139 +122,16 @@ def seperate_seq_iter(self, source_code): return sequential_code, iterative_code - # The process function - def process_code(self, source_code): - # Redirect the information to console - start_console() - - iterative_code, sequential_code = self.parse_code(source_code) - - # print(sequential_code) - # print(iterative_code) - - # The Python exec function - # Run the sequential part - gui_module, hal_module = self.generate_modules() - reference_environment = {"GUI": gui_module, "HAL": hal_module} - while (self.stop_brain == True): - if (self.reload == True): - return - time.sleep(0.1) - exec(sequential_code, reference_environment) - - # Run the iterative part inside template - # and keep the check for flag - while self.reload == False: - while (self.stop_brain == True): - if (self.reload == True): - return - time.sleep(0.1) - - start_time = datetime.now() - - # Execute the iterative portion - exec(iterative_code, reference_environment) - - # Template specifics to run! - finish_time = datetime.now() - dt = finish_time - start_time - ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 - - # Keep updating the iteration counter - if (iterative_code == ""): - self.iteration_counter = 0 - else: - self.iteration_counter = self.iteration_counter + 1 - - # The code should be run for atleast the target time step - # If it's less put to sleep - if (ms < self.ideal_cycle): - time.sleep((self.ideal_cycle - ms) / 1000.0) - - close_console() - print("Current Thread Joined!") - - # Function to generate the modules for use in ACE Editor - def generate_modules(self): - # Define HAL module - hal_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) - hal_module.HAL = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) - # hal_module.drone = imp.new_module("drone") - # motors# hal_module.HAL.motors = imp.new_module("motors") - - # Add HAL functions - hal_module.HAL.get_frontal_image = self.hal.get_frontal_image - hal_module.HAL.get_ventral_image = self.hal.get_ventral_image - hal_module.HAL.get_position = self.hal.get_position - hal_module.HAL.get_velocity = self.hal.get_velocity - hal_module.HAL.get_yaw_rate = self.hal.get_yaw_rate - hal_module.HAL.get_orientation = self.hal.get_orientation - hal_module.HAL.get_roll = self.hal.get_roll - hal_module.HAL.get_pitch = self.hal.get_pitch - hal_module.HAL.get_yaw = self.hal.get_yaw - hal_module.HAL.get_landed_state = self.hal.get_landed_state - hal_module.HAL.set_cmd_pos = self.hal.set_cmd_pos - hal_module.HAL.set_cmd_vel = self.hal.set_cmd_vel - hal_module.HAL.set_cmd_mix = self.hal.set_cmd_mix - hal_module.HAL.takeoff = self.hal.takeoff - hal_module.HAL.land = self.hal.land - hal_module.HAL.init_beacons = self.hal.init_beacons - hal_module.HAL.get_next_beacon = self.hal.get_next_beacon - - # Define GUI module - gui_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) - gui_module.GUI = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) - - # Add GUI functions - gui_module.GUI.showImage = self.gui.showImage - gui_module.GUI.showLeftImage = self.gui.showLeftImage - - # Adding modules to system - # Protip: The names should be different from - # other modules, otherwise some errors - sys.modules["HAL"] = hal_module - sys.modules["GUI"] = gui_module - - return gui_module, hal_module - - # Function to measure the frequency of iterations - def measure_frequency(self): - previous_time = datetime.now() - # An infinite loop - while True: - # Sleep for 2 seconds - time.sleep(2) - - # Measure the current time and subtract from the previous time to get real time interval - current_time = datetime.now() - dt = current_time - previous_time - ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 - previous_time = current_time - - # Get the time period - try: - # Division by zero - self.measured_cycle = ms / self.iteration_counter - except: - self.measured_cycle = 0 - - # Reset the counter - self.iteration_counter = 0 - - # Send to client - self.send_frequency_message() - - # Function to generate and send frequency messages def send_frequency_message(self): # This function generates and sends frequency measures of the brain and gui - brain_frequency = 0; gui_frequency = 0 + brain_frequency = 0;gui_frequency = 0 try: - brain_frequency = round(1000 / self.measured_cycle, 1) + brain_frequency = round(1000 / self.brain_ideal_cycle.get(), 1) except ZeroDivisionError: brain_frequency = 0 try: - gui_frequency = round(1000 / self.thread_gui.measured_cycle, 1) + gui_frequency = round(1000 / self.gui_ideal_cycle.get(), 1) except ZeroDivisionError: gui_frequency = 0 @@ -242,29 +156,32 @@ def track_stats(self): args = ["gz", "stats", "-p"] # Prints gz statistics. "-p": Output comma-separated values containing- # real-time factor (percent), simtime (sec), realtime (sec), paused (T or F) - stats_process = subprocess.Popen(args, stdout=subprocess.PIPE, bufsize=1, universal_newlines=True) + stats_process = subprocess.Popen(args, stdout=subprocess.PIPE, bufsize=0) # bufsize=1 enables line-bufferred mode (the input buffer is flushed # automatically on newlines if you would write to process.stdin ) with stats_process.stdout: - for line in iter(stats_process.stdout.readline, ''): - stats_list = [x.strip() for x in line.split(',')] - self.real_time_factor = stats_list[0] + for line in iter(stats_process.stdout.readline, b''): + stats_list = [x.strip() for x in line.split(b',')] + self.real_time_factor = stats_list[0].decode("utf-8") # Function to maintain thread execution def execute_thread(self, source_code): # Keep checking until the thread is alive # The thread will die when the coming iteration reads the flag - if self.thread is not None: - while self.thread.is_alive(): - time.sleep(0.2) + if(self.brain_process != None): + while self.brain_process.is_alive(): + pass # Turn the flag down, the iteration has successfully stopped! - self.reload = False + self.reload.clear() # New thread execution - self.thread = threading.Thread(target=self.process_code, args=[source_code]) - self.thread.start() + code = self.parse_code(source_code) + if code[0] == "" and code[1] == "": + return + + self.brain_process = BrainProcess(code, self.reload) + self.brain_process.start() self.send_code_message() - print("New Thread Started!") # Function to read and set frequency from incoming message def read_frequency_message(self, message): @@ -272,66 +189,58 @@ def read_frequency_message(self, message): # Set brain frequency frequency = float(frequency_message["brain"]) - self.ideal_cycle = 1000.0 / frequency + self.brain_time_cycle.add(1000.0 / frequency) # Set gui frequency frequency = float(frequency_message["gui"]) - self.thread_gui.ideal_cycle = 1000.0 / frequency + self.gui_time_cycle.add(1000.0 / frequency) return # The websocket function # Gets called when there is an incoming message from the client def handle(self, client, server, message): - if message[:5] == "#freq": + if(message[:5] == "#freq"): frequency_message = message[5:] self.read_frequency_message(frequency_message) time.sleep(1) + self.send_frequency_message() return - elif(message[:5] == "#ping"): time.sleep(1) self.send_ping_message() return - - elif (message[:5] == "#code"): + elif (message[:5] == "#code"): try: # Once received turn the reload flag up and send it to execute_thread function - self.user_code = message[6:] + code = message # print(repr(code)) - self.reload = True - self.execute_thread(self.user_code) + self.reload.set() + self.execute_thread(code) except: pass - - elif (message[:5] == "#rest"): + + elif (message[:5] == "#stop"): try: - self.reload = True - self.stop_brain = True - self.execute_thread(self.user_code) + self.reload.set() + self.execute_thread(code) except: pass - - elif (message[:5] == "#stop"): - self.stop_brain = True - - elif (message[:5] == "#play"): - self.stop_brain = False + self.server.send_message(self.client, "#stpd") # Function that gets called when the server is connected def connected(self, client, server): self.client = client - # Start the GUI update thread - self.thread_gui = ThreadGUI(self.gui) - self.thread_gui.start() + # Start the HAL update thread + self.hal.start_thread() - # Start the real time factor tracker thread + # Start real time factor tracker thread self.stats_thread = threading.Thread(target=self.track_stats) self.stats_thread.start() - # Start measure frequency - self.measure_thread = threading.Thread(target=self.measure_frequency) - self.measure_thread.start() + # Initialize the ping message + self.send_frequency_message() + print("After sneding freweq msg") print(client, 'connected') diff --git a/exercises/static/exercises/position_control/web-template/gui.py b/exercises/static/exercises/position_control/web-template/gui.py old mode 100644 new mode 100755 index 51d327f1a..a040940b6 --- a/exercises/static/exercises/position_control/web-template/gui.py +++ b/exercises/static/exercises/position_control/web-template/gui.py @@ -5,6 +5,17 @@ import time from datetime import datetime from websocket_server import WebsocketServer +import logging +import rospy +import cv2 +import sys +import numpy as np +import multiprocessing + +from interfaces.pose3d import ListenerPose3d +from shared.image import SharedImage +from shared.value import SharedValue + # Graphical User Interface Class @@ -12,8 +23,8 @@ class GUI: # Initialization function # The actual initialization def __init__(self, host): - t = threading.Thread(target=self.run_server) + rospy.init_node("GUI") self.payload = {'image': ''} self.left_payload = {'image': ''} self.server = None @@ -21,81 +32,49 @@ def __init__(self, host): self.host = host - # Image variables - self.image_to_be_shown = None - self.image_to_be_shown_updated = False - self.image_show_lock = threading.Lock() - - self.left_image_to_be_shown = None - self.left_image_to_be_shown_updated = False - self.left_image_show_lock = threading.Lock() - - self.acknowledge = False - self.acknowledge_lock = threading.Lock() + # Image variable host + self.shared_image = SharedImage("guifrontalimage") + self.shared_left_image = SharedImage("guiventralimage") + # Event objects for multiprocessing + self.ack_event = multiprocessing.Event() + self.cli_event = multiprocessing.Event() # Take the console object to set the same websocket and client + + + # Start server thread + t = threading.Thread(target=self.run_server) t.start() - # Explicit initialization function - # Class method, so user can call it without instantiation - @classmethod - def initGUI(cls, host): - # self.payload = {'image': '', 'shape': []} - new_instance = cls(host) - return new_instance # Function to prepare image payload # Encodes the image as a JSON string and sends through the WS def payloadImage(self): - self.image_show_lock.acquire() - image_to_be_shown_updated = self.image_to_be_shown_updated - image_to_be_shown = self.image_to_be_shown - self.image_show_lock.release() - - image = image_to_be_shown + image = self.shared_image.get() payload = {'image': '', 'shape': ''} - - if not image_to_be_shown_updated: - return payload - + shape = image.shape frame = cv2.imencode('.JPEG', image)[1] encoded_image = base64.b64encode(frame) - + payload['image'] = encoded_image.decode('utf-8') payload['shape'] = shape - - self.image_show_lock.acquire() - self.image_to_be_shown_updated = False - self.image_show_lock.release() - + return payload - + # Function to prepare image payload # Encodes the image as a JSON string and sends through the WS def payloadLeftImage(self): - self.left_image_show_lock.acquire() - left_image_to_be_shown_updated = self.left_image_to_be_shown_updated - left_image_to_be_shown = self.left_image_to_be_shown - self.left_image_show_lock.release() - - image = left_image_to_be_shown + image = self.shared_left_image.get() payload = {'image': '', 'shape': ''} - - if not left_image_to_be_shown_updated: - return payload - + shape = image.shape frame = cv2.imencode('.JPEG', image)[1] encoded_image = base64.b64encode(frame) - + payload['image'] = encoded_image.decode('utf-8') payload['shape'] = shape - - self.left_image_show_lock.acquire() - self.left_image_to_be_shown_updated = False - self.left_image_show_lock.release() - + return payload # Function for student to call @@ -116,20 +95,10 @@ def showLeftImage(self, image): # Called when a new client is received def get_client(self, client, server): self.client = client + self.cli_event.set() + + print(client, 'connected') - # Function to get value of Acknowledge - def get_acknowledge(self): - self.acknowledge_lock.acquire() - acknowledge = self.acknowledge - self.acknowledge_lock.release() - - return acknowledge - - # Function to get value of Acknowledge - def set_acknowledge(self, value): - self.acknowledge_lock.acquire() - self.acknowledge = value - self.acknowledge_lock.release() # Update the gui def update_gui(self): @@ -151,14 +120,22 @@ def update_gui(self): # Gets called when there is an incoming message from the client def get_message(self, client, server, message): # Acknowledge Message for GUI Thread - if message[:4] == "#ack": - self.set_acknowledge(True) + + if(message[:4] == "#ack"): + # Set acknowledgement flag + self.ack_event.set() + # Function that gets called when the connected closes + def handle_close(self, client, server): + print(client, 'closed') + + # Activate the server def run_server(self): self.server = WebsocketServer(port=2303, host=self.host) self.server.set_fn_new_client(self.get_client) self.server.set_fn_message_received(self.get_message) + self.server.set_fn_client_left(self.handle_close) logged = False while not logged: @@ -179,32 +156,45 @@ def reset_gui(self): # This class decouples the user thread # and the GUI update thread -class ThreadGUI: - def __init__(self, gui): - self.gui = gui +class ProcessGUI(multiprocessing.Process): + def __init__(self): + super(ProcessGUI, self).__init__() + self.host = sys.argv[1] # Time variables - self.ideal_cycle = 80 - self.measured_cycle = 80 + self.time_cycle = SharedValue("gui_time_cycle") + self.ideal_cycle = SharedValue("gui_ideal_cycle") self.iteration_counter = 0 + # Function to initialize events + def initialize_events(self): + # Events + self.ack_event = self.gui.ack_event + self.cli_event = self.gui.cli_event + self.exit_signal = multiprocessing.Event() + # Function to start the execution of threads - def start(self): + def run(self): + # Initialize GUI + self.gui = GUI(self.host) + self.initialize_events() + + # Wait for client before starting + self.cli_event.wait() self.measure_thread = threading.Thread(target=self.measure_thread) - self.thread = threading.Thread(target=self.run) + self.thread = threading.Thread(target=self.run_gui) self.measure_thread.start() self.thread.start() - print("GUI Thread Started!") + print("GUI Process Started!") + + self.exit_signal.wait() # The measuring thread to measure frequency def measure_thread(self): - while self.gui.client is None: - pass - previous_time = datetime.now() - while True: + while(True): # Sleep for 2 seconds time.sleep(2) @@ -217,32 +207,40 @@ def measure_thread(self): # Get the time period try: # Division by zero - self.measured_cycle = ms / self.iteration_counter + self.ideal_cycle.add(ms / self.iteration_counter) except: - self.measured_cycle = 0 + self.ideal_cycle.add(0) # Reset the counter self.iteration_counter = 0 # The main thread of execution - def run(self): - while self.gui.client is None: - pass - - while True: + def run_gui(self): + while(True): start_time = datetime.now() + # Send update signal self.gui.update_gui() - acknowledge_message = self.gui.get_acknowledge() - - while not acknowledge_message: - acknowledge_message = self.gui.get_acknowledge() - self.gui.set_acknowledge(False) + # Wait for acknowldege signal + self.ack_event.wait() + self.ack_event.clear() finish_time = datetime.now() self.iteration_counter = self.iteration_counter + 1 dt = finish_time - start_time ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 - if ms < self.ideal_cycle: - time.sleep((self.ideal_cycle-ms) / 1000.0) + time_cycle = self.time_cycle.get() + + if(ms < time_cycle): + time.sleep((time_cycle-ms) / 1000.0) + + self.exit_signal.set() + + # Functions to handle auxillary GUI functions + def reset_gui(self): + self.gui.reset_gui() + +if __name__ == "__main__": + gui = ProcessGUI() + gui.start() \ No newline at end of file diff --git a/exercises/static/exercises/position_control/web-template/hal.py b/exercises/static/exercises/position_control/web-template/hal.py old mode 100644 new mode 100755 index 4f11a1fbc..eaf75d164 --- a/exercises/static/exercises/position_control/web-template/hal.py +++ b/exercises/static/exercises/position_control/web-template/hal.py @@ -1,10 +1,14 @@ -import numpy as np import rospy import cv2 +import threading +import time +from datetime import datetime +import numpy as np from drone_wrapper import DroneWrapper -from Beacon import Beacon - +from shared.Beacon import Beacon +from shared.image import SharedImage +from shared.value import SharedValue # Hardware Abstraction Layer class HAL: @@ -13,70 +17,109 @@ class HAL: def __init__(self): rospy.init_node("HAL") - + + self.shared_frontal_image = SharedImage("halfrontalimage") + self.shared_ventral_image = SharedImage("halventralimage") + self.shared_x = SharedValue("x") + self.shared_y = SharedValue("y") + self.shared_z = SharedValue("z") + self.shared_takeoff_z = SharedValue("sharedtakeoffz") + self.shared_az = SharedValue("az") + self.shared_azt = SharedValue("azt") + self.shared_vx = SharedValue("vx") + self.shared_vy = SharedValue("vy") + self.shared_vz = SharedValue("vz") + self.shared_landed_state = SharedValue("landedstate") + self.shared_position = SharedValue("position") + self.shared_velocity = SharedValue("velocity") + self.shared_orientation = SharedValue("orientation") + self.shared_roll = SharedValue("roll") + self.shared_pitch = SharedValue("pitch") + self.shared_yaw = SharedValue("yaw") + self.shared_yaw_rate = SharedValue("yawrate") + self.shared_beacons = SharedValue("beacons") + self.image = None - self.drone = DroneWrapper(name="rqt") + self.drone = DroneWrapper(name="rqt",ns="/iris/") + + # Update thread + self.thread = ThreadHAL(self.update_hal) # Explicit initialization functions # Class method, so user can call it without instantiation - @classmethod - def initRobot(cls): - new_instance = cls() - return new_instance + + + # Function to start the update thread + def start_thread(self): + self.thread.start() # Get Image from ROS Driver Camera def get_frontal_image(self): image = self.drone.get_frontal_image() image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) - return image_rgb + self.shared_frontal_image.add(image_rgb) def get_ventral_image(self): image = self.drone.get_ventral_image() image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) - return image_rgb + self.shared_ventral_image.add(image_rgb) def get_position(self): pos = self.drone.get_position() - return pos + self.shared_position.add(pos,type_name="list") def get_velocity(self): vel = self.drone.get_velocity() - return vel + self.shared_velocity.add(vel ,type_name="list") def get_yaw_rate(self): yaw_rate = self.drone.get_yaw_rate() - return yaw_rate + self.shared_yaw_rate.add(yaw_rate) def get_orientation(self): orientation = self.drone.get_orientation() - return orientation + self.shared_orientation.add(orientation ,type_name="list") def get_roll(self): roll = self.drone.get_roll() - return roll + self.shared_roll.add(roll) def get_pitch(self): pitch = self.drone.get_pitch() - return pitch + self.shared_pitch.add(pitch) def get_yaw(self): yaw = self.drone.get_yaw() - return yaw + self.shared_yaw.add(yaw) def get_landed_state(self): state = self.drone.get_landed_state() - return state + self.shared_landed_state.add(state) + + def set_cmd_pos(self): + x = self.shared_x.get() + y = self.shared_y.get() + z = self.shared_z.get() + az = self.shared_az.get() - def set_cmd_pos(self, x, y, z, az): self.drone.set_cmd_pos(x, y, z, az) - def set_cmd_vel(self, vx, vy, vz, az): + def set_cmd_vel(self): + vx = self.shared_vx.get() + vy = self.shared_vy.get() + vz = self.shared_vz.get() + az = self.shared_azt.get() self.drone.set_cmd_vel(vx, vy, vz, az) - def set_cmd_mix(self, vx, vy, z, az): + def set_cmd_mix(self): + vx = self.shared_vx.get() + vy = self.shared_vy.get() + z = self.shared_z.get() + az = self.shared_azt.get() self.drone.set_cmd_mix(vx, vy, z, az) - def takeoff(self, h=3): + def takeoff(self): + h = self.shared_takeoff_z.get() self.drone.takeoff(h) def land(self): @@ -90,9 +133,71 @@ def init_beacons(self): self.beacons.append(Beacon('beacon4', np.array([-5, 0, 0]), False, False)) self.beacons.append(Beacon('beacon5', np.array([10, 0, 0]), False, False)) self.beacons.append(Beacon('initial', np.array([0, 0, 0]), False, False)) + self.shared_beacons.add(self.beacons ,type_name="list") def get_next_beacon(self): for beacon in self.beacons: if beacon.is_reached() == False: - return beacon - return None \ No newline at end of file + self.shared_beacons.add(beacon ,type_name="list") + + + def update_hal(self): + self.get_frontal_image() + self.get_ventral_image() + self.get_position() + self.get_velocity() + self.get_yaw_rate() + self.get_orientation() + self.get_pitch() + self.get_roll() + self.get_yaw() + self.get_landed_state() + self.set_cmd_pos() + self.set_cmd_vel() + self.set_cmd_mix() + self.init_beacons() + self.get_next_beacon() + + + # Destructor function to close all fds + def __del__(self): + self.shared_frontal_image.close() + self.shared_ventral_image.close() + self.shared_x.close() + self.shared_y.close() + self.shared_z.close() + self.shared_takeoff_z.close() + self.shared_az.close() + self.shared_azt.close() + self.shared_vx.close() + self.shared_vy.close() + self.shared_vz.close() + self.shared_landed_state.close() + self.shared_position.close() + self.shared_velocity.close() + self.shared_orientation.close() + self.shared_roll.close() + self.shared_pitch.close() + self.shared_yaw.close() + self.shared_yaw_rate.close() + self.shared_beacons.close() + +class ThreadHAL(threading.Thread): + def __init__(self, update_function): + super(ThreadHAL, self).__init__() + self.time_cycle = 80 + self.update_function = update_function + + def run(self): + while(True): + start_time = datetime.now() + + self.update_function() + + finish_time = datetime.now() + + dt = finish_time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + + if(ms < self.time_cycle): + time.sleep((self.time_cycle - ms) / 1000.0) \ No newline at end of file diff --git a/exercises/static/exercises/position_control/web-template/launch/gazebo.launch b/exercises/static/exercises/position_control/web-template/launch/gazebo.launch deleted file mode 100644 index 9e5114f8d..000000000 --- a/exercises/static/exercises/position_control/web-template/launch/gazebo.launch +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/exercises/static/exercises/position_control/web-template/launch/launch.py b/exercises/static/exercises/position_control/web-template/launch/launch.py deleted file mode 100644 index 6e4c166ad..000000000 --- a/exercises/static/exercises/position_control/web-template/launch/launch.py +++ /dev/null @@ -1,131 +0,0 @@ -#!/usr/bin/env python3 - -import stat -import rospy -from os import lstat -from subprocess import Popen, PIPE - - -DRI_PATH = "/dev/dri/card0" -EXERCISE = "position_control" -TIMEOUT = 30 -MAX_ATTEMPT = 2 - - -# Check if acceleration can be enabled -def check_device(device_path): - try: - return stat.S_ISCHR(lstat(device_path)[stat.ST_MODE]) - except: - return False - - -# Spawn new process -def spawn_process(args, insert_vglrun=False): - if insert_vglrun: - args.insert(0, "vglrun") - process = Popen(args, stdout=PIPE, bufsize=1, universal_newlines=True) - return process - - -class Test(): - def gazebo(self): - rospy.logwarn("[GAZEBO] Launching") - try: - rospy.wait_for_service("/gazebo/get_model_properties", TIMEOUT) - return True - except rospy.ROSException: - return False - - def px4(self): - rospy.logwarn("[PX4-SITL] Launching") - start_time = rospy.get_time() - args = ["./PX4-Autopilot/build/px4_sitl_default/bin/px4-commander","--instance", "0", "check"] - while rospy.get_time() - start_time < TIMEOUT: - process = spawn_process(args, insert_vglrun=False) - with process.stdout: - for line in iter(process.stdout.readline, ''): - if ("Prearm check: OK" in line): - return True - rospy.sleep(2) - return False - - def mavros(self, ns=""): - rospy.logwarn("[MAVROS] Launching") - try: - rospy.wait_for_service(ns + "/mavros/cmd/arming", TIMEOUT) - return True - except rospy.ROSException: - return False - - -class Launch(): - def __init__(self): - self.test = Test() - self.acceleration_enabled = check_device(DRI_PATH) - - # Start roscore - args = ["/opt/ros/noetic/bin/roscore"] - spawn_process(args, insert_vglrun=False) - - rospy.init_node("launch", anonymous=True) - - def start(self): - ######## LAUNCH GAZEBO ######## - args = ["/opt/ros/noetic/bin/roslaunch", - "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/gazebo.launch", - "--wait", - "--log" - ] - - attempt = 1 - while True: - spawn_process(args, insert_vglrun=self.acceleration_enabled) - if self.test.gazebo() == True: - break - if attempt == MAX_ATTEMPT: - rospy.logerr("[GAZEBO] Launch Failed") - return - attempt = attempt + 1 - - - ######## LAUNCH PX4 ######## - args = ["/opt/ros/noetic/bin/roslaunch", - "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/px4.launch", - "--log" - ] - - attempt = 1 - while True: - spawn_process(args, insert_vglrun=self.acceleration_enabled) - if self.test.px4() == True: - break - if attempt == MAX_ATTEMPT: - rospy.logerr("[PX4] Launch Failed") - return - attempt = attempt + 1 - - - ######## LAUNCH MAVROS ######## - args = ["/opt/ros/noetic/bin/roslaunch", - "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/mavros.launch", - "--log" - ] - - attempt = 1 - while True: - spawn_process(args, insert_vglrun=self.acceleration_enabled) - if self.test.mavros() == True: - break - if attempt == MAX_ATTEMPT: - rospy.logerr("[MAVROS] Launch Failed") - return - attempt = attempt + 1 - - -if __name__ == "__main__": - launch = Launch() - launch.start() - - with open("/drones_launch.log", "w") as f: - f.write("success") diff --git a/exercises/static/exercises/position_control/web-template/launch/mavros.launch b/exercises/static/exercises/position_control/web-template/launch/mavros.launch deleted file mode 100644 index b899c0ec1..000000000 --- a/exercises/static/exercises/position_control/web-template/launch/mavros.launch +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/exercises/static/exercises/position_control/web-template/launch/position_control.launch b/exercises/static/exercises/position_control/web-template/launch/position_control.launch new file mode 100755 index 000000000..2447ba769 --- /dev/null +++ b/exercises/static/exercises/position_control/web-template/launch/position_control.launch @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/exercises/static/exercises/position_control/web-template/launch/px4.launch b/exercises/static/exercises/position_control/web-template/launch/px4.launch deleted file mode 100644 index 43f1f66a9..000000000 --- a/exercises/static/exercises/position_control/web-template/launch/px4.launch +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/exercises/static/exercises/position_control/web-template/Beacon.py b/exercises/static/exercises/position_control/web-template/shared/Beacon.py similarity index 100% rename from exercises/static/exercises/position_control/web-template/Beacon.py rename to exercises/static/exercises/position_control/web-template/shared/Beacon.py diff --git a/exercises/static/exercises/position_control/web-template/shared/__init__.py b/exercises/static/exercises/position_control/web-template/shared/__init__.py new file mode 100755 index 000000000..e69de29bb diff --git a/exercises/static/exercises/position_control/web-template/shared/image.py b/exercises/static/exercises/position_control/web-template/shared/image.py new file mode 100755 index 000000000..de5c9f9d6 --- /dev/null +++ b/exercises/static/exercises/position_control/web-template/shared/image.py @@ -0,0 +1,109 @@ +import numpy as np +import mmap +from posix_ipc import Semaphore, O_CREX, ExistentialError, O_CREAT, SharedMemory, unlink_shared_memory +from ctypes import sizeof, memmove, addressof, create_string_buffer +from shared.structure_img import MD + +# Probably, using self variables gives errors with memmove +# Therefore, a global variable for utility +md_buf = create_string_buffer(sizeof(MD)) + +class SharedImage: + def __init__(self, name): + # Initialize variables for memory regions and buffers and Semaphore + self.shm_buf = None; self.shm_region = None + self.md_buf = None; self.md_region = None + self.image_lock = None + + self.shm_name = name; self.md_name = name + "-meta" + self.image_lock_name = name + + # Initialize or retreive metadata memory region + try: + self.md_region = SharedMemory(self.md_name) + self.md_buf = mmap.mmap(self.md_region.fd, sizeof(MD)) + self.md_region.close_fd() + except ExistentialError: + self.md_region = SharedMemory(self.md_name, O_CREAT, size=sizeof(MD)) + self.md_buf = mmap.mmap(self.md_region.fd, self.md_region.size) + self.md_region.close_fd() + + # Initialize or retreive Semaphore + try: + self.image_lock = Semaphore(self.image_lock_name, O_CREX) + except ExistentialError: + image_lock = Semaphore(self.image_lock_name, O_CREAT) + image_lock.unlink() + self.image_lock = Semaphore(self.image_lock_name, O_CREX) + + self.image_lock.release() + + # Get the shared image + def get(self): + # Define metadata + metadata = MD() + + # Get metadata from the shared region + self.image_lock.acquire() + md_buf[:] = self.md_buf + memmove(addressof(metadata), md_buf, sizeof(metadata)) + self.image_lock.release() + + # Try to retreive the image from shm_buffer + # Otherwise return a zero image + try: + self.shm_region = SharedMemory(self.shm_name) + self.shm_buf = mmap.mmap(self.shm_region.fd, metadata.size) + self.shm_region.close_fd() + + self.image_lock.acquire() + image = np.ndarray(shape=(metadata.shape_0, metadata.shape_1, metadata.shape_2), + dtype='uint8', buffer=self.shm_buf) + self.image_lock.release() + + # Check for a None image + if(image.size == 0): + image = np.zeros((3, 3, 3), np.uint8) + + except ExistentialError: + image = np.zeros((3, 3, 3), np.uint8) + + return image + + # Add the shared image + def add(self, image): + try: + # Get byte size of the image + byte_size = image.nbytes + + # Get the shared memory buffer to read from + if not self.shm_region: + self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=byte_size) + self.shm_buf = mmap.mmap(self.shm_region.fd, byte_size) + self.shm_region.close_fd() + + # Generate meta data + metadata = MD(image.shape[0], image.shape[1], image.shape[2], byte_size) + + # Send the meta data and image to shared regions + self.image_lock.acquire() + memmove(md_buf, addressof(metadata), sizeof(metadata)) + self.md_buf[:] = md_buf[:] + self.shm_buf[:] = image.tobytes() + self.image_lock.release() + except: + pass + + # Destructor function to unlink and disconnect + def close(self): + self.image_lock.acquire() + self.md_buf.close() + + try: + unlink_shared_memory(self.md_name) + unlink_shared_memory(self.shm_name) + except ExistentialError: + pass + + self.image_lock.release() + self.image_lock.close() diff --git a/exercises/static/exercises/position_control/web-template/shared/structure_img.py b/exercises/static/exercises/position_control/web-template/shared/structure_img.py new file mode 100755 index 000000000..ae40b3707 --- /dev/null +++ b/exercises/static/exercises/position_control/web-template/shared/structure_img.py @@ -0,0 +1,9 @@ +from ctypes import Structure, c_int32, c_int64 + +class MD(Structure): + _fields_ = [ + ('shape_0', c_int32), + ('shape_1', c_int32), + ('shape_2', c_int32), + ('size', c_int64) + ] \ No newline at end of file diff --git a/exercises/static/exercises/position_control/web-template/shared/value.py b/exercises/static/exercises/position_control/web-template/shared/value.py new file mode 100755 index 000000000..6d32cf2cd --- /dev/null +++ b/exercises/static/exercises/position_control/web-template/shared/value.py @@ -0,0 +1,103 @@ +import numpy as np +import mmap +from posix_ipc import Semaphore, O_CREX, ExistentialError, O_CREAT, SharedMemory, unlink_shared_memory +from ctypes import sizeof, memmove, addressof, create_string_buffer, c_float +import struct + +class SharedValue: + def __init__(self, name): + # Initialize varaibles for memory regions and buffers and Semaphore + self.shm_buf = None; self.shm_region = None + self.value_lock = None + + self.shm_name = name; self.value_lock_name = name + + # Initialize shared memory buffer + # try: + # self.shm_region = SharedMemory(self.shm_name) + # self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) + # self.shm_region.close_fd() + #except ExistentialError: + # self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=sizeof(c_float)) + # self.shm_buf = mmap.mmap(self.shm_region.fd, self.shm_region.size) + # self.shm_region.close_fd() + + # Initialize or retreive Semaphore + try: + self.value_lock = Semaphore(self.value_lock_name, O_CREX) + except ExistentialError: + value_lock = Semaphore(self.value_lock_name, O_CREAT) + value_lock.unlink() + self.value_lock = Semaphore(self.value_lock_name, O_CREX) + + self.value_lock.release() + + # Get the shared value + def get(self, type_name= "value"): + # Retreive the data from buffer + if type_name=="value": + try: + self.shm_region = SharedMemory(self.shm_name) + self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) + self.shm_region.close_fd() + except ExistentialError: + self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=sizeof(c_float)) + self.shm_buf = mmap.mmap(self.shm_region.fd, self.shm_region.size) + self.shm_region.close_fd() + self.value_lock.acquire() + value = struct.unpack('f', self.shm_buf)[0] + self.value_lock.release() + + return value + elif type_name=="list": + self.shm_region = SharedMemory(self.shm_name) + self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) + self.shm_region.close_fd() + self.value_lock.acquire() + array_val = np.ndarray(shape=(6,), + dtype='float32', buffer=self.shm_buf) + self.value_lock.release() + + return array_val + + else: + print("missing argument for return type") + + + # Add the shared value + def add(self, value, type_name= "value"): + # Send the data to shared regions + if type_name=="value": + try: + self.shm_region = SharedMemory(self.shm_name) + self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) + self.shm_region.close_fd() + except ExistentialError: + self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=sizeof(c_float)) + self.shm_buf = mmap.mmap(self.shm_region.fd, self.shm_region.size) + self.shm_region.close_fd() + + self.value_lock.acquire() + self.shm_buf[:] = struct.pack('f', value) + self.value_lock.release() + elif type_name=="list": + byte_size = value.nbytes + self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=byte_size) + self.shm_buf = mmap.mmap(self.shm_region.fd, byte_size) + self.shm_region.close_fd() + self.value_lock.acquire() + self.shm_buf[:] = value.tobytes() + self.value_lock.release() + + # Destructor function to unlink and disconnect + def close(self): + self.value_lock.acquire() + self.shm_buf.close() + + try: + unlink_shared_memory(self.shm_name) + except ExistentialError: + pass + + self.value_lock.release() + self.value_lock.close() diff --git a/exercises/static/exercises/position_control/web-template/user_functions.py b/exercises/static/exercises/position_control/web-template/user_functions.py new file mode 100755 index 000000000..c95cc2113 --- /dev/null +++ b/exercises/static/exercises/position_control/web-template/user_functions.py @@ -0,0 +1,136 @@ +from shared.image import SharedImage +from shared.value import SharedValue +import numpy as np +import cv2 +from shared.Beacon import Beacon + +# Define HAL functions +class HALFunctions: + def __init__(self): + # Initialize image variable + self.shared_frontal_image = SharedImage("halfrontalimage") + self.shared_ventral_image = SharedImage("halventralimage") + self.shared_x = SharedValue("x") + self.shared_y = SharedValue("y") + self.shared_z = SharedValue("z") + self.shared_takeoff_z = SharedValue("sharedtakeoffz") + self.shared_az = SharedValue("az") + self.shared_azt = SharedValue("azt") + self.shared_vx = SharedValue("vx") + self.shared_vy = SharedValue("vy") + self.shared_vz = SharedValue("vz") + self.shared_landed_state = SharedValue("landedstate") + self.shared_position = SharedValue("position") + self.shared_velocity = SharedValue("velocity") + self.shared_orientation = SharedValue("orientation") + self.shared_roll = SharedValue("roll") + self.shared_pitch = SharedValue("pitch") + self.shared_yaw = SharedValue("yaw") + self.shared_yaw_rate = SharedValue("yawrate") + self.shared_beacons = SharedValue("beacons") + + + # Get image function + def get_frontal_image(self): + image = self.shared_frontal_image.get() + return image + + # Get left image function + def get_ventral_image(self): + image = self.shared_ventral_image.get() + return image + + def takeoff(self, height): + self.shared_takeoff_z.add(height) + + def land(self): + pass + + def set_cmd_pos(self, x, y , z, az): + self.shared_x.add(x) + self.shared_y.add(y) + self.shared_z.add(z) + self.shared_az.add(az) + + def set_cmd_vel(self, vx, vy, vz, az): + self.shared_vx.add(vx) + self.shared_vy.add(vy) + self.shared_vz.add(vz) + self.shared_azt.add(az) + + def set_cmd_mix(self, vx, vy, z, az): + self.shared_vx.add(vx) + self.shared_vy.add(vy) + self.shared_vz.add(z) + self.shared_azt.add(az) + + + def get_position(self): + position = self.shared_position.get(type_name = "list") + return position + + def get_velocity(self): + velocity = self.shared_velocity.get(type_name = "list") + return velocity + + def get_yaw_rate(self): + yaw_rate = self.shared_yaw_rate.get(type_name = "value") + return yaw_rate + + def get_orientation(self): + orientation = self.shared_orientation.get(type_name = "list") + return orientation + + def get_roll(self): + roll = self.shared_roll.get(type_name = "value") + return roll + + def get_pitch(self): + pitch = self.shared_pitch.get(type_name = "value") + return pitch + + def get_yaw(self): + yaw = self.shared_yaw.get(type_name = "value") + return yaw + + def get_landed_state(self): + landed_state = self.shared_landed_state.get(type_name = "value") + return landed_state + + def init_beacons(self): + self.beacons = [] + self.beacons.append(Beacon('beacon1', np.array([0, 5, 0]), False, False)) + self.beacons.append(Beacon('beacon2', np.array([5, 0, 0]), False, False)) + self.beacons.append(Beacon('beacon3', np.array([0, -5, 0]), False, False)) + self.beacons.append(Beacon('beacon4', np.array([-5, 0, 0]), False, False)) + self.beacons.append(Beacon('beacon5', np.array([10, 0, 0]), False, False)) + self.beacons.append(Beacon('initial', np.array([0, 0, 0]), False, False)) + self.shared_beacons.add(self.beacons ,type_name="list") + + def get_next_beacon(self): + for beacon in self.beacons: + if beacon.is_reached() == False: + self.shared_beacons.add(beacon ,type_name="list") + beacon = self.shared_landed_state.get(type_name = "value") + return beacon + +# Define GUI functions +class GUIFunctions: + def __init__(self): + # Initialize image variable + self.shared_image = SharedImage("guifrontalimage") + self.shared_left_image = SharedImage("guiventralimage") + + # Show image function + def showImage(self, image): + # Reshape to 3 channel if it has only 1 in order to display it + if (len(image.shape) < 3): + image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB) + self.shared_image.add(image) + + # Show left image function + def showLeftImage(self, image): + # Reshape to 3 channel if it has only 1 in order to display it + if (len(image.shape) < 3): + image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB) + self.shared_left_image.add(image) \ No newline at end of file diff --git a/scripts/instructions.json b/scripts/instructions.json index b87b5e2c6..70f5fae76 100644 --- a/scripts/instructions.json +++ b/scripts/instructions.json @@ -68,8 +68,9 @@ }, "position_control": { "gazebo_path": "/RoboticsAcademy/exercises/position_control/web-template/launch", - "instructions_ros": ["python3 ./RoboticsAcademy/exercises/position_control/web-template/launch/launch.py"], - "instructions_host": "python3 /RoboticsAcademy/exercises/position_control/web-template/exercise.py 0.0.0.0" + "instructions_ros": ["/opt/ros/noetic/bin/roslaunch ./RoboticsAcademy/exercises/position_control/web-template/launch/position_control.launch"], + "instructions_host": "python3 /RoboticsAcademy/exercises/position_control/web-template/exercise.py 0.0.0.0", + "instructions_gui": "python3 /RoboticsAcademy/exercises/position_control/web-template/gui.py 0.0.0.0 {}" }, "car_junction": { "gazebo_path": "/RoboticsAcademy/exercises/car_junction/web-template/launch", From 1fa63030e894ab4f17a916233dac2ef0d9eb16d7 Mon Sep 17 00:00:00 2001 From: RUFFY-369 Date: Mon, 12 Sep 2022 19:36:52 +0530 Subject: [PATCH 24/42] multiprocessing final for follow road --- .../follow_road/web-template/brain.py | 166 +++++++++++ .../follow_road/web-template/exercise.py | 265 ++++++------------ .../exercises/follow_road/web-template/gui.py | 180 ++++++------ .../exercises/follow_road/web-template/hal.py | 138 +++++++-- .../web-template/launch/follow_road.launch | 61 ++++ .../web-template/launch/gazebo.launch | 24 -- .../follow_road/web-template/launch/launch.py | 131 --------- .../web-template/launch/mavros.launch | 13 - .../web-template/launch/px4.launch | 21 -- .../web-template/shared/__init__.py | 0 .../follow_road/web-template/shared/image.py | 109 +++++++ .../web-template/shared/structure_img.py | 9 + .../follow_road/web-template/shared/value.py | 93 ++++++ .../web-template/user_functions.py | 117 ++++++++ .../launch/labyrinth_escape.launch | 2 +- scripts/instructions.json | 5 +- 16 files changed, 850 insertions(+), 484 deletions(-) create mode 100755 exercises/static/exercises/follow_road/web-template/brain.py create mode 100755 exercises/static/exercises/follow_road/web-template/launch/follow_road.launch delete mode 100644 exercises/static/exercises/follow_road/web-template/launch/gazebo.launch delete mode 100644 exercises/static/exercises/follow_road/web-template/launch/launch.py delete mode 100644 exercises/static/exercises/follow_road/web-template/launch/mavros.launch delete mode 100644 exercises/static/exercises/follow_road/web-template/launch/px4.launch create mode 100755 exercises/static/exercises/follow_road/web-template/shared/__init__.py create mode 100755 exercises/static/exercises/follow_road/web-template/shared/image.py create mode 100755 exercises/static/exercises/follow_road/web-template/shared/structure_img.py create mode 100755 exercises/static/exercises/follow_road/web-template/shared/value.py create mode 100755 exercises/static/exercises/follow_road/web-template/user_functions.py diff --git a/exercises/static/exercises/follow_road/web-template/brain.py b/exercises/static/exercises/follow_road/web-template/brain.py new file mode 100755 index 000000000..2330c04ba --- /dev/null +++ b/exercises/static/exercises/follow_road/web-template/brain.py @@ -0,0 +1,166 @@ +import time +import threading +import multiprocessing +import sys +from datetime import datetime +import re +import json +import importlib + +import rospy +from std_srvs.srv import Empty +import cv2 + +from user_functions import GUIFunctions, HALFunctions +from console import start_console, close_console + +from shared.value import SharedValue + +# The brain process class +class BrainProcess(multiprocessing.Process): + def __init__(self, code, exit_signal): + super(BrainProcess, self).__init__() + + # Initialize exit signal + self.exit_signal = exit_signal + + # Function definitions for users to use + self.hal = HALFunctions() + self.gui = GUIFunctions() + + # Time variables + self.time_cycle = SharedValue('brain_time_cycle') + self.ideal_cycle = SharedValue('brain_ideal_cycle') + self.iteration_counter = 0 + + # Get the sequential and iterative code + # Something wrong over here! The code is reversing + # Found a solution but could not find the reason for this (parse_code function's return line of exercise.py is the reason) + self.sequential_code = code[1] + self.iterative_code = code[0] + + # Function to run to start the process + def run(self): + # Two threads for running and measuring + self.measure_thread = threading.Thread(target=self.measure_frequency) + self.thread = threading.Thread(target=self.process_code) + + self.measure_thread.start() + self.thread.start() + + print("Brain Process Started!") + + self.exit_signal.wait() + + # The process function + def process_code(self): + # Redirect information to console + start_console() + + # Reference Environment for the exec() function + iterative_code, sequential_code = self.iterative_code, self.sequential_code + + # The Python exec function + # Run the sequential part + gui_module, hal_module = self.generate_modules() + if sequential_code != "": + reference_environment = {"GUI": gui_module, "HAL": hal_module} + exec(sequential_code, reference_environment) + + # Run the iterative part inside template + # and keep the check for flag + while not self.exit_signal.is_set(): + start_time = datetime.now() + + # Execute the iterative portion + if iterative_code != "": + exec(iterative_code, reference_environment) + + # Template specifics to run! + finish_time = datetime.now() + dt = finish_time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + + # Keep updating the iteration counter + if(iterative_code == ""): + self.iteration_counter = 0 + else: + self.iteration_counter = self.iteration_counter + 1 + + # The code should be run for atleast the target time step + # If it's less put to sleep + # If it's more no problem as such, but we can change it! + time_cycle = self.time_cycle.get() + + if(ms < time_cycle): + time.sleep((time_cycle - ms) / 1000.0) + + close_console() + print("Current Thread Joined!") + + + # Function to generate the modules for use in ACE Editor + def generate_modules(self): + # Define HAL module + hal_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) + hal_module.HAL = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) + hal_module.HAL.motors = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("motors", None)) + + # Add HAL functions + hal_module.HAL.get_frontal_image = self.hal.get_frontal_image + hal_module.HAL.get_ventral_image = self.hal.get_ventral_image + hal_module.HAL.takeoff = self.hal.takeoff + hal_module.HAL.land = self.hal.land + hal_module.HAL.set_cmd_pos = self.hal.set_cmd_pos + hal_module.HAL.set_cmd_vel = self.hal.set_cmd_vel + hal_module.HAL.set_cmd_mix = self.hal.set_cmd_mix + hal_module.HAL.get_position = self.hal.get_position + hal_module.HAL.get_velocity = self.hal.get_velocity + hal_module.HAL.get_orientation = self.hal.get_orientation + hal_module.HAL.get_roll = self.hal.get_roll + hal_module.HAL.get_pitch = self.hal.get_pitch + hal_module.HAL.get_yaw = self.hal.get_yaw + hal_module.HAL.get_landed_state = self.hal.get_landed_state + hal_module.HAL.get_yaw_rate = self.hal.get_yaw_rate + + + + # Define GUI module + gui_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) + gui_module.GUI = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) + + # Add GUI functions + gui_module.GUI.showImage = self.gui.showImage + gui_module.GUI.showLeftImage = self.gui.showLeftImage + + # Adding modules to system + # Protip: The names should be different from + # other modules, otherwise some errors + sys.modules["HAL"] = hal_module + sys.modules["GUI"] = gui_module + + return gui_module, hal_module + + # Function to measure the frequency of iterations + def measure_frequency(self): + previous_time = datetime.now() + # An infinite loop + while not self.exit_signal.is_set(): + # Sleep for 2 seconds + time.sleep(2) + + # Measure the current time and subtract from the previous time to get real time interval + current_time = datetime.now() + dt = current_time - previous_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + previous_time = current_time + + # Get the time period + try: + # Division by zero + self.ideal_cycle.add(ms / self.iteration_counter) + except: + self.ideal_cycle.add(0) + + # Reset the counter + self.iteration_counter = 0 \ No newline at end of file diff --git a/exercises/static/exercises/follow_road/web-template/exercise.py b/exercises/static/exercises/follow_road/web-template/exercise.py index 71d5934a8..82bf0c72a 100755 --- a/exercises/static/exercises/follow_road/web-template/exercise.py +++ b/exercises/static/exercises/follow_road/web-template/exercise.py @@ -1,3 +1,5 @@ + + #!/usr/bin/env python from __future__ import print_function @@ -5,6 +7,7 @@ from websocket_server import WebsocketServer import time import threading +import multiprocessing import subprocess import sys from datetime import datetime @@ -14,8 +17,13 @@ import rospy from std_srvs.srv import Empty +import cv2 + +from shared.value import SharedValue +from brain import BrainProcess +import queue + -from gui import GUI, ThreadGUI from hal import HAL from console import start_console, close_console @@ -25,36 +33,65 @@ class Template: # self.ideal_cycle to run an execution for at least 1 second # self.process for the current running process def __init__(self): - self.measure_thread = None - self.thread = None - self.reload = False - self.stop_brain = True - self.user_code = "" + + self.brain_process = None + self.reload = multiprocessing.Event() # Time variables - self.ideal_cycle = 80 - self.measured_cycle = 80 - self.iteration_counter = 0 + self.brain_time_cycle = SharedValue('brain_time_cycle') + self.brain_ideal_cycle = SharedValue('brain_ideal_cycle') self.real_time_factor = 0 + self.frequency_message = {'brain': '', 'gui': '', 'rtf': ''} + # GUI variables + self.gui_time_cycle = SharedValue('gui_time_cycle') + self.gui_ideal_cycle = SharedValue('gui_ideal_cycle') + self.server = None self.client = None self.host = sys.argv[1] - # Initialize the GUI, HAL and Console behind the scenes self.hal = HAL() - self.gui = GUI(self.host) + self.paused = False + # Function to parse the code # A few assumptions: # 1. The user always passes sequential and iterative codes # 2. Only a single infinite loop def parse_code(self, source_code): - sequential_code, iterative_code = self.seperate_seq_iter(source_code) - return iterative_code, sequential_code + # Check for save/load + if(source_code[:5] == "#save"): + source_code = source_code[5:] + self.save_code(source_code) + + return "", "" + + elif(source_code[:5] == "#load"): + source_code = source_code + self.load_code() + self.server.send_message(self.client, source_code) + + return "", "" + + else: + sequential_code, iterative_code = self.seperate_seq_iter(source_code[6:]) + return iterative_code, sequential_code + - # Function to separate the iterative and sequential code + # Function for saving + def save_code(self, source_code): + with open('code/academy.py', 'w') as code_file: + code_file.write(source_code) + + # Function for loading + def load_code(self): + with open('code/academy.py', 'r') as code_file: + source_code = code_file.read() + + return source_code + + # Function to seperate the iterative and sequential code def seperate_seq_iter(self, source_code): if source_code == "": return "", "" @@ -62,8 +99,8 @@ def seperate_seq_iter(self, source_code): # Search for an instance of while True infinite_loop = re.search(r'[^ ]while\s*\(\s*True\s*\)\s*:|[^ ]while\s*True\s*:|[^ ]while\s*1\s*:|[^ ]while\s*\(\s*1\s*\)\s*:', source_code) - # Separate the content inside while True and the other - # (Separating the sequential and iterative part!) + # Seperate the content inside while True and the other + # (Seperating the sequential and iterative part!) try: start_index = infinite_loop.start() iterative_code = source_code[start_index:] @@ -85,137 +122,16 @@ def seperate_seq_iter(self, source_code): return sequential_code, iterative_code - # The process function - def process_code(self, source_code): - # Redirect the information to console - start_console() - - iterative_code, sequential_code = self.parse_code(source_code) - - # print(sequential_code) - # print(iterative_code) - - # The Python exec function - # Run the sequential part - gui_module, hal_module = self.generate_modules() - reference_environment = {"GUI": gui_module, "HAL": hal_module} - while (self.stop_brain == True): - if (self.reload == True): - return - time.sleep(0.1) - exec(sequential_code, reference_environment) - - # Run the iterative part inside template - # and keep the check for flag - while self.reload == False: - while (self.stop_brain == True): - if (self.reload == True): - return - time.sleep(0.1) - - start_time = datetime.now() - - # Execute the iterative portion - exec(iterative_code, reference_environment) - - # Template specifics to run! - finish_time = datetime.now() - dt = finish_time - start_time - ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 - - # Keep updating the iteration counter - if (iterative_code == ""): - self.iteration_counter = 0 - else: - self.iteration_counter = self.iteration_counter + 1 - - # The code should be run for atleast the target time step - # If it's less put to sleep - if (ms < self.ideal_cycle): - time.sleep((self.ideal_cycle - ms) / 1000.0) - - close_console() - print("Current Thread Joined!") - - # Function to generate the modules for use in ACE Editor - def generate_modules(self): - # Define HAL module - hal_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) - hal_module.HAL = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) - # hal_module.drone = imp.new_module("drone") - # motors# hal_module.HAL.motors = imp.new_module("motors") - - # Add HAL functions - hal_module.HAL.get_frontal_image = self.hal.get_frontal_image - hal_module.HAL.get_ventral_image = self.hal.get_ventral_image - hal_module.HAL.get_position = self.hal.get_position - hal_module.HAL.get_velocity = self.hal.get_velocity - hal_module.HAL.get_yaw_rate = self.hal.get_yaw_rate - hal_module.HAL.get_orientation = self.hal.get_orientation - hal_module.HAL.get_roll = self.hal.get_roll - hal_module.HAL.get_pitch = self.hal.get_pitch - hal_module.HAL.get_yaw = self.hal.get_yaw - hal_module.HAL.get_landed_state = self.hal.get_landed_state - hal_module.HAL.set_cmd_pos = self.hal.set_cmd_pos - hal_module.HAL.set_cmd_vel = self.hal.set_cmd_vel - hal_module.HAL.set_cmd_mix = self.hal.set_cmd_mix - hal_module.HAL.takeoff = self.hal.takeoff - hal_module.HAL.land = self.hal.land - - # Define GUI module - gui_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) - gui_module.GUI = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) - - # Add GUI functions - gui_module.GUI.showImage = self.gui.showImage - gui_module.GUI.showLeftImage = self.gui.showLeftImage - - # Adding modules to system - # Protip: The names should be different from - # other modules, otherwise some errors - sys.modules["HAL"] = hal_module - sys.modules["GUI"] = gui_module - - return gui_module, hal_module - - # Function to measure the frequency of iterations - def measure_frequency(self): - previous_time = datetime.now() - # An infinite loop - while True: - # Sleep for 2 seconds - time.sleep(2) - - # Measure the current time and subtract from the previous time to get real time interval - current_time = datetime.now() - dt = current_time - previous_time - ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 - previous_time = current_time - - # Get the time period - try: - # Division by zero - self.measured_cycle = ms / self.iteration_counter - except: - self.measured_cycle = 0 - - # Reset the counter - self.iteration_counter = 0 - - # Send to client - self.send_frequency_message() - - # Function to generate and send frequency messages def send_frequency_message(self): # This function generates and sends frequency measures of the brain and gui - brain_frequency = 0; gui_frequency = 0 + brain_frequency = 0;gui_frequency = 0 try: - brain_frequency = round(1000 / self.measured_cycle, 1) + brain_frequency = round(1000 / self.brain_ideal_cycle.get(), 1) except ZeroDivisionError: brain_frequency = 0 try: - gui_frequency = round(1000 / self.thread_gui.measured_cycle, 1) + gui_frequency = round(1000 / self.gui_ideal_cycle.get(), 1) except ZeroDivisionError: gui_frequency = 0 @@ -240,29 +156,32 @@ def track_stats(self): args = ["gz", "stats", "-p"] # Prints gz statistics. "-p": Output comma-separated values containing- # real-time factor (percent), simtime (sec), realtime (sec), paused (T or F) - stats_process = subprocess.Popen(args, stdout=subprocess.PIPE, bufsize=1, universal_newlines=True) + stats_process = subprocess.Popen(args, stdout=subprocess.PIPE, bufsize=0) # bufsize=1 enables line-bufferred mode (the input buffer is flushed # automatically on newlines if you would write to process.stdin ) with stats_process.stdout: - for line in iter(stats_process.stdout.readline, ''): - stats_list = [x.strip() for x in line.split(',')] - self.real_time_factor = stats_list[0] + for line in iter(stats_process.stdout.readline, b''): + stats_list = [x.strip() for x in line.split(b',')] + self.real_time_factor = stats_list[0].decode("utf-8") # Function to maintain thread execution def execute_thread(self, source_code): # Keep checking until the thread is alive # The thread will die when the coming iteration reads the flag - if self.thread is not None: - while self.thread.is_alive(): - time.sleep(0.2) + if(self.brain_process != None): + while self.brain_process.is_alive(): + pass # Turn the flag down, the iteration has successfully stopped! - self.reload = False + self.reload.clear() # New thread execution - self.thread = threading.Thread(target=self.process_code, args=[source_code]) - self.thread.start() + code = self.parse_code(source_code) + if code[0] == "" and code[1] == "": + return + + self.brain_process = BrainProcess(code, self.reload) + self.brain_process.start() self.send_code_message() - print("New Thread Started!") # Function to read and set frequency from incoming message def read_frequency_message(self, message): @@ -270,66 +189,58 @@ def read_frequency_message(self, message): # Set brain frequency frequency = float(frequency_message["brain"]) - self.ideal_cycle = 1000.0 / frequency + self.brain_time_cycle.add(1000.0 / frequency) # Set gui frequency frequency = float(frequency_message["gui"]) - self.thread_gui.ideal_cycle = 1000.0 / frequency + self.gui_time_cycle.add(1000.0 / frequency) return # The websocket function # Gets called when there is an incoming message from the client def handle(self, client, server, message): - if message[:5] == "#freq": + if(message[:5] == "#freq"): frequency_message = message[5:] self.read_frequency_message(frequency_message) time.sleep(1) + self.send_frequency_message() return - elif(message[:5] == "#ping"): time.sleep(1) self.send_ping_message() return - - elif (message[:5] == "#code"): + elif (message[:5] == "#code"): try: # Once received turn the reload flag up and send it to execute_thread function - self.user_code = message[6:] + code = message # print(repr(code)) - self.reload = True - self.execute_thread(self.user_code) + self.reload.set() + self.execute_thread(code) except: pass - elif (message[:5] == "#rest"): + elif (message[:5] == "#stop"): try: - self.reload = True - self.stop_brain = True - self.execute_thread(self.user_code) + self.reload.set() + self.execute_thread(code) except: pass - - elif (message[:5] == "#stop"): - self.stop_brain = True - - elif (message[:5] == "#play"): - self.stop_brain = False + self.server.send_message(self.client, "#stpd") # Function that gets called when the server is connected def connected(self, client, server): self.client = client - # Start the GUI update thread - self.thread_gui = ThreadGUI(self.gui) - self.thread_gui.start() + # Start the HAL update thread + self.hal.start_thread() - # Start the real time factor tracker thread + # Start real time factor tracker thread self.stats_thread = threading.Thread(target=self.track_stats) self.stats_thread.start() - # Start measure frequency - self.measure_thread = threading.Thread(target=self.measure_frequency) - self.measure_thread.start() + # Initialize the ping message + self.send_frequency_message() + print("After sneding freweq msg") print(client, 'connected') diff --git a/exercises/static/exercises/follow_road/web-template/gui.py b/exercises/static/exercises/follow_road/web-template/gui.py index f4fd34044..2b2e10176 100755 --- a/exercises/static/exercises/follow_road/web-template/gui.py +++ b/exercises/static/exercises/follow_road/web-template/gui.py @@ -6,15 +6,22 @@ from datetime import datetime from websocket_server import WebsocketServer import logging +import rospy +import cv2 +import sys +import numpy as np +import multiprocessing +from shared.image import SharedImage +from shared.value import SharedValue # Graphical User Interface Class class GUI: # Initialization function # The actual initialization def __init__(self, host): - t = threading.Thread(target=self.run_server) + rospy.init_node("GUI") self.payload = {'image': ''} self.left_payload = {'image': ''} self.server = None @@ -22,81 +29,47 @@ def __init__(self, host): self.host = host - # Image variables - self.image_to_be_shown = None - self.image_to_be_shown_updated = False - self.image_show_lock = threading.Lock() - - self.left_image_to_be_shown = None - self.left_image_to_be_shown_updated = False - self.left_image_show_lock = threading.Lock() - - self.acknowledge = False - self.acknowledge_lock = threading.Lock() + # Image variable host + self.shared_image = SharedImage("guifrontalimage") + self.shared_left_image = SharedImage("guiventralimage") - # Take the console object to set the same websocket and client + # Event objects for multiprocessing + self.ack_event = multiprocessing.Event() + self.cli_event = multiprocessing.Event() + + # Start server thread + t = threading.Thread(target=self.run_server) t.start() - # Explicit initialization function - # Class method, so user can call it without instantiation - @classmethod - def initGUI(cls, host): - # self.payload = {'image': '', 'shape': []} - new_instance = cls(host) - return new_instance # Function to prepare image payload # Encodes the image as a JSON string and sends through the WS def payloadImage(self): - self.image_show_lock.acquire() - image_to_be_shown_updated = self.image_to_be_shown_updated - image_to_be_shown = self.image_to_be_shown - self.image_show_lock.release() - - image = image_to_be_shown + image = self.shared_image.get() payload = {'image': '', 'shape': ''} - - if not image_to_be_shown_updated: - return payload - + shape = image.shape frame = cv2.imencode('.JPEG', image)[1] encoded_image = base64.b64encode(frame) - + payload['image'] = encoded_image.decode('utf-8') payload['shape'] = shape - - self.image_show_lock.acquire() - self.image_to_be_shown_updated = False - self.image_show_lock.release() - + return payload - + # Function to prepare image payload # Encodes the image as a JSON string and sends through the WS def payloadLeftImage(self): - self.left_image_show_lock.acquire() - left_image_to_be_shown_updated = self.left_image_to_be_shown_updated - left_image_to_be_shown = self.left_image_to_be_shown - self.left_image_show_lock.release() - - image = left_image_to_be_shown + image = self.shared_left_image.get() payload = {'image': '', 'shape': ''} - - if not left_image_to_be_shown_updated: - return payload - + shape = image.shape frame = cv2.imencode('.JPEG', image)[1] encoded_image = base64.b64encode(frame) - + payload['image'] = encoded_image.decode('utf-8') payload['shape'] = shape - - self.left_image_show_lock.acquire() - self.left_image_to_be_shown_updated = False - self.left_image_show_lock.release() - + return payload # Function for student to call @@ -117,20 +90,10 @@ def showLeftImage(self, image): # Called when a new client is received def get_client(self, client, server): self.client = client + self.cli_event.set() + + print(client, 'connected') - # Function to get value of Acknowledge - def get_acknowledge(self): - self.acknowledge_lock.acquire() - acknowledge = self.acknowledge - self.acknowledge_lock.release() - - return acknowledge - - # Function to get value of Acknowledge - def set_acknowledge(self, value): - self.acknowledge_lock.acquire() - self.acknowledge = value - self.acknowledge_lock.release() # Update the gui def update_gui(self): @@ -152,8 +115,15 @@ def update_gui(self): # Gets called when there is an incoming message from the client def get_message(self, client, server, message): # Acknowledge Message for GUI Thread - if message[:4] == "#ack": - self.set_acknowledge(True) + + if(message[:4] == "#ack"): + # Set acknowledgement flag + self.ack_event.set() + + # Function that gets called when the connected closes + def handle_close(self, client, server): + print(client, 'closed') + # Activate the server @@ -161,6 +131,7 @@ def run_server(self): self.server = WebsocketServer(port=2303, host=self.host) self.server.set_fn_new_client(self.get_client) self.server.set_fn_message_received(self.get_message) + self.server.set_fn_client_left(self.handle_close) logged = False while not logged: @@ -181,32 +152,45 @@ def reset_gui(self): # This class decouples the user thread # and the GUI update thread -class ThreadGUI: - def __init__(self, gui): - self.gui = gui +class ProcessGUI(multiprocessing.Process): + def __init__(self): + super(ProcessGUI, self).__init__() + self.host = sys.argv[1] # Time variables - self.ideal_cycle = 80 - self.measured_cycle = 80 + self.time_cycle = SharedValue("gui_time_cycle") + self.ideal_cycle = SharedValue("gui_ideal_cycle") self.iteration_counter = 0 + # Function to initialize events + def initialize_events(self): + # Events + self.ack_event = self.gui.ack_event + self.cli_event = self.gui.cli_event + self.exit_signal = multiprocessing.Event() + # Function to start the execution of threads - def start(self): + def run(self): + # Initialize GUI + self.gui = GUI(self.host) + self.initialize_events() + + # Wait for client before starting + self.cli_event.wait() self.measure_thread = threading.Thread(target=self.measure_thread) - self.thread = threading.Thread(target=self.run) + self.thread = threading.Thread(target=self.run_gui) self.measure_thread.start() self.thread.start() - print("GUI Thread Started!") + print("GUI Process Started!") + + self.exit_signal.wait() # The measuring thread to measure frequency def measure_thread(self): - while self.gui.client is None: - pass - previous_time = datetime.now() - while True: + while(True): # Sleep for 2 seconds time.sleep(2) @@ -219,32 +203,40 @@ def measure_thread(self): # Get the time period try: # Division by zero - self.measured_cycle = ms / self.iteration_counter + self.ideal_cycle.add(ms / self.iteration_counter) except: - self.measured_cycle = 0 + self.ideal_cycle.add(0) # Reset the counter self.iteration_counter = 0 # The main thread of execution - def run(self): - while self.gui.client is None: - pass - - while True: + def run_gui(self): + while(True): start_time = datetime.now() + # Send update signal self.gui.update_gui() - acknowledge_message = self.gui.get_acknowledge() - - while not acknowledge_message: - acknowledge_message = self.gui.get_acknowledge() - self.gui.set_acknowledge(False) + # Wait for acknowldege signal + self.ack_event.wait() + self.ack_event.clear() finish_time = datetime.now() self.iteration_counter = self.iteration_counter + 1 dt = finish_time - start_time ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 - if ms < self.ideal_cycle: - time.sleep((self.ideal_cycle-ms) / 1000.0) + time_cycle = self.time_cycle.get() + + if(ms < time_cycle): + time.sleep((time_cycle-ms) / 1000.0) + + self.exit_signal.set() + + # Functions to handle auxillary GUI functions + def reset_gui(self): + self.gui.reset_gui() + +if __name__ == "__main__": + gui = ProcessGUI() + gui.start() \ No newline at end of file diff --git a/exercises/static/exercises/follow_road/web-template/hal.py b/exercises/static/exercises/follow_road/web-template/hal.py index fd13904d6..17c39cd6b 100755 --- a/exercises/static/exercises/follow_road/web-template/hal.py +++ b/exercises/static/exercises/follow_road/web-template/hal.py @@ -5,7 +5,8 @@ from datetime import datetime from drone_wrapper import DroneWrapper - +from shared.image import SharedImage +from shared.value import SharedValue # Hardware Abstraction Layer class HAL: @@ -14,71 +15,166 @@ class HAL: def __init__(self): rospy.init_node("HAL") - + + self.shared_frontal_image = SharedImage("halfrontalimage") + self.shared_ventral_image = SharedImage("halventralimage") + self.shared_x = SharedValue("x") + self.shared_y = SharedValue("y") + self.shared_z = SharedValue("z") + self.shared_takeoff_z = SharedValue("sharedtakeoffz") + self.shared_az = SharedValue("az") + self.shared_azt = SharedValue("azt") + self.shared_vx = SharedValue("vx") + self.shared_vy = SharedValue("vy") + self.shared_vz = SharedValue("vz") + self.shared_landed_state = SharedValue("landedstate") + self.shared_position = SharedValue("position") + self.shared_velocity = SharedValue("velocity") + self.shared_orientation = SharedValue("orientation") + self.shared_roll = SharedValue("roll") + self.shared_pitch = SharedValue("pitch") + self.shared_yaw = SharedValue("yaw") + self.shared_yaw_rate = SharedValue("yawrate") + self.image = None - self.drone = DroneWrapper(name="rqt") + self.drone = DroneWrapper(name="rqt",ns="/iris/") + + # Update thread + self.thread = ThreadHAL(self.update_hal) # Explicit initialization functions # Class method, so user can call it without instantiation - @classmethod - def initRobot(cls): - new_instance = cls() - return new_instance + + + # Function to start the update thread + def start_thread(self): + self.thread.start() # Get Image from ROS Driver Camera def get_frontal_image(self): image = self.drone.get_frontal_image() image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) - return image_rgb + self.shared_frontal_image.add(image_rgb) def get_ventral_image(self): image = self.drone.get_ventral_image() image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) - return image_rgb + self.shared_ventral_image.add(image_rgb) def get_position(self): pos = self.drone.get_position() - return pos + self.shared_position.add(pos,type_name="list") def get_velocity(self): vel = self.drone.get_velocity() - return vel + self.shared_velocity.add(vel ,type_name="list") def get_yaw_rate(self): yaw_rate = self.drone.get_yaw_rate() - return yaw_rate + self.shared_yaw_rate.add(yaw_rate) def get_orientation(self): orientation = self.drone.get_orientation() - return orientation + self.shared_orientation.add(orientation ,type_name="list") def get_roll(self): roll = self.drone.get_roll() - return roll + self.shared_roll.add(roll) def get_pitch(self): pitch = self.drone.get_pitch() - return pitch + self.shared_pitch.add(pitch) def get_yaw(self): yaw = self.drone.get_yaw() - return yaw + self.shared_yaw.add(yaw) def get_landed_state(self): state = self.drone.get_landed_state() - return state + self.shared_landed_state.add(state) + + def set_cmd_pos(self): + x = self.shared_x.get() + y = self.shared_y.get() + z = self.shared_z.get() + az = self.shared_az.get() - def set_cmd_pos(self, x, y, z, az): self.drone.set_cmd_pos(x, y, z, az) - def set_cmd_vel(self, vx, vy, vz, az): + def set_cmd_vel(self): + vx = self.shared_vx.get() + vy = self.shared_vy.get() + vz = self.shared_vz.get() + az = self.shared_azt.get() self.drone.set_cmd_vel(vx, vy, vz, az) - def set_cmd_mix(self, vx, vy, z, az): + def set_cmd_mix(self): + vx = self.shared_vx.get() + vy = self.shared_vy.get() + z = self.shared_z.get() + az = self.shared_azt.get() self.drone.set_cmd_mix(vx, vy, z, az) - def takeoff(self, h=3): + def takeoff(self): + h = self.shared_takeoff_z.get() self.drone.takeoff(h) def land(self): self.drone.land() + + def update_hal(self): + self.get_frontal_image() + self.get_ventral_image() + self.get_position() + self.get_velocity() + self.get_yaw_rate() + self.get_orientation() + self.get_pitch() + self.get_roll() + self.get_yaw() + self.get_landed_state() + self.set_cmd_pos() + self.set_cmd_vel() + self.set_cmd_mix() + + # Destructor function to close all fds + def __del__(self): + self.shared_frontal_image.close() + self.shared_ventral_image.close() + self.shared_x.close() + self.shared_y.close() + self.shared_z.close() + self.shared_takeoff_z.close() + self.shared_az.close() + self.shared_azt.close() + self.shared_vx.close() + self.shared_vy.close() + self.shared_vz.close() + self.shared_landed_state.close() + self.shared_position.close() + self.shared_velocity.close() + self.shared_orientation.close() + self.shared_roll.close() + self.shared_pitch.close() + self.shared_yaw.close() + self.shared_yaw_rate.close() + +class ThreadHAL(threading.Thread): + def __init__(self, update_function): + super(ThreadHAL, self).__init__() + self.time_cycle = 80 + self.update_function = update_function + + def run(self): + while(True): + start_time = datetime.now() + + self.update_function() + + finish_time = datetime.now() + + dt = finish_time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + + if(ms < self.time_cycle): + time.sleep((self.time_cycle - ms) / 1000.0) \ No newline at end of file diff --git a/exercises/static/exercises/follow_road/web-template/launch/follow_road.launch b/exercises/static/exercises/follow_road/web-template/launch/follow_road.launch new file mode 100755 index 000000000..f20676831 --- /dev/null +++ b/exercises/static/exercises/follow_road/web-template/launch/follow_road.launch @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/exercises/static/exercises/follow_road/web-template/launch/gazebo.launch b/exercises/static/exercises/follow_road/web-template/launch/gazebo.launch deleted file mode 100644 index 0f72c6a7f..000000000 --- a/exercises/static/exercises/follow_road/web-template/launch/gazebo.launch +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/exercises/static/exercises/follow_road/web-template/launch/launch.py b/exercises/static/exercises/follow_road/web-template/launch/launch.py deleted file mode 100644 index 796a5f751..000000000 --- a/exercises/static/exercises/follow_road/web-template/launch/launch.py +++ /dev/null @@ -1,131 +0,0 @@ -#!/usr/bin/env python3 - -import stat -import rospy -from os import lstat -from subprocess import Popen, PIPE - - -DRI_PATH = "/dev/dri/card0" -EXERCISE = "follow_road" -TIMEOUT = 30 -MAX_ATTEMPT = 2 - - -# Check if acceleration can be enabled -def check_device(device_path): - try: - return stat.S_ISCHR(lstat(device_path)[stat.ST_MODE]) - except: - return False - - -# Spawn new process -def spawn_process(args, insert_vglrun=False): - if insert_vglrun: - args.insert(0, "vglrun") - process = Popen(args, stdout=PIPE, bufsize=1, universal_newlines=True) - return process - - -class Test(): - def gazebo(self): - rospy.logwarn("[GAZEBO] Launching") - try: - rospy.wait_for_service("/gazebo/get_model_properties", TIMEOUT) - return True - except rospy.ROSException: - return False - - def px4(self): - rospy.logwarn("[PX4-SITL] Launching") - start_time = rospy.get_time() - args = ["./PX4-Autopilot/build/px4_sitl_default/bin/px4-commander","--instance", "0", "check"] - while rospy.get_time() - start_time < TIMEOUT: - process = spawn_process(args, insert_vglrun=False) - with process.stdout: - for line in iter(process.stdout.readline, ''): - if ("Prearm check: OK" in line): - return True - rospy.sleep(2) - return False - - def mavros(self, ns=""): - rospy.logwarn("[MAVROS] Launching") - try: - rospy.wait_for_service(ns + "/mavros/cmd/arming", TIMEOUT) - return True - except rospy.ROSException: - return False - - -class Launch(): - def __init__(self): - self.test = Test() - self.acceleration_enabled = check_device(DRI_PATH) - - # Start roscore - args = ["/opt/ros/noetic/bin/roscore"] - spawn_process(args, insert_vglrun=False) - - rospy.init_node("launch", anonymous=True) - - def start(self): - ######## LAUNCH GAZEBO ######## - args = ["/opt/ros/noetic/bin/roslaunch", - "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/gazebo.launch", - "--wait", - "--log" - ] - - attempt = 1 - while True: - spawn_process(args, insert_vglrun=self.acceleration_enabled) - if self.test.gazebo() == True: - break - if attempt == MAX_ATTEMPT: - rospy.logerr("[GAZEBO] Launch Failed") - return - attempt = attempt + 1 - - - ######## LAUNCH PX4 ######## - args = ["/opt/ros/noetic/bin/roslaunch", - "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/px4.launch", - "--log" - ] - - attempt = 1 - while True: - spawn_process(args, insert_vglrun=self.acceleration_enabled) - if self.test.px4() == True: - break - if attempt == MAX_ATTEMPT: - rospy.logerr("[PX4] Launch Failed") - return - attempt = attempt + 1 - - - ######## LAUNCH MAVROS ######## - args = ["/opt/ros/noetic/bin/roslaunch", - "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/mavros.launch", - "--log" - ] - - attempt = 1 - while True: - spawn_process(args, insert_vglrun=self.acceleration_enabled) - if self.test.mavros() == True: - break - if attempt == MAX_ATTEMPT: - rospy.logerr("[MAVROS] Launch Failed") - return - attempt = attempt + 1 - - -if __name__ == "__main__": - launch = Launch() - launch.start() - - with open("/drones_launch.log", "w") as f: - f.write("success") diff --git a/exercises/static/exercises/follow_road/web-template/launch/mavros.launch b/exercises/static/exercises/follow_road/web-template/launch/mavros.launch deleted file mode 100644 index b899c0ec1..000000000 --- a/exercises/static/exercises/follow_road/web-template/launch/mavros.launch +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/exercises/static/exercises/follow_road/web-template/launch/px4.launch b/exercises/static/exercises/follow_road/web-template/launch/px4.launch deleted file mode 100644 index 2fa0fedc9..000000000 --- a/exercises/static/exercises/follow_road/web-template/launch/px4.launch +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/exercises/static/exercises/follow_road/web-template/shared/__init__.py b/exercises/static/exercises/follow_road/web-template/shared/__init__.py new file mode 100755 index 000000000..e69de29bb diff --git a/exercises/static/exercises/follow_road/web-template/shared/image.py b/exercises/static/exercises/follow_road/web-template/shared/image.py new file mode 100755 index 000000000..de5c9f9d6 --- /dev/null +++ b/exercises/static/exercises/follow_road/web-template/shared/image.py @@ -0,0 +1,109 @@ +import numpy as np +import mmap +from posix_ipc import Semaphore, O_CREX, ExistentialError, O_CREAT, SharedMemory, unlink_shared_memory +from ctypes import sizeof, memmove, addressof, create_string_buffer +from shared.structure_img import MD + +# Probably, using self variables gives errors with memmove +# Therefore, a global variable for utility +md_buf = create_string_buffer(sizeof(MD)) + +class SharedImage: + def __init__(self, name): + # Initialize variables for memory regions and buffers and Semaphore + self.shm_buf = None; self.shm_region = None + self.md_buf = None; self.md_region = None + self.image_lock = None + + self.shm_name = name; self.md_name = name + "-meta" + self.image_lock_name = name + + # Initialize or retreive metadata memory region + try: + self.md_region = SharedMemory(self.md_name) + self.md_buf = mmap.mmap(self.md_region.fd, sizeof(MD)) + self.md_region.close_fd() + except ExistentialError: + self.md_region = SharedMemory(self.md_name, O_CREAT, size=sizeof(MD)) + self.md_buf = mmap.mmap(self.md_region.fd, self.md_region.size) + self.md_region.close_fd() + + # Initialize or retreive Semaphore + try: + self.image_lock = Semaphore(self.image_lock_name, O_CREX) + except ExistentialError: + image_lock = Semaphore(self.image_lock_name, O_CREAT) + image_lock.unlink() + self.image_lock = Semaphore(self.image_lock_name, O_CREX) + + self.image_lock.release() + + # Get the shared image + def get(self): + # Define metadata + metadata = MD() + + # Get metadata from the shared region + self.image_lock.acquire() + md_buf[:] = self.md_buf + memmove(addressof(metadata), md_buf, sizeof(metadata)) + self.image_lock.release() + + # Try to retreive the image from shm_buffer + # Otherwise return a zero image + try: + self.shm_region = SharedMemory(self.shm_name) + self.shm_buf = mmap.mmap(self.shm_region.fd, metadata.size) + self.shm_region.close_fd() + + self.image_lock.acquire() + image = np.ndarray(shape=(metadata.shape_0, metadata.shape_1, metadata.shape_2), + dtype='uint8', buffer=self.shm_buf) + self.image_lock.release() + + # Check for a None image + if(image.size == 0): + image = np.zeros((3, 3, 3), np.uint8) + + except ExistentialError: + image = np.zeros((3, 3, 3), np.uint8) + + return image + + # Add the shared image + def add(self, image): + try: + # Get byte size of the image + byte_size = image.nbytes + + # Get the shared memory buffer to read from + if not self.shm_region: + self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=byte_size) + self.shm_buf = mmap.mmap(self.shm_region.fd, byte_size) + self.shm_region.close_fd() + + # Generate meta data + metadata = MD(image.shape[0], image.shape[1], image.shape[2], byte_size) + + # Send the meta data and image to shared regions + self.image_lock.acquire() + memmove(md_buf, addressof(metadata), sizeof(metadata)) + self.md_buf[:] = md_buf[:] + self.shm_buf[:] = image.tobytes() + self.image_lock.release() + except: + pass + + # Destructor function to unlink and disconnect + def close(self): + self.image_lock.acquire() + self.md_buf.close() + + try: + unlink_shared_memory(self.md_name) + unlink_shared_memory(self.shm_name) + except ExistentialError: + pass + + self.image_lock.release() + self.image_lock.close() diff --git a/exercises/static/exercises/follow_road/web-template/shared/structure_img.py b/exercises/static/exercises/follow_road/web-template/shared/structure_img.py new file mode 100755 index 000000000..ae40b3707 --- /dev/null +++ b/exercises/static/exercises/follow_road/web-template/shared/structure_img.py @@ -0,0 +1,9 @@ +from ctypes import Structure, c_int32, c_int64 + +class MD(Structure): + _fields_ = [ + ('shape_0', c_int32), + ('shape_1', c_int32), + ('shape_2', c_int32), + ('size', c_int64) + ] \ No newline at end of file diff --git a/exercises/static/exercises/follow_road/web-template/shared/value.py b/exercises/static/exercises/follow_road/web-template/shared/value.py new file mode 100755 index 000000000..e7bfad8a2 --- /dev/null +++ b/exercises/static/exercises/follow_road/web-template/shared/value.py @@ -0,0 +1,93 @@ +import numpy as np +import mmap +from posix_ipc import Semaphore, O_CREX, ExistentialError, O_CREAT, SharedMemory, unlink_shared_memory +from ctypes import sizeof, memmove, addressof, create_string_buffer, c_float +import struct + +class SharedValue: + def __init__(self, name): + # Initialize varaibles for memory regions and buffers and Semaphore + self.shm_buf = None; self.shm_region = None + self.value_lock = None + + self.shm_name = name; self.value_lock_name = name + + # Initialize or retreive Semaphore + try: + self.value_lock = Semaphore(self.value_lock_name, O_CREX) + except ExistentialError: + value_lock = Semaphore(self.value_lock_name, O_CREAT) + value_lock.unlink() + self.value_lock = Semaphore(self.value_lock_name, O_CREX) + + self.value_lock.release() + + # Get the shared value + def get(self, type_name= "value"): + # Retreive the data from buffer + if type_name=="value": + try: + self.shm_region = SharedMemory(self.shm_name) + self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) + self.shm_region.close_fd() + except ExistentialError: + self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=sizeof(c_float)) + self.shm_buf = mmap.mmap(self.shm_region.fd, self.shm_region.size) + self.shm_region.close_fd() + self.value_lock.acquire() + value = struct.unpack('f', self.shm_buf)[0] + self.value_lock.release() + + return value + elif type_name=="list": + self.shm_region = SharedMemory(self.shm_name) + self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) + self.shm_region.close_fd() + self.value_lock.acquire() + array_val = np.ndarray(shape=(3,), + dtype='float32', buffer=self.shm_buf) + self.value_lock.release() + + return array_val + + else: + print("missing argument for return type") + + + # Add the shared value + def add(self, value, type_name= "value"): + # Send the data to shared regions + if type_name=="value": + try: + self.shm_region = SharedMemory(self.shm_name) + self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) + self.shm_region.close_fd() + except ExistentialError: + self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=sizeof(c_float)) + self.shm_buf = mmap.mmap(self.shm_region.fd, self.shm_region.size) + self.shm_region.close_fd() + + self.value_lock.acquire() + self.shm_buf[:] = struct.pack('f', value) + self.value_lock.release() + elif type_name=="list": + byte_size = value.nbytes + self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=byte_size) + self.shm_buf = mmap.mmap(self.shm_region.fd, byte_size) + self.shm_region.close_fd() + self.value_lock.acquire() + self.shm_buf[:] = value.tobytes() + self.value_lock.release() + + # Destructor function to unlink and disconnect + def close(self): + self.value_lock.acquire() + self.shm_buf.close() + + try: + unlink_shared_memory(self.shm_name) + except ExistentialError: + pass + + self.value_lock.release() + self.value_lock.close() diff --git a/exercises/static/exercises/follow_road/web-template/user_functions.py b/exercises/static/exercises/follow_road/web-template/user_functions.py new file mode 100755 index 000000000..d741601fc --- /dev/null +++ b/exercises/static/exercises/follow_road/web-template/user_functions.py @@ -0,0 +1,117 @@ +from shared.image import SharedImage +from shared.value import SharedValue +import numpy as np +import cv2 + +# Define HAL functions +class HALFunctions: + def __init__(self): + # Initialize image variable + self.shared_frontal_image = SharedImage("halfrontalimage") + self.shared_ventral_image = SharedImage("halventralimage") + self.shared_x = SharedValue("x") + self.shared_y = SharedValue("y") + self.shared_z = SharedValue("z") + self.shared_takeoff_z = SharedValue("sharedtakeoffz") + self.shared_az = SharedValue("az") + self.shared_azt = SharedValue("azt") + self.shared_vx = SharedValue("vx") + self.shared_vy = SharedValue("vy") + self.shared_vz = SharedValue("vz") + self.shared_landed_state = SharedValue("landedstate") + self.shared_position = SharedValue("position") + self.shared_velocity = SharedValue("velocity") + self.shared_orientation = SharedValue("orientation") + self.shared_roll = SharedValue("roll") + self.shared_pitch = SharedValue("pitch") + self.shared_yaw = SharedValue("yaw") + self.shared_yaw_rate = SharedValue("yawrate") + + + # Get image function + def get_frontal_image(self): + image = self.shared_frontal_image.get() + return image + + # Get left image function + def get_ventral_image(self): + image = self.shared_ventral_image.get() + return image + + def takeoff(self, height): + self.shared_takeoff_z.add(height) + + def land(self): + pass + + def set_cmd_pos(self, x, y , z, az): + self.shared_x.add(x) + self.shared_y.add(y) + self.shared_z.add(z) + self.shared_az.add(az) + + def set_cmd_vel(self, vx, vy, vz, az): + self.shared_vx.add(vx) + self.shared_vy.add(vy) + self.shared_vz.add(vz) + self.shared_azt.add(az) + + def set_cmd_mix(self, vx, vy, z, az): + self.shared_vx.add(vx) + self.shared_vy.add(vy) + self.shared_vz.add(z) + self.shared_azt.add(az) + + + def get_position(self): + position = self.shared_position.get(type_name = "list") + return position + + def get_velocity(self): + velocity = self.shared_velocity.get(type_name = "list") + return velocity + + def get_yaw_rate(self): + yaw_rate = self.shared_yaw_rate.get(type_name = "value") + return yaw_rate + + def get_orientation(self): + orientation = self.shared_orientation.get(type_name = "list") + return orientation + + def get_roll(self): + roll = self.shared_roll.get(type_name = "value") + return roll + + def get_pitch(self): + pitch = self.shared_pitch.get(type_name = "value") + return pitch + + def get_yaw(self): + yaw = self.shared_yaw.get(type_name = "value") + return yaw + + def get_landed_state(self): + landed_state = self.shared_landed_state.get(type_name = "value") + return landed_state + +# Define GUI functions +class GUIFunctions: + def __init__(self): + # Initialize image variable + self.shared_image = SharedImage("guifrontalimage") + self.shared_left_image = SharedImage("guiventralimage") + + # Show image function + def showImage(self, image): + # Reshape to 3 channel if it has only 1 in order to display it + if (len(image.shape) < 3): + image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB) + self.shared_image.add(image) + + # Show left image function + def showLeftImage(self, image): + # Reshape to 3 channel if it has only 1 in order to display it + if (len(image.shape) < 3): + image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB) + self.shared_left_image.add(image) \ No newline at end of file diff --git a/exercises/static/exercises/labyrinth_escape/web-template/launch/labyrinth_escape.launch b/exercises/static/exercises/labyrinth_escape/web-template/launch/labyrinth_escape.launch index 810617533..576047298 100755 --- a/exercises/static/exercises/labyrinth_escape/web-template/launch/labyrinth_escape.launch +++ b/exercises/static/exercises/labyrinth_escape/web-template/launch/labyrinth_escape.launch @@ -31,7 +31,7 @@ - + diff --git a/scripts/instructions.json b/scripts/instructions.json index 70f5fae76..a20914e9e 100644 --- a/scripts/instructions.json +++ b/scripts/instructions.json @@ -51,8 +51,9 @@ }, "follow_road": { "gazebo_path": "/RoboticsAcademy/exercises/follow_road/web-template/launch", - "instructions_ros": ["python3 ./RoboticsAcademy/exercises/follow_road/web-template/launch/launch.py"], - "instructions_host": "python3 /RoboticsAcademy/exercises/follow_road/web-template/exercise.py 0.0.0.0" + "instructions_ros": ["/opt/ros/noetic/bin/roslaunch ./RoboticsAcademy/exercises/follow_road/web-template/launch/follow_road.launch"], + "instructions_host": "python3 /RoboticsAcademy/exercises/follow_road/web-template/exercise.py 0.0.0.0", + "instructions_gui": "python3 /RoboticsAcademy/exercises/follow_road/web-template/gui.py 0.0.0.0 {}" }, "dl_digit_classifier": { "instructions_host": "python3 /RoboticsAcademy/exercises/dl_digit_classifier/web-template/exercise.py 0.0.0.0" From 0b24a8a18774e23a8680880f0c07596864f9d2e1 Mon Sep 17 00:00:00 2001 From: RUFFY-369 Date: Mon, 12 Sep 2022 19:40:49 +0530 Subject: [PATCH 25/42] multiprocessing final for drone gymkhana --- .../drone_gymkhana/web-template/brain.py | 166 +++++++++++ .../drone_gymkhana/web-template/exercise.py | 265 ++++++------------ .../drone_gymkhana/web-template/gui.py | 182 ++++++------ .../drone_gymkhana/web-template/hal.py | 142 ++++++++-- .../web-template/launch/drone_gymkhana.launch | 59 ++++ .../web-template/launch/gazebo.launch | 24 -- .../web-template/launch/launch.py | 131 --------- .../web-template/launch/mavros.launch | 13 - .../web-template/launch/px4.launch | 19 -- .../web-template/shared/__init__.py | 0 .../web-template/shared/image.py | 109 +++++++ .../web-template/shared/structure_img.py | 9 + .../web-template/shared/value.py | 93 ++++++ .../web-template/user_functions.py | 117 ++++++++ scripts/instructions.json | 5 +- 15 files changed, 852 insertions(+), 482 deletions(-) create mode 100755 exercises/static/exercises/drone_gymkhana/web-template/brain.py mode change 100644 => 100755 exercises/static/exercises/drone_gymkhana/web-template/exercise.py mode change 100644 => 100755 exercises/static/exercises/drone_gymkhana/web-template/gui.py mode change 100644 => 100755 exercises/static/exercises/drone_gymkhana/web-template/hal.py create mode 100755 exercises/static/exercises/drone_gymkhana/web-template/launch/drone_gymkhana.launch delete mode 100644 exercises/static/exercises/drone_gymkhana/web-template/launch/gazebo.launch delete mode 100644 exercises/static/exercises/drone_gymkhana/web-template/launch/launch.py delete mode 100644 exercises/static/exercises/drone_gymkhana/web-template/launch/mavros.launch delete mode 100644 exercises/static/exercises/drone_gymkhana/web-template/launch/px4.launch create mode 100755 exercises/static/exercises/drone_gymkhana/web-template/shared/__init__.py create mode 100755 exercises/static/exercises/drone_gymkhana/web-template/shared/image.py create mode 100755 exercises/static/exercises/drone_gymkhana/web-template/shared/structure_img.py create mode 100755 exercises/static/exercises/drone_gymkhana/web-template/shared/value.py create mode 100755 exercises/static/exercises/drone_gymkhana/web-template/user_functions.py diff --git a/exercises/static/exercises/drone_gymkhana/web-template/brain.py b/exercises/static/exercises/drone_gymkhana/web-template/brain.py new file mode 100755 index 000000000..2330c04ba --- /dev/null +++ b/exercises/static/exercises/drone_gymkhana/web-template/brain.py @@ -0,0 +1,166 @@ +import time +import threading +import multiprocessing +import sys +from datetime import datetime +import re +import json +import importlib + +import rospy +from std_srvs.srv import Empty +import cv2 + +from user_functions import GUIFunctions, HALFunctions +from console import start_console, close_console + +from shared.value import SharedValue + +# The brain process class +class BrainProcess(multiprocessing.Process): + def __init__(self, code, exit_signal): + super(BrainProcess, self).__init__() + + # Initialize exit signal + self.exit_signal = exit_signal + + # Function definitions for users to use + self.hal = HALFunctions() + self.gui = GUIFunctions() + + # Time variables + self.time_cycle = SharedValue('brain_time_cycle') + self.ideal_cycle = SharedValue('brain_ideal_cycle') + self.iteration_counter = 0 + + # Get the sequential and iterative code + # Something wrong over here! The code is reversing + # Found a solution but could not find the reason for this (parse_code function's return line of exercise.py is the reason) + self.sequential_code = code[1] + self.iterative_code = code[0] + + # Function to run to start the process + def run(self): + # Two threads for running and measuring + self.measure_thread = threading.Thread(target=self.measure_frequency) + self.thread = threading.Thread(target=self.process_code) + + self.measure_thread.start() + self.thread.start() + + print("Brain Process Started!") + + self.exit_signal.wait() + + # The process function + def process_code(self): + # Redirect information to console + start_console() + + # Reference Environment for the exec() function + iterative_code, sequential_code = self.iterative_code, self.sequential_code + + # The Python exec function + # Run the sequential part + gui_module, hal_module = self.generate_modules() + if sequential_code != "": + reference_environment = {"GUI": gui_module, "HAL": hal_module} + exec(sequential_code, reference_environment) + + # Run the iterative part inside template + # and keep the check for flag + while not self.exit_signal.is_set(): + start_time = datetime.now() + + # Execute the iterative portion + if iterative_code != "": + exec(iterative_code, reference_environment) + + # Template specifics to run! + finish_time = datetime.now() + dt = finish_time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + + # Keep updating the iteration counter + if(iterative_code == ""): + self.iteration_counter = 0 + else: + self.iteration_counter = self.iteration_counter + 1 + + # The code should be run for atleast the target time step + # If it's less put to sleep + # If it's more no problem as such, but we can change it! + time_cycle = self.time_cycle.get() + + if(ms < time_cycle): + time.sleep((time_cycle - ms) / 1000.0) + + close_console() + print("Current Thread Joined!") + + + # Function to generate the modules for use in ACE Editor + def generate_modules(self): + # Define HAL module + hal_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) + hal_module.HAL = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) + hal_module.HAL.motors = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("motors", None)) + + # Add HAL functions + hal_module.HAL.get_frontal_image = self.hal.get_frontal_image + hal_module.HAL.get_ventral_image = self.hal.get_ventral_image + hal_module.HAL.takeoff = self.hal.takeoff + hal_module.HAL.land = self.hal.land + hal_module.HAL.set_cmd_pos = self.hal.set_cmd_pos + hal_module.HAL.set_cmd_vel = self.hal.set_cmd_vel + hal_module.HAL.set_cmd_mix = self.hal.set_cmd_mix + hal_module.HAL.get_position = self.hal.get_position + hal_module.HAL.get_velocity = self.hal.get_velocity + hal_module.HAL.get_orientation = self.hal.get_orientation + hal_module.HAL.get_roll = self.hal.get_roll + hal_module.HAL.get_pitch = self.hal.get_pitch + hal_module.HAL.get_yaw = self.hal.get_yaw + hal_module.HAL.get_landed_state = self.hal.get_landed_state + hal_module.HAL.get_yaw_rate = self.hal.get_yaw_rate + + + + # Define GUI module + gui_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) + gui_module.GUI = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) + + # Add GUI functions + gui_module.GUI.showImage = self.gui.showImage + gui_module.GUI.showLeftImage = self.gui.showLeftImage + + # Adding modules to system + # Protip: The names should be different from + # other modules, otherwise some errors + sys.modules["HAL"] = hal_module + sys.modules["GUI"] = gui_module + + return gui_module, hal_module + + # Function to measure the frequency of iterations + def measure_frequency(self): + previous_time = datetime.now() + # An infinite loop + while not self.exit_signal.is_set(): + # Sleep for 2 seconds + time.sleep(2) + + # Measure the current time and subtract from the previous time to get real time interval + current_time = datetime.now() + dt = current_time - previous_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + previous_time = current_time + + # Get the time period + try: + # Division by zero + self.ideal_cycle.add(ms / self.iteration_counter) + except: + self.ideal_cycle.add(0) + + # Reset the counter + self.iteration_counter = 0 \ No newline at end of file diff --git a/exercises/static/exercises/drone_gymkhana/web-template/exercise.py b/exercises/static/exercises/drone_gymkhana/web-template/exercise.py old mode 100644 new mode 100755 index 8f68f43ed..82bf0c72a --- a/exercises/static/exercises/drone_gymkhana/web-template/exercise.py +++ b/exercises/static/exercises/drone_gymkhana/web-template/exercise.py @@ -1,3 +1,5 @@ + + #!/usr/bin/env python from __future__ import print_function @@ -5,6 +7,7 @@ from websocket_server import WebsocketServer import time import threading +import multiprocessing import subprocess import sys from datetime import datetime @@ -14,8 +17,13 @@ import rospy from std_srvs.srv import Empty +import cv2 + +from shared.value import SharedValue +from brain import BrainProcess +import queue + -from gui import GUI, ThreadGUI from hal import HAL from console import start_console, close_console @@ -25,36 +33,65 @@ class Template: # self.ideal_cycle to run an execution for at least 1 second # self.process for the current running process def __init__(self): - self.measure_thread = None - self.thread = None - self.reload = False - self.stop_brain = True - self.user_code = "" + + self.brain_process = None + self.reload = multiprocessing.Event() # Time variables - self.ideal_cycle = 80 - self.measured_cycle = 80 - self.iteration_counter = 0 + self.brain_time_cycle = SharedValue('brain_time_cycle') + self.brain_ideal_cycle = SharedValue('brain_ideal_cycle') self.real_time_factor = 0 + self.frequency_message = {'brain': '', 'gui': '', 'rtf': ''} + # GUI variables + self.gui_time_cycle = SharedValue('gui_time_cycle') + self.gui_ideal_cycle = SharedValue('gui_ideal_cycle') + self.server = None self.client = None self.host = sys.argv[1] - # Initialize the GUI, HAL and Console behind the scenes self.hal = HAL() - self.gui = GUI(self.host) + self.paused = False + # Function to parse the code # A few assumptions: # 1. The user always passes sequential and iterative codes # 2. Only a single infinite loop def parse_code(self, source_code): - sequential_code, iterative_code = self.seperate_seq_iter(source_code) - return iterative_code, sequential_code + # Check for save/load + if(source_code[:5] == "#save"): + source_code = source_code[5:] + self.save_code(source_code) + + return "", "" + + elif(source_code[:5] == "#load"): + source_code = source_code + self.load_code() + self.server.send_message(self.client, source_code) + + return "", "" + + else: + sequential_code, iterative_code = self.seperate_seq_iter(source_code[6:]) + return iterative_code, sequential_code + + + # Function for saving + def save_code(self, source_code): + with open('code/academy.py', 'w') as code_file: + code_file.write(source_code) + + # Function for loading + def load_code(self): + with open('code/academy.py', 'r') as code_file: + source_code = code_file.read() + + return source_code - # Function to separate the iterative and sequential code + # Function to seperate the iterative and sequential code def seperate_seq_iter(self, source_code): if source_code == "": return "", "" @@ -62,8 +99,8 @@ def seperate_seq_iter(self, source_code): # Search for an instance of while True infinite_loop = re.search(r'[^ ]while\s*\(\s*True\s*\)\s*:|[^ ]while\s*True\s*:|[^ ]while\s*1\s*:|[^ ]while\s*\(\s*1\s*\)\s*:', source_code) - # Separate the content inside while True and the other - # (Separating the sequential and iterative part!) + # Seperate the content inside while True and the other + # (Seperating the sequential and iterative part!) try: start_index = infinite_loop.start() iterative_code = source_code[start_index:] @@ -85,137 +122,16 @@ def seperate_seq_iter(self, source_code): return sequential_code, iterative_code - # The process function - def process_code(self, source_code): - # Redirect the information to console - start_console() - - iterative_code, sequential_code = self.parse_code(source_code) - - # print(sequential_code) - # print(iterative_code) - - # The Python exec function - # Run the sequential part - gui_module, hal_module = self.generate_modules() - reference_environment = {"GUI": gui_module, "HAL": hal_module} - while (self.stop_brain == True): - if (self.reload == True): - return - time.sleep(0.1) - exec(sequential_code, reference_environment) - - # Run the iterative part inside template - # and keep the check for flag - while self.reload == False: - while (self.stop_brain == True): - if (self.reload == True): - return - time.sleep(0.1) - - start_time = datetime.now() - - # Execute the iterative portion - exec(iterative_code, reference_environment) - - # Template specifics to run! - finish_time = datetime.now() - dt = finish_time - start_time - ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 - - # Keep updating the iteration counter - if (iterative_code == ""): - self.iteration_counter = 0 - else: - self.iteration_counter = self.iteration_counter + 1 - - # The code should be run for atleast the target time step - # If it's less put to sleep - if (ms < self.ideal_cycle): - time.sleep((self.ideal_cycle - ms) / 1000.0) - - close_console() - print("Current Thread Joined!") - - # Function to generate the modules for use in ACE Editor - def generate_modules(self): - # Define HAL module - hal_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) - hal_module.HAL = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) - # hal_module.drone = imp.new_module("drone") - # motors# hal_module.HAL.motors = imp.new_module("motors") - - # Add HAL functions - hal_module.HAL.get_frontal_image = self.hal.get_frontal_image - hal_module.HAL.get_ventral_image = self.hal.get_ventral_image - hal_module.HAL.get_position = self.hal.get_position - hal_module.HAL.get_velocity = self.hal.get_velocity - hal_module.HAL.get_yaw_rate = self.hal.get_yaw_rate - hal_module.HAL.get_orientation = self.hal.get_orientation - hal_module.HAL.get_roll = self.hal.get_roll - hal_module.HAL.get_pitch = self.hal.get_pitch - hal_module.HAL.get_yaw = self.hal.get_yaw - hal_module.HAL.get_landed_state = self.hal.get_landed_state - hal_module.HAL.set_cmd_pos = self.hal.set_cmd_pos - hal_module.HAL.set_cmd_vel = self.hal.set_cmd_vel - hal_module.HAL.set_cmd_mix = self.hal.set_cmd_mix - hal_module.HAL.takeoff = self.hal.takeoff - hal_module.HAL.land = self.hal.land - - # Define GUI module - gui_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) - gui_module.GUI = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) - - # Add GUI functions - gui_module.GUI.showImage = self.gui.showImage - gui_module.GUI.showLeftImage = self.gui.showLeftImage - - # Adding modules to system - # Protip: The names should be different from - # other modules, otherwise some errors - sys.modules["HAL"] = hal_module - sys.modules["GUI"] = gui_module - - return gui_module, hal_module - - # Function to measure the frequency of iterations - def measure_frequency(self): - previous_time = datetime.now() - # An infinite loop - while True: - # Sleep for 2 seconds - time.sleep(2) - - # Measure the current time and subtract from the previous time to get real time interval - current_time = datetime.now() - dt = current_time - previous_time - ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 - previous_time = current_time - - # Get the time period - try: - # Division by zero - self.measured_cycle = ms / self.iteration_counter - except: - self.measured_cycle = 0 - - # Reset the counter - self.iteration_counter = 0 - - # Send to client - self.send_frequency_message() - - # Function to generate and send frequency messages def send_frequency_message(self): # This function generates and sends frequency measures of the brain and gui - brain_frequency = 0; gui_frequency = 0 + brain_frequency = 0;gui_frequency = 0 try: - brain_frequency = round(1000 / self.measured_cycle, 1) + brain_frequency = round(1000 / self.brain_ideal_cycle.get(), 1) except ZeroDivisionError: brain_frequency = 0 try: - gui_frequency = round(1000 / self.thread_gui.measured_cycle, 1) + gui_frequency = round(1000 / self.gui_ideal_cycle.get(), 1) except ZeroDivisionError: gui_frequency = 0 @@ -240,29 +156,32 @@ def track_stats(self): args = ["gz", "stats", "-p"] # Prints gz statistics. "-p": Output comma-separated values containing- # real-time factor (percent), simtime (sec), realtime (sec), paused (T or F) - stats_process = subprocess.Popen(args, stdout=subprocess.PIPE, bufsize=1, universal_newlines=True) + stats_process = subprocess.Popen(args, stdout=subprocess.PIPE, bufsize=0) # bufsize=1 enables line-bufferred mode (the input buffer is flushed # automatically on newlines if you would write to process.stdin ) with stats_process.stdout: - for line in iter(stats_process.stdout.readline, ''): - stats_list = [x.strip() for x in line.split(',')] - self.real_time_factor = stats_list[0] + for line in iter(stats_process.stdout.readline, b''): + stats_list = [x.strip() for x in line.split(b',')] + self.real_time_factor = stats_list[0].decode("utf-8") # Function to maintain thread execution def execute_thread(self, source_code): # Keep checking until the thread is alive # The thread will die when the coming iteration reads the flag - if self.thread is not None: - while self.thread.is_alive(): - time.sleep(0.2) + if(self.brain_process != None): + while self.brain_process.is_alive(): + pass # Turn the flag down, the iteration has successfully stopped! - self.reload = False + self.reload.clear() # New thread execution - self.thread = threading.Thread(target=self.process_code, args=[source_code]) - self.thread.start() + code = self.parse_code(source_code) + if code[0] == "" and code[1] == "": + return + + self.brain_process = BrainProcess(code, self.reload) + self.brain_process.start() self.send_code_message() - print("New Thread Started!") # Function to read and set frequency from incoming message def read_frequency_message(self, message): @@ -270,66 +189,58 @@ def read_frequency_message(self, message): # Set brain frequency frequency = float(frequency_message["brain"]) - self.ideal_cycle = 1000.0 / frequency + self.brain_time_cycle.add(1000.0 / frequency) # Set gui frequency frequency = float(frequency_message["gui"]) - self.thread_gui.ideal_cycle = 1000.0 / frequency + self.gui_time_cycle.add(1000.0 / frequency) return # The websocket function # Gets called when there is an incoming message from the client def handle(self, client, server, message): - if message[:5] == "#freq": + if(message[:5] == "#freq"): frequency_message = message[5:] self.read_frequency_message(frequency_message) time.sleep(1) + self.send_frequency_message() return - elif(message[:5] == "#ping"): time.sleep(1) self.send_ping_message() return - - elif (message[:5] == "#code"): + elif (message[:5] == "#code"): try: # Once received turn the reload flag up and send it to execute_thread function - self.user_code = message[6:] + code = message # print(repr(code)) - self.reload = True - self.execute_thread(self.user_code) + self.reload.set() + self.execute_thread(code) except: pass - elif (message[:5] == "#rest"): + elif (message[:5] == "#stop"): try: - self.reload = True - self.stop_brain = True - self.execute_thread(self.user_code) + self.reload.set() + self.execute_thread(code) except: pass - - elif (message[:5] == "#stop"): - self.stop_brain = True - - elif (message[:5] == "#play"): - self.stop_brain = False + self.server.send_message(self.client, "#stpd") # Function that gets called when the server is connected def connected(self, client, server): self.client = client - # Start the GUI update thread - self.thread_gui = ThreadGUI(self.gui) - self.thread_gui.start() + # Start the HAL update thread + self.hal.start_thread() - # Start the real time factor tracker thread + # Start real time factor tracker thread self.stats_thread = threading.Thread(target=self.track_stats) self.stats_thread.start() - # Start measure frequency - self.measure_thread = threading.Thread(target=self.measure_frequency) - self.measure_thread.start() + # Initialize the ping message + self.send_frequency_message() + print("After sneding freweq msg") print(client, 'connected') diff --git a/exercises/static/exercises/drone_gymkhana/web-template/gui.py b/exercises/static/exercises/drone_gymkhana/web-template/gui.py old mode 100644 new mode 100755 index 51d327f1a..2b2e10176 --- a/exercises/static/exercises/drone_gymkhana/web-template/gui.py +++ b/exercises/static/exercises/drone_gymkhana/web-template/gui.py @@ -5,15 +5,23 @@ import time from datetime import datetime from websocket_server import WebsocketServer +import logging +import rospy +import cv2 +import sys +import numpy as np +import multiprocessing +from shared.image import SharedImage +from shared.value import SharedValue # Graphical User Interface Class class GUI: # Initialization function # The actual initialization def __init__(self, host): - t = threading.Thread(target=self.run_server) + rospy.init_node("GUI") self.payload = {'image': ''} self.left_payload = {'image': ''} self.server = None @@ -21,81 +29,47 @@ def __init__(self, host): self.host = host - # Image variables - self.image_to_be_shown = None - self.image_to_be_shown_updated = False - self.image_show_lock = threading.Lock() - - self.left_image_to_be_shown = None - self.left_image_to_be_shown_updated = False - self.left_image_show_lock = threading.Lock() - - self.acknowledge = False - self.acknowledge_lock = threading.Lock() + # Image variable host + self.shared_image = SharedImage("guifrontalimage") + self.shared_left_image = SharedImage("guiventralimage") - # Take the console object to set the same websocket and client + # Event objects for multiprocessing + self.ack_event = multiprocessing.Event() + self.cli_event = multiprocessing.Event() + + # Start server thread + t = threading.Thread(target=self.run_server) t.start() - # Explicit initialization function - # Class method, so user can call it without instantiation - @classmethod - def initGUI(cls, host): - # self.payload = {'image': '', 'shape': []} - new_instance = cls(host) - return new_instance # Function to prepare image payload # Encodes the image as a JSON string and sends through the WS def payloadImage(self): - self.image_show_lock.acquire() - image_to_be_shown_updated = self.image_to_be_shown_updated - image_to_be_shown = self.image_to_be_shown - self.image_show_lock.release() - - image = image_to_be_shown + image = self.shared_image.get() payload = {'image': '', 'shape': ''} - - if not image_to_be_shown_updated: - return payload - + shape = image.shape frame = cv2.imencode('.JPEG', image)[1] encoded_image = base64.b64encode(frame) - + payload['image'] = encoded_image.decode('utf-8') payload['shape'] = shape - - self.image_show_lock.acquire() - self.image_to_be_shown_updated = False - self.image_show_lock.release() - + return payload - + # Function to prepare image payload # Encodes the image as a JSON string and sends through the WS def payloadLeftImage(self): - self.left_image_show_lock.acquire() - left_image_to_be_shown_updated = self.left_image_to_be_shown_updated - left_image_to_be_shown = self.left_image_to_be_shown - self.left_image_show_lock.release() - - image = left_image_to_be_shown + image = self.shared_left_image.get() payload = {'image': '', 'shape': ''} - - if not left_image_to_be_shown_updated: - return payload - + shape = image.shape frame = cv2.imencode('.JPEG', image)[1] encoded_image = base64.b64encode(frame) - + payload['image'] = encoded_image.decode('utf-8') payload['shape'] = shape - - self.left_image_show_lock.acquire() - self.left_image_to_be_shown_updated = False - self.left_image_show_lock.release() - + return payload # Function for student to call @@ -116,20 +90,10 @@ def showLeftImage(self, image): # Called when a new client is received def get_client(self, client, server): self.client = client + self.cli_event.set() + + print(client, 'connected') - # Function to get value of Acknowledge - def get_acknowledge(self): - self.acknowledge_lock.acquire() - acknowledge = self.acknowledge - self.acknowledge_lock.release() - - return acknowledge - - # Function to get value of Acknowledge - def set_acknowledge(self, value): - self.acknowledge_lock.acquire() - self.acknowledge = value - self.acknowledge_lock.release() # Update the gui def update_gui(self): @@ -151,14 +115,23 @@ def update_gui(self): # Gets called when there is an incoming message from the client def get_message(self, client, server, message): # Acknowledge Message for GUI Thread - if message[:4] == "#ack": - self.set_acknowledge(True) + + if(message[:4] == "#ack"): + # Set acknowledgement flag + self.ack_event.set() + + # Function that gets called when the connected closes + def handle_close(self, client, server): + print(client, 'closed') + + # Activate the server def run_server(self): self.server = WebsocketServer(port=2303, host=self.host) self.server.set_fn_new_client(self.get_client) self.server.set_fn_message_received(self.get_message) + self.server.set_fn_client_left(self.handle_close) logged = False while not logged: @@ -179,32 +152,45 @@ def reset_gui(self): # This class decouples the user thread # and the GUI update thread -class ThreadGUI: - def __init__(self, gui): - self.gui = gui +class ProcessGUI(multiprocessing.Process): + def __init__(self): + super(ProcessGUI, self).__init__() + self.host = sys.argv[1] # Time variables - self.ideal_cycle = 80 - self.measured_cycle = 80 + self.time_cycle = SharedValue("gui_time_cycle") + self.ideal_cycle = SharedValue("gui_ideal_cycle") self.iteration_counter = 0 + # Function to initialize events + def initialize_events(self): + # Events + self.ack_event = self.gui.ack_event + self.cli_event = self.gui.cli_event + self.exit_signal = multiprocessing.Event() + # Function to start the execution of threads - def start(self): + def run(self): + # Initialize GUI + self.gui = GUI(self.host) + self.initialize_events() + + # Wait for client before starting + self.cli_event.wait() self.measure_thread = threading.Thread(target=self.measure_thread) - self.thread = threading.Thread(target=self.run) + self.thread = threading.Thread(target=self.run_gui) self.measure_thread.start() self.thread.start() - print("GUI Thread Started!") + print("GUI Process Started!") + + self.exit_signal.wait() # The measuring thread to measure frequency def measure_thread(self): - while self.gui.client is None: - pass - previous_time = datetime.now() - while True: + while(True): # Sleep for 2 seconds time.sleep(2) @@ -217,32 +203,40 @@ def measure_thread(self): # Get the time period try: # Division by zero - self.measured_cycle = ms / self.iteration_counter + self.ideal_cycle.add(ms / self.iteration_counter) except: - self.measured_cycle = 0 + self.ideal_cycle.add(0) # Reset the counter self.iteration_counter = 0 # The main thread of execution - def run(self): - while self.gui.client is None: - pass - - while True: + def run_gui(self): + while(True): start_time = datetime.now() + # Send update signal self.gui.update_gui() - acknowledge_message = self.gui.get_acknowledge() - - while not acknowledge_message: - acknowledge_message = self.gui.get_acknowledge() - self.gui.set_acknowledge(False) + # Wait for acknowldege signal + self.ack_event.wait() + self.ack_event.clear() finish_time = datetime.now() self.iteration_counter = self.iteration_counter + 1 dt = finish_time - start_time ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 - if ms < self.ideal_cycle: - time.sleep((self.ideal_cycle-ms) / 1000.0) + time_cycle = self.time_cycle.get() + + if(ms < time_cycle): + time.sleep((time_cycle-ms) / 1000.0) + + self.exit_signal.set() + + # Functions to handle auxillary GUI functions + def reset_gui(self): + self.gui.reset_gui() + +if __name__ == "__main__": + gui = ProcessGUI() + gui.start() \ No newline at end of file diff --git a/exercises/static/exercises/drone_gymkhana/web-template/hal.py b/exercises/static/exercises/drone_gymkhana/web-template/hal.py old mode 100644 new mode 100755 index 25635a119..17c39cd6b --- a/exercises/static/exercises/drone_gymkhana/web-template/hal.py +++ b/exercises/static/exercises/drone_gymkhana/web-template/hal.py @@ -1,9 +1,12 @@ -import numpy as np import rospy import cv2 +import threading +import time +from datetime import datetime from drone_wrapper import DroneWrapper - +from shared.image import SharedImage +from shared.value import SharedValue # Hardware Abstraction Layer class HAL: @@ -12,71 +15,166 @@ class HAL: def __init__(self): rospy.init_node("HAL") - + + self.shared_frontal_image = SharedImage("halfrontalimage") + self.shared_ventral_image = SharedImage("halventralimage") + self.shared_x = SharedValue("x") + self.shared_y = SharedValue("y") + self.shared_z = SharedValue("z") + self.shared_takeoff_z = SharedValue("sharedtakeoffz") + self.shared_az = SharedValue("az") + self.shared_azt = SharedValue("azt") + self.shared_vx = SharedValue("vx") + self.shared_vy = SharedValue("vy") + self.shared_vz = SharedValue("vz") + self.shared_landed_state = SharedValue("landedstate") + self.shared_position = SharedValue("position") + self.shared_velocity = SharedValue("velocity") + self.shared_orientation = SharedValue("orientation") + self.shared_roll = SharedValue("roll") + self.shared_pitch = SharedValue("pitch") + self.shared_yaw = SharedValue("yaw") + self.shared_yaw_rate = SharedValue("yawrate") + self.image = None - self.drone = DroneWrapper(name="rqt") + self.drone = DroneWrapper(name="rqt",ns="/iris/") + + # Update thread + self.thread = ThreadHAL(self.update_hal) # Explicit initialization functions # Class method, so user can call it without instantiation - @classmethod - def initRobot(cls): - new_instance = cls() - return new_instance + + + # Function to start the update thread + def start_thread(self): + self.thread.start() # Get Image from ROS Driver Camera def get_frontal_image(self): image = self.drone.get_frontal_image() image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) - return image_rgb + self.shared_frontal_image.add(image_rgb) def get_ventral_image(self): image = self.drone.get_ventral_image() image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) - return image_rgb + self.shared_ventral_image.add(image_rgb) def get_position(self): pos = self.drone.get_position() - return pos + self.shared_position.add(pos,type_name="list") def get_velocity(self): vel = self.drone.get_velocity() - return vel + self.shared_velocity.add(vel ,type_name="list") def get_yaw_rate(self): yaw_rate = self.drone.get_yaw_rate() - return yaw_rate + self.shared_yaw_rate.add(yaw_rate) def get_orientation(self): orientation = self.drone.get_orientation() - return orientation + self.shared_orientation.add(orientation ,type_name="list") def get_roll(self): roll = self.drone.get_roll() - return roll + self.shared_roll.add(roll) def get_pitch(self): pitch = self.drone.get_pitch() - return pitch + self.shared_pitch.add(pitch) def get_yaw(self): yaw = self.drone.get_yaw() - return yaw + self.shared_yaw.add(yaw) def get_landed_state(self): state = self.drone.get_landed_state() - return state + self.shared_landed_state.add(state) + + def set_cmd_pos(self): + x = self.shared_x.get() + y = self.shared_y.get() + z = self.shared_z.get() + az = self.shared_az.get() - def set_cmd_pos(self, x, y, z, az): self.drone.set_cmd_pos(x, y, z, az) - def set_cmd_vel(self, vx, vy, vz, az): + def set_cmd_vel(self): + vx = self.shared_vx.get() + vy = self.shared_vy.get() + vz = self.shared_vz.get() + az = self.shared_azt.get() self.drone.set_cmd_vel(vx, vy, vz, az) - def set_cmd_mix(self, vx, vy, z, az): + def set_cmd_mix(self): + vx = self.shared_vx.get() + vy = self.shared_vy.get() + z = self.shared_z.get() + az = self.shared_azt.get() self.drone.set_cmd_mix(vx, vy, z, az) - def takeoff(self, h=3): + def takeoff(self): + h = self.shared_takeoff_z.get() self.drone.takeoff(h) def land(self): self.drone.land() + + def update_hal(self): + self.get_frontal_image() + self.get_ventral_image() + self.get_position() + self.get_velocity() + self.get_yaw_rate() + self.get_orientation() + self.get_pitch() + self.get_roll() + self.get_yaw() + self.get_landed_state() + self.set_cmd_pos() + self.set_cmd_vel() + self.set_cmd_mix() + + # Destructor function to close all fds + def __del__(self): + self.shared_frontal_image.close() + self.shared_ventral_image.close() + self.shared_x.close() + self.shared_y.close() + self.shared_z.close() + self.shared_takeoff_z.close() + self.shared_az.close() + self.shared_azt.close() + self.shared_vx.close() + self.shared_vy.close() + self.shared_vz.close() + self.shared_landed_state.close() + self.shared_position.close() + self.shared_velocity.close() + self.shared_orientation.close() + self.shared_roll.close() + self.shared_pitch.close() + self.shared_yaw.close() + self.shared_yaw_rate.close() + +class ThreadHAL(threading.Thread): + def __init__(self, update_function): + super(ThreadHAL, self).__init__() + self.time_cycle = 80 + self.update_function = update_function + + def run(self): + while(True): + start_time = datetime.now() + + self.update_function() + + finish_time = datetime.now() + + dt = finish_time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + + if(ms < self.time_cycle): + time.sleep((self.time_cycle - ms) / 1000.0) \ No newline at end of file diff --git a/exercises/static/exercises/drone_gymkhana/web-template/launch/drone_gymkhana.launch b/exercises/static/exercises/drone_gymkhana/web-template/launch/drone_gymkhana.launch new file mode 100755 index 000000000..1d9a98599 --- /dev/null +++ b/exercises/static/exercises/drone_gymkhana/web-template/launch/drone_gymkhana.launch @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/exercises/static/exercises/drone_gymkhana/web-template/launch/gazebo.launch b/exercises/static/exercises/drone_gymkhana/web-template/launch/gazebo.launch deleted file mode 100644 index ff2ad5960..000000000 --- a/exercises/static/exercises/drone_gymkhana/web-template/launch/gazebo.launch +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/exercises/static/exercises/drone_gymkhana/web-template/launch/launch.py b/exercises/static/exercises/drone_gymkhana/web-template/launch/launch.py deleted file mode 100644 index 49f2f0e6b..000000000 --- a/exercises/static/exercises/drone_gymkhana/web-template/launch/launch.py +++ /dev/null @@ -1,131 +0,0 @@ -#!/usr/bin/env python3 - -import stat -import rospy -from os import lstat -from subprocess import Popen, PIPE - - -DRI_PATH = "/dev/dri/card0" -EXERCISE = "drone_gymkhana" -TIMEOUT = 30 -MAX_ATTEMPT = 2 - - -# Check if acceleration can be enabled -def check_device(device_path): - try: - return stat.S_ISCHR(lstat(device_path)[stat.ST_MODE]) - except: - return False - - -# Spawn new process -def spawn_process(args, insert_vglrun=False): - if insert_vglrun: - args.insert(0, "vglrun") - process = Popen(args, stdout=PIPE, bufsize=1, universal_newlines=True) - return process - - -class Test(): - def gazebo(self): - rospy.logwarn("[GAZEBO] Launching") - try: - rospy.wait_for_service("/gazebo/get_model_properties", TIMEOUT) - return True - except rospy.ROSException: - return False - - def px4(self): - rospy.logwarn("[PX4-SITL] Launching") - start_time = rospy.get_time() - args = ["./PX4-Autopilot/build/px4_sitl_default/bin/px4-commander","--instance", "0", "check"] - while rospy.get_time() - start_time < TIMEOUT: - process = spawn_process(args, insert_vglrun=False) - with process.stdout: - for line in iter(process.stdout.readline, ''): - if ("Prearm check: OK" in line): - return True - rospy.sleep(2) - return False - - def mavros(self, ns=""): - rospy.logwarn("[MAVROS] Launching") - try: - rospy.wait_for_service(ns + "/mavros/cmd/arming", TIMEOUT) - return True - except rospy.ROSException: - return False - - -class Launch(): - def __init__(self): - self.test = Test() - self.acceleration_enabled = check_device(DRI_PATH) - - # Start roscore - args = ["/opt/ros/noetic/bin/roscore"] - spawn_process(args, insert_vglrun=False) - - rospy.init_node("launch", anonymous=True) - - def start(self): - ######## LAUNCH GAZEBO ######## - args = ["/opt/ros/noetic/bin/roslaunch", - "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/gazebo.launch", - "--wait", - "--log" - ] - - attempt = 1 - while True: - spawn_process(args, insert_vglrun=self.acceleration_enabled) - if self.test.gazebo() == True: - break - if attempt == MAX_ATTEMPT: - rospy.logerr("[GAZEBO] Launch Failed") - return - attempt = attempt + 1 - - - ######## LAUNCH PX4 ######## - args = ["/opt/ros/noetic/bin/roslaunch", - "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/px4.launch", - "--log" - ] - - attempt = 1 - while True: - spawn_process(args, insert_vglrun=self.acceleration_enabled) - if self.test.px4() == True: - break - if attempt == MAX_ATTEMPT: - rospy.logerr("[PX4] Launch Failed") - return - attempt = attempt + 1 - - - ######## LAUNCH MAVROS ######## - args = ["/opt/ros/noetic/bin/roslaunch", - "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/mavros.launch", - "--log" - ] - - attempt = 1 - while True: - spawn_process(args, insert_vglrun=self.acceleration_enabled) - if self.test.mavros() == True: - break - if attempt == MAX_ATTEMPT: - rospy.logerr("[MAVROS] Launch Failed") - return - attempt = attempt + 1 - - -if __name__ == "__main__": - launch = Launch() - launch.start() - - with open("/drones_launch.log", "w") as f: - f.write("success") diff --git a/exercises/static/exercises/drone_gymkhana/web-template/launch/mavros.launch b/exercises/static/exercises/drone_gymkhana/web-template/launch/mavros.launch deleted file mode 100644 index b899c0ec1..000000000 --- a/exercises/static/exercises/drone_gymkhana/web-template/launch/mavros.launch +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/exercises/static/exercises/drone_gymkhana/web-template/launch/px4.launch b/exercises/static/exercises/drone_gymkhana/web-template/launch/px4.launch deleted file mode 100644 index 59f3108b3..000000000 --- a/exercises/static/exercises/drone_gymkhana/web-template/launch/px4.launch +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/exercises/static/exercises/drone_gymkhana/web-template/shared/__init__.py b/exercises/static/exercises/drone_gymkhana/web-template/shared/__init__.py new file mode 100755 index 000000000..e69de29bb diff --git a/exercises/static/exercises/drone_gymkhana/web-template/shared/image.py b/exercises/static/exercises/drone_gymkhana/web-template/shared/image.py new file mode 100755 index 000000000..de5c9f9d6 --- /dev/null +++ b/exercises/static/exercises/drone_gymkhana/web-template/shared/image.py @@ -0,0 +1,109 @@ +import numpy as np +import mmap +from posix_ipc import Semaphore, O_CREX, ExistentialError, O_CREAT, SharedMemory, unlink_shared_memory +from ctypes import sizeof, memmove, addressof, create_string_buffer +from shared.structure_img import MD + +# Probably, using self variables gives errors with memmove +# Therefore, a global variable for utility +md_buf = create_string_buffer(sizeof(MD)) + +class SharedImage: + def __init__(self, name): + # Initialize variables for memory regions and buffers and Semaphore + self.shm_buf = None; self.shm_region = None + self.md_buf = None; self.md_region = None + self.image_lock = None + + self.shm_name = name; self.md_name = name + "-meta" + self.image_lock_name = name + + # Initialize or retreive metadata memory region + try: + self.md_region = SharedMemory(self.md_name) + self.md_buf = mmap.mmap(self.md_region.fd, sizeof(MD)) + self.md_region.close_fd() + except ExistentialError: + self.md_region = SharedMemory(self.md_name, O_CREAT, size=sizeof(MD)) + self.md_buf = mmap.mmap(self.md_region.fd, self.md_region.size) + self.md_region.close_fd() + + # Initialize or retreive Semaphore + try: + self.image_lock = Semaphore(self.image_lock_name, O_CREX) + except ExistentialError: + image_lock = Semaphore(self.image_lock_name, O_CREAT) + image_lock.unlink() + self.image_lock = Semaphore(self.image_lock_name, O_CREX) + + self.image_lock.release() + + # Get the shared image + def get(self): + # Define metadata + metadata = MD() + + # Get metadata from the shared region + self.image_lock.acquire() + md_buf[:] = self.md_buf + memmove(addressof(metadata), md_buf, sizeof(metadata)) + self.image_lock.release() + + # Try to retreive the image from shm_buffer + # Otherwise return a zero image + try: + self.shm_region = SharedMemory(self.shm_name) + self.shm_buf = mmap.mmap(self.shm_region.fd, metadata.size) + self.shm_region.close_fd() + + self.image_lock.acquire() + image = np.ndarray(shape=(metadata.shape_0, metadata.shape_1, metadata.shape_2), + dtype='uint8', buffer=self.shm_buf) + self.image_lock.release() + + # Check for a None image + if(image.size == 0): + image = np.zeros((3, 3, 3), np.uint8) + + except ExistentialError: + image = np.zeros((3, 3, 3), np.uint8) + + return image + + # Add the shared image + def add(self, image): + try: + # Get byte size of the image + byte_size = image.nbytes + + # Get the shared memory buffer to read from + if not self.shm_region: + self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=byte_size) + self.shm_buf = mmap.mmap(self.shm_region.fd, byte_size) + self.shm_region.close_fd() + + # Generate meta data + metadata = MD(image.shape[0], image.shape[1], image.shape[2], byte_size) + + # Send the meta data and image to shared regions + self.image_lock.acquire() + memmove(md_buf, addressof(metadata), sizeof(metadata)) + self.md_buf[:] = md_buf[:] + self.shm_buf[:] = image.tobytes() + self.image_lock.release() + except: + pass + + # Destructor function to unlink and disconnect + def close(self): + self.image_lock.acquire() + self.md_buf.close() + + try: + unlink_shared_memory(self.md_name) + unlink_shared_memory(self.shm_name) + except ExistentialError: + pass + + self.image_lock.release() + self.image_lock.close() diff --git a/exercises/static/exercises/drone_gymkhana/web-template/shared/structure_img.py b/exercises/static/exercises/drone_gymkhana/web-template/shared/structure_img.py new file mode 100755 index 000000000..ae40b3707 --- /dev/null +++ b/exercises/static/exercises/drone_gymkhana/web-template/shared/structure_img.py @@ -0,0 +1,9 @@ +from ctypes import Structure, c_int32, c_int64 + +class MD(Structure): + _fields_ = [ + ('shape_0', c_int32), + ('shape_1', c_int32), + ('shape_2', c_int32), + ('size', c_int64) + ] \ No newline at end of file diff --git a/exercises/static/exercises/drone_gymkhana/web-template/shared/value.py b/exercises/static/exercises/drone_gymkhana/web-template/shared/value.py new file mode 100755 index 000000000..e7bfad8a2 --- /dev/null +++ b/exercises/static/exercises/drone_gymkhana/web-template/shared/value.py @@ -0,0 +1,93 @@ +import numpy as np +import mmap +from posix_ipc import Semaphore, O_CREX, ExistentialError, O_CREAT, SharedMemory, unlink_shared_memory +from ctypes import sizeof, memmove, addressof, create_string_buffer, c_float +import struct + +class SharedValue: + def __init__(self, name): + # Initialize varaibles for memory regions and buffers and Semaphore + self.shm_buf = None; self.shm_region = None + self.value_lock = None + + self.shm_name = name; self.value_lock_name = name + + # Initialize or retreive Semaphore + try: + self.value_lock = Semaphore(self.value_lock_name, O_CREX) + except ExistentialError: + value_lock = Semaphore(self.value_lock_name, O_CREAT) + value_lock.unlink() + self.value_lock = Semaphore(self.value_lock_name, O_CREX) + + self.value_lock.release() + + # Get the shared value + def get(self, type_name= "value"): + # Retreive the data from buffer + if type_name=="value": + try: + self.shm_region = SharedMemory(self.shm_name) + self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) + self.shm_region.close_fd() + except ExistentialError: + self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=sizeof(c_float)) + self.shm_buf = mmap.mmap(self.shm_region.fd, self.shm_region.size) + self.shm_region.close_fd() + self.value_lock.acquire() + value = struct.unpack('f', self.shm_buf)[0] + self.value_lock.release() + + return value + elif type_name=="list": + self.shm_region = SharedMemory(self.shm_name) + self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) + self.shm_region.close_fd() + self.value_lock.acquire() + array_val = np.ndarray(shape=(3,), + dtype='float32', buffer=self.shm_buf) + self.value_lock.release() + + return array_val + + else: + print("missing argument for return type") + + + # Add the shared value + def add(self, value, type_name= "value"): + # Send the data to shared regions + if type_name=="value": + try: + self.shm_region = SharedMemory(self.shm_name) + self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) + self.shm_region.close_fd() + except ExistentialError: + self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=sizeof(c_float)) + self.shm_buf = mmap.mmap(self.shm_region.fd, self.shm_region.size) + self.shm_region.close_fd() + + self.value_lock.acquire() + self.shm_buf[:] = struct.pack('f', value) + self.value_lock.release() + elif type_name=="list": + byte_size = value.nbytes + self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=byte_size) + self.shm_buf = mmap.mmap(self.shm_region.fd, byte_size) + self.shm_region.close_fd() + self.value_lock.acquire() + self.shm_buf[:] = value.tobytes() + self.value_lock.release() + + # Destructor function to unlink and disconnect + def close(self): + self.value_lock.acquire() + self.shm_buf.close() + + try: + unlink_shared_memory(self.shm_name) + except ExistentialError: + pass + + self.value_lock.release() + self.value_lock.close() diff --git a/exercises/static/exercises/drone_gymkhana/web-template/user_functions.py b/exercises/static/exercises/drone_gymkhana/web-template/user_functions.py new file mode 100755 index 000000000..d741601fc --- /dev/null +++ b/exercises/static/exercises/drone_gymkhana/web-template/user_functions.py @@ -0,0 +1,117 @@ +from shared.image import SharedImage +from shared.value import SharedValue +import numpy as np +import cv2 + +# Define HAL functions +class HALFunctions: + def __init__(self): + # Initialize image variable + self.shared_frontal_image = SharedImage("halfrontalimage") + self.shared_ventral_image = SharedImage("halventralimage") + self.shared_x = SharedValue("x") + self.shared_y = SharedValue("y") + self.shared_z = SharedValue("z") + self.shared_takeoff_z = SharedValue("sharedtakeoffz") + self.shared_az = SharedValue("az") + self.shared_azt = SharedValue("azt") + self.shared_vx = SharedValue("vx") + self.shared_vy = SharedValue("vy") + self.shared_vz = SharedValue("vz") + self.shared_landed_state = SharedValue("landedstate") + self.shared_position = SharedValue("position") + self.shared_velocity = SharedValue("velocity") + self.shared_orientation = SharedValue("orientation") + self.shared_roll = SharedValue("roll") + self.shared_pitch = SharedValue("pitch") + self.shared_yaw = SharedValue("yaw") + self.shared_yaw_rate = SharedValue("yawrate") + + + # Get image function + def get_frontal_image(self): + image = self.shared_frontal_image.get() + return image + + # Get left image function + def get_ventral_image(self): + image = self.shared_ventral_image.get() + return image + + def takeoff(self, height): + self.shared_takeoff_z.add(height) + + def land(self): + pass + + def set_cmd_pos(self, x, y , z, az): + self.shared_x.add(x) + self.shared_y.add(y) + self.shared_z.add(z) + self.shared_az.add(az) + + def set_cmd_vel(self, vx, vy, vz, az): + self.shared_vx.add(vx) + self.shared_vy.add(vy) + self.shared_vz.add(vz) + self.shared_azt.add(az) + + def set_cmd_mix(self, vx, vy, z, az): + self.shared_vx.add(vx) + self.shared_vy.add(vy) + self.shared_vz.add(z) + self.shared_azt.add(az) + + + def get_position(self): + position = self.shared_position.get(type_name = "list") + return position + + def get_velocity(self): + velocity = self.shared_velocity.get(type_name = "list") + return velocity + + def get_yaw_rate(self): + yaw_rate = self.shared_yaw_rate.get(type_name = "value") + return yaw_rate + + def get_orientation(self): + orientation = self.shared_orientation.get(type_name = "list") + return orientation + + def get_roll(self): + roll = self.shared_roll.get(type_name = "value") + return roll + + def get_pitch(self): + pitch = self.shared_pitch.get(type_name = "value") + return pitch + + def get_yaw(self): + yaw = self.shared_yaw.get(type_name = "value") + return yaw + + def get_landed_state(self): + landed_state = self.shared_landed_state.get(type_name = "value") + return landed_state + +# Define GUI functions +class GUIFunctions: + def __init__(self): + # Initialize image variable + self.shared_image = SharedImage("guifrontalimage") + self.shared_left_image = SharedImage("guiventralimage") + + # Show image function + def showImage(self, image): + # Reshape to 3 channel if it has only 1 in order to display it + if (len(image.shape) < 3): + image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB) + self.shared_image.add(image) + + # Show left image function + def showLeftImage(self, image): + # Reshape to 3 channel if it has only 1 in order to display it + if (len(image.shape) < 3): + image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB) + self.shared_left_image.add(image) \ No newline at end of file diff --git a/scripts/instructions.json b/scripts/instructions.json index a20914e9e..29fb4b175 100644 --- a/scripts/instructions.json +++ b/scripts/instructions.json @@ -92,8 +92,9 @@ }, "drone_gymkhana": { "gazebo_path": "/RoboticsAcademy/exercises/drone_gymkhana/web-template/launch", - "instructions_ros": ["python3 ./RoboticsAcademy/exercises/drone_gymkhana/web-template/launch/launch.py"], - "instructions_host": "python3 /RoboticsAcademy/exercises/drone_gymkhana/web-template/exercise.py 0.0.0.0" + "instructions_ros": ["/opt/ros/noetic/bin/roslaunch ./RoboticsAcademy/exercises/drone_gymkhana/web-template/launch/drone_gymkhana.launch"], + "instructions_host": "python3 /RoboticsAcademy/exercises/drone_gymkhana/web-template/exercise.py 0.0.0.0", + "instructions_gui": "python3 /RoboticsAcademy/exercises/drone_gymkhana/web-template/gui.py 0.0.0.0 {}" }, "visual_lander": { "gazebo_path": "/RoboticsAcademy/exercises/visual_lander/web-template/launch", From 424954b924ebca18f79ce517c161c868673f34a2 Mon Sep 17 00:00:00 2001 From: RUFFY-369 Date: Mon, 12 Sep 2022 19:54:35 +0530 Subject: [PATCH 26/42] minor spacing change for visual lander --- .../visual_lander/web-template/launch/visual_lander.launch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/static/exercises/visual_lander/web-template/launch/visual_lander.launch b/exercises/static/exercises/visual_lander/web-template/launch/visual_lander.launch index 9b4962255..804d208e4 100755 --- a/exercises/static/exercises/visual_lander/web-template/launch/visual_lander.launch +++ b/exercises/static/exercises/visual_lander/web-template/launch/visual_lander.launch @@ -31,7 +31,7 @@ - + From cac810c022fad6af53b760254c36bcbcd3297c1c Mon Sep 17 00:00:00 2001 From: RUFFY-369 Date: Tue, 13 Sep 2022 13:35:13 +0530 Subject: [PATCH 27/42] add depth cam for labyrinth escape and drone hangar --- .../drone_hangar/web-template/launch/drone_hangar.launch | 3 +++ .../web-template/launch/labyrinth_escape.launch | 3 +++ 2 files changed, 6 insertions(+) diff --git a/exercises/static/exercises/drone_hangar/web-template/launch/drone_hangar.launch b/exercises/static/exercises/drone_hangar/web-template/launch/drone_hangar.launch index a98173e56..d33e4586f 100755 --- a/exercises/static/exercises/drone_hangar/web-template/launch/drone_hangar.launch +++ b/exercises/static/exercises/drone_hangar/web-template/launch/drone_hangar.launch @@ -10,6 +10,7 @@ + @@ -35,6 +36,7 @@ + @@ -51,6 +53,7 @@ + diff --git a/exercises/static/exercises/labyrinth_escape/web-template/launch/labyrinth_escape.launch b/exercises/static/exercises/labyrinth_escape/web-template/launch/labyrinth_escape.launch index 576047298..2f0bb1c60 100755 --- a/exercises/static/exercises/labyrinth_escape/web-template/launch/labyrinth_escape.launch +++ b/exercises/static/exercises/labyrinth_escape/web-template/launch/labyrinth_escape.launch @@ -10,6 +10,7 @@ + @@ -35,6 +36,7 @@ + @@ -51,6 +53,7 @@ + From d4f4dddc80b47a86cdd7c8fbd74d6318830440dd Mon Sep 17 00:00:00 2001 From: RUFFY-369 Date: Wed, 14 Sep 2022 02:15:57 +0530 Subject: [PATCH 28/42] add a flashlight for HAL interface fo the drone in rescue people exercise as frontend --- .../rescue_people/web-template/brain.py | 2 ++ .../rescue_people/web-template/hal.py | 10 ++++++++++ .../web-template/launch/rescue_people.launch | 2 ++ .../web-template/shared/light.py | 19 +++++++++++++++++++ .../web-template/user_functions.py | 6 ++++++ 5 files changed, 39 insertions(+) create mode 100755 exercises/static/exercises/rescue_people/web-template/shared/light.py diff --git a/exercises/static/exercises/rescue_people/web-template/brain.py b/exercises/static/exercises/rescue_people/web-template/brain.py index 2330c04ba..bf6093961 100755 --- a/exercises/static/exercises/rescue_people/web-template/brain.py +++ b/exercises/static/exercises/rescue_people/web-template/brain.py @@ -122,6 +122,8 @@ def generate_modules(self): hal_module.HAL.get_yaw = self.hal.get_yaw hal_module.HAL.get_landed_state = self.hal.get_landed_state hal_module.HAL.get_yaw_rate = self.hal.get_yaw_rate + hal_module.HAL.set_cmd_on = self.hal.set_cmd_on + hal_module.HAL.set_cmd_off = self.hal.set_cmd_off diff --git a/exercises/static/exercises/rescue_people/web-template/hal.py b/exercises/static/exercises/rescue_people/web-template/hal.py index 17c39cd6b..a0cf18640 100755 --- a/exercises/static/exercises/rescue_people/web-template/hal.py +++ b/exercises/static/exercises/rescue_people/web-template/hal.py @@ -5,6 +5,7 @@ from datetime import datetime from drone_wrapper import DroneWrapper +from shared.light import Light from shared.image import SharedImage from shared.value import SharedValue @@ -38,6 +39,7 @@ def __init__(self): self.image = None self.drone = DroneWrapper(name="rqt",ns="/iris/") + self.light = Light() # Update thread self.thread = ThreadHAL(self.update_hal) @@ -121,6 +123,12 @@ def takeoff(self): def land(self): self.drone.land() + + def set_cmd_on(self): + self.light.set_cmd_on() + + def set_cmd_off(self): + self.light.set_cmd_off() def update_hal(self): self.get_frontal_image() @@ -136,6 +144,8 @@ def update_hal(self): self.set_cmd_pos() self.set_cmd_vel() self.set_cmd_mix() + self.set_cmd_on() + self.set_cmd_off() # Destructor function to close all fds def __del__(self): diff --git a/exercises/static/exercises/rescue_people/web-template/launch/rescue_people.launch b/exercises/static/exercises/rescue_people/web-template/launch/rescue_people.launch index e740c2eed..565a62897 100755 --- a/exercises/static/exercises/rescue_people/web-template/launch/rescue_people.launch +++ b/exercises/static/exercises/rescue_people/web-template/launch/rescue_people.launch @@ -10,6 +10,7 @@ + @@ -37,6 +38,7 @@ + diff --git a/exercises/static/exercises/rescue_people/web-template/shared/light.py b/exercises/static/exercises/rescue_people/web-template/shared/light.py new file mode 100755 index 000000000..943d5d951 --- /dev/null +++ b/exercises/static/exercises/rescue_people/web-template/shared/light.py @@ -0,0 +1,19 @@ +import subprocess + +class Light: + def __init__(self): + pass + + def set_cmd_on(self): + + try: + subprocess.run('''gz topic -p /gazebo/default/light/modify -m "name:'iris::flashlight::light_source::lamp' range:30.0"''', shell = True,check= True) + except: + pass + + def set_cmd_off(self): + + try: + subprocess.run('''gz topic -p /gazebo/default/light/modify -m "name:'iris::flashlight::light_source::lamp' range:0.0"''', shell = True,check= True) + except: + pass diff --git a/exercises/static/exercises/rescue_people/web-template/user_functions.py b/exercises/static/exercises/rescue_people/web-template/user_functions.py index d741601fc..c6b2b30a2 100755 --- a/exercises/static/exercises/rescue_people/web-template/user_functions.py +++ b/exercises/static/exercises/rescue_people/web-template/user_functions.py @@ -94,6 +94,12 @@ def get_yaw(self): def get_landed_state(self): landed_state = self.shared_landed_state.get(type_name = "value") return landed_state + + def set_cmd_on(self): + pass + + def set_cmd_off(self): + pass # Define GUI functions class GUIFunctions: From 6f05d9e0b5e51943776a2303585201253aa912d9 Mon Sep 17 00:00:00 2001 From: RUFFY-369 Date: Wed, 14 Sep 2022 16:24:05 +0530 Subject: [PATCH 29/42] fix spacing issue --- .../web-template/launch/package_delivery.launch | 4 ++-- .../web-template/launch/position_control.launch | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/exercises/static/exercises/package_delivery/web-template/launch/package_delivery.launch b/exercises/static/exercises/package_delivery/web-template/launch/package_delivery.launch index f1d383018..a3a66929a 100755 --- a/exercises/static/exercises/package_delivery/web-template/launch/package_delivery.launch +++ b/exercises/static/exercises/package_delivery/web-template/launch/package_delivery.launch @@ -31,8 +31,8 @@ - - + + diff --git a/exercises/static/exercises/position_control/web-template/launch/position_control.launch b/exercises/static/exercises/position_control/web-template/launch/position_control.launch index 2447ba769..61ae74d66 100755 --- a/exercises/static/exercises/position_control/web-template/launch/position_control.launch +++ b/exercises/static/exercises/position_control/web-template/launch/position_control.launch @@ -31,7 +31,7 @@ - + From c2be8816f08b74085d35d27afda370a0b1a3d568 Mon Sep 17 00:00:00 2001 From: RUFFY-369 Date: Wed, 14 Sep 2022 17:50:18 +0530 Subject: [PATCH 30/42] final manager.py for each drone ex working with sim --- scripts/manager.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/scripts/manager.py b/scripts/manager.py index 9d999d5be..d91481f48 100644 --- a/scripts/manager.py +++ b/scripts/manager.py @@ -24,9 +24,7 @@ def check_device(device_path): RADI_VERSION = "3.2.3" DRI_PATH = "/dev/dri/card0" ACCELERATION_ENABLED = check_device(DRI_PATH) -DRONE_EX = ["follow_road", "labyrinth_escape", "position_control", - "rescue_people", "drone_hangar", "drone_gymkhana", "visual_lander", "drone_cat_mouse_game", - "package_delivery", "power_tower_inspection"] +DRONE_EX = [] CIRCUIT_EX = ["follow_line", "follow_line_game"] HARD_RESET_EX = ["obstacle_avoidance"] STDR_EX = ["laser_mapping", "laser_loc"] From 34cb9a63271f32b3969b721f241ef4b951615ca7 Mon Sep 17 00:00:00 2001 From: RUFFY-369 Date: Wed, 14 Sep 2022 18:10:55 +0530 Subject: [PATCH 31/42] drone hangar world shadows bug fixed --- .../exercises/drone_hangar/web-template/drone_hangar.world | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/static/exercises/drone_hangar/web-template/drone_hangar.world b/exercises/static/exercises/drone_hangar/web-template/drone_hangar.world index b4f62b1a0..78fc9ad80 100644 --- a/exercises/static/exercises/drone_hangar/web-template/drone_hangar.world +++ b/exercises/static/exercises/drone_hangar/web-template/drone_hangar.world @@ -73,7 +73,7 @@ 0.05 0.001 - 1 + 0 From 863b2d209db37dff8ce202f500fbcd42f7abb54c Mon Sep 17 00:00:00 2001 From: RUFFY-369 Date: Wed, 14 Sep 2022 19:10:09 +0530 Subject: [PATCH 32/42] unnecessary lines removal --- .../static/exercises/follow_turtlebot/web-template/exercise.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/exercises/static/exercises/follow_turtlebot/web-template/exercise.py b/exercises/static/exercises/follow_turtlebot/web-template/exercise.py index 0b48f60f1..82bf0c72a 100755 --- a/exercises/static/exercises/follow_turtlebot/web-template/exercise.py +++ b/exercises/static/exercises/follow_turtlebot/web-template/exercise.py @@ -25,7 +25,6 @@ from hal import HAL -from shared.turtlebot import Turtlebot from console import start_console, close_console @@ -54,8 +53,6 @@ def __init__(self): self.host = sys.argv[1] # Initialize the GUI, HAL and Console behind the scenes self.hal = HAL() - self.turtlebot = Turtlebot() - # self.gui = GUI(self.host, self.turtlebot) self.paused = False From 675b7fb59d4a49ab44138dd420c07d41513d1506 Mon Sep 17 00:00:00 2001 From: RUFFY-369 Date: Thu, 15 Sep 2022 18:25:58 +0530 Subject: [PATCH 33/42] turtlebot movement bug fixed --- .../follow_turtlebot/web-template/gui.py | 7 +- .../launch/follow_turtlebot.launch | 2 +- .../web-template/shared/turtlebot.py | 78 ++++++++----------- 3 files changed, 37 insertions(+), 50 deletions(-) diff --git a/exercises/static/exercises/follow_turtlebot/web-template/gui.py b/exercises/static/exercises/follow_turtlebot/web-template/gui.py index 74be0a153..22ca1c241 100755 --- a/exercises/static/exercises/follow_turtlebot/web-template/gui.py +++ b/exercises/static/exercises/follow_turtlebot/web-template/gui.py @@ -22,7 +22,7 @@ class GUI: # Initialization function # The actual initialization - def __init__(self, host, turtlebot): + def __init__(self, host): rospy.init_node("GUI") self.payload = {'image': ''} @@ -40,7 +40,7 @@ def __init__(self, host, turtlebot): self.ack_event = multiprocessing.Event() self.cli_event = multiprocessing.Event() # Take the console object to set the same websocket and client - self.turtlebot = turtlebot + self.turtlebot = Turtlebot() # Start server thread t = threading.Thread(target=self.run_server) @@ -168,7 +168,6 @@ def __init__(self): super(ProcessGUI, self).__init__() self.host = sys.argv[1] - self.turtlebot = Turtlebot() # Time variables self.time_cycle = SharedValue("gui_time_cycle") self.ideal_cycle = SharedValue("gui_ideal_cycle") @@ -184,7 +183,7 @@ def initialize_events(self): # Function to start the execution of threads def run(self): # Initialize GUI - self.gui = GUI(self.host, self.turtlebot) + self.gui = GUI(self.host) self.initialize_events() # Wait for client before starting diff --git a/exercises/static/exercises/follow_turtlebot/web-template/launch/follow_turtlebot.launch b/exercises/static/exercises/follow_turtlebot/web-template/launch/follow_turtlebot.launch index c87525257..a821608d7 100755 --- a/exercises/static/exercises/follow_turtlebot/web-template/launch/follow_turtlebot.launch +++ b/exercises/static/exercises/follow_turtlebot/web-template/launch/follow_turtlebot.launch @@ -25,7 +25,7 @@ - + diff --git a/exercises/static/exercises/follow_turtlebot/web-template/shared/turtlebot.py b/exercises/static/exercises/follow_turtlebot/web-template/shared/turtlebot.py index a3c16925d..2187f3bd6 100755 --- a/exercises/static/exercises/follow_turtlebot/web-template/shared/turtlebot.py +++ b/exercises/static/exercises/follow_turtlebot/web-template/shared/turtlebot.py @@ -1,19 +1,20 @@ -import sys import rospy -from threading import Event - +import random +from geometry_msgs.msg import Twist +import gazebo_msgs.msg from gazebo_msgs.msg import ModelState -from gazebo_msgs.srv import GetModelState from gazebo_msgs.srv import SetModelState -from interfaces.threadStoppable import StoppableThread class Turtlebot(): def __init__(self): self.set_state = rospy.ServiceProxy('/gazebo/set_model_state', SetModelState) - self.get_state = rospy.ServiceProxy('/gazebo/get_model_state', GetModelState) - self.play_event = Event() - rospy.sleep(2) - self.stop_turtlebot() + rospy.sleep(0.1) + self.msg = Twist() + rospy.Subscriber("/gazebo/model_states", gazebo_msgs.msg.ModelStates, self.turtlebot_pose) + self.play = 0 + self.tb_vel_pub = rospy.Publisher("/cmd_vel", Twist, queue_size=1) + rospy.Timer(rospy.Duration(0.1), self.tb_state_update_callback) + # Explicit initialization functions # Class method, so user can call it without instantiation @@ -21,45 +22,35 @@ def __init__(self): def initRobot(cls): new_instance = cls() return new_instance - + + def turtlebot_pose(self,data): + if self.play == 1: + self.x_v = random.uniform(0.0,0.2) + self.y_v= random.uniform(0.0,0.2) if self.x_v<=0.15 else 0.05 + + def tb_state_update_callback(self, event= None): + if self.play == 1: + self.x_v = random.uniform(0.0,0.2) + self.y_v= random.uniform(0.0,0.2) if self.x_v<=0.15 else 0.05 + self.msg.linear.x = self.x_v + self.msg.linear.y = self.y_v + elif self.play == 0: + self.msg.linear.x = 0.0 + self.msg.linear.y = 0.0 + else: + pass + self.tb_vel_pub.publish(self.msg) + def start_turtlebot(self): - try: - self.play_event.set() - rospy.sleep(0.5) - self.thread.join() - except: - print("Thread not yet started") + self.play = 1 + def stop_turtlebot(self): - self.play_event.clear() - self.thread = StoppableThread(target=self.__stop__, args=[]) - self.thread.start() + self.play = 0 - def __stop__(self): - rec = self.get_state("turtlebot3", "base_link") - req = ModelState() - req.model_name = "turtlebot3" - req.twist.linear.x = 0.0 - req.twist.linear.y = 0.0 - req.twist.linear.z = 0.0 - req.twist.angular.x = 0.0 - req.twist.angular.y = 0.0 - req.twist.angular.z = 0.0 - req.pose.position.x = rec.pose.position.x + 3.0 - req.pose.position.y = rec.pose.position.y + 1.0 - req.pose.position.z = 0.0 - req.pose.orientation.x = rec.pose.orientation.x - req.pose.orientation.y = rec.pose.orientation.y - req.pose.orientation.z = rec.pose.orientation.z - req.pose.orientation.w = 1.0 - while True: - if self.play_event.is_set(): - sys.exit() # kill stop_turtlebot thread - else: - self.set_state(req) def reset_turtlebot(self): - self.start_turtlebot() + self.play = 2 req = ModelState() req.model_name = "turtlebot3" req.twist.linear.x = 0.0 @@ -76,6 +67,3 @@ def reset_turtlebot(self): req.pose.orientation.z = 0.0 req.pose.orientation.w = 0.0 self.set_state(req) - self.stop_turtlebot() - - From 24ac93758ccd489829dd11da463f633846f587ec Mon Sep 17 00:00:00 2001 From: RUFFY-369 Date: Fri, 16 Sep 2022 16:33:08 +0530 Subject: [PATCH 34/42] pos control ex bug fix for beacon and value script of multiprocessing refactored --- .../web-template/shared/value.py | 6 +- .../web-template/shared/value.py | 6 +- .../drone_hangar/web-template/shared/value.py | 93 ------------------- .../follow_road/web-template/shared/value.py | 6 +- .../web-template/shared/value.py | 16 +--- .../web-template/shared/value.py | 6 +- .../web-template/shared/value.py | 6 +- .../position_control/web-template/hal.py | 11 +-- .../web-template/shared/value.py | 32 ++++--- .../web-template/user_functions.py | 9 +- .../web-template/shared/value.py | 6 +- .../web-template/shared/value.py | 6 +- .../web-template/shared/value.py | 6 +- 13 files changed, 59 insertions(+), 150 deletions(-) delete mode 100755 exercises/static/exercises/drone_hangar/web-template/shared/value.py diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/shared/value.py b/exercises/static/exercises/drone_cat_mouse/web-template/shared/value.py index e7bfad8a2..9315148d4 100755 --- a/exercises/static/exercises/drone_cat_mouse/web-template/shared/value.py +++ b/exercises/static/exercises/drone_cat_mouse/web-template/shared/value.py @@ -40,8 +40,10 @@ def get(self, type_name= "value"): return value elif type_name=="list": - self.shm_region = SharedMemory(self.shm_name) - self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) + mock_val_arr = np.array([0.0,0.0,0.0]) + byte_size = mock_val_arr.nbytes + self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=byte_size) + self.shm_buf = mmap.mmap(self.shm_region.fd, byte_size) self.shm_region.close_fd() self.value_lock.acquire() array_val = np.ndarray(shape=(3,), diff --git a/exercises/static/exercises/drone_gymkhana/web-template/shared/value.py b/exercises/static/exercises/drone_gymkhana/web-template/shared/value.py index e7bfad8a2..9315148d4 100755 --- a/exercises/static/exercises/drone_gymkhana/web-template/shared/value.py +++ b/exercises/static/exercises/drone_gymkhana/web-template/shared/value.py @@ -40,8 +40,10 @@ def get(self, type_name= "value"): return value elif type_name=="list": - self.shm_region = SharedMemory(self.shm_name) - self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) + mock_val_arr = np.array([0.0,0.0,0.0]) + byte_size = mock_val_arr.nbytes + self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=byte_size) + self.shm_buf = mmap.mmap(self.shm_region.fd, byte_size) self.shm_region.close_fd() self.value_lock.acquire() array_val = np.ndarray(shape=(3,), diff --git a/exercises/static/exercises/drone_hangar/web-template/shared/value.py b/exercises/static/exercises/drone_hangar/web-template/shared/value.py deleted file mode 100755 index e7bfad8a2..000000000 --- a/exercises/static/exercises/drone_hangar/web-template/shared/value.py +++ /dev/null @@ -1,93 +0,0 @@ -import numpy as np -import mmap -from posix_ipc import Semaphore, O_CREX, ExistentialError, O_CREAT, SharedMemory, unlink_shared_memory -from ctypes import sizeof, memmove, addressof, create_string_buffer, c_float -import struct - -class SharedValue: - def __init__(self, name): - # Initialize varaibles for memory regions and buffers and Semaphore - self.shm_buf = None; self.shm_region = None - self.value_lock = None - - self.shm_name = name; self.value_lock_name = name - - # Initialize or retreive Semaphore - try: - self.value_lock = Semaphore(self.value_lock_name, O_CREX) - except ExistentialError: - value_lock = Semaphore(self.value_lock_name, O_CREAT) - value_lock.unlink() - self.value_lock = Semaphore(self.value_lock_name, O_CREX) - - self.value_lock.release() - - # Get the shared value - def get(self, type_name= "value"): - # Retreive the data from buffer - if type_name=="value": - try: - self.shm_region = SharedMemory(self.shm_name) - self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) - self.shm_region.close_fd() - except ExistentialError: - self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=sizeof(c_float)) - self.shm_buf = mmap.mmap(self.shm_region.fd, self.shm_region.size) - self.shm_region.close_fd() - self.value_lock.acquire() - value = struct.unpack('f', self.shm_buf)[0] - self.value_lock.release() - - return value - elif type_name=="list": - self.shm_region = SharedMemory(self.shm_name) - self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) - self.shm_region.close_fd() - self.value_lock.acquire() - array_val = np.ndarray(shape=(3,), - dtype='float32', buffer=self.shm_buf) - self.value_lock.release() - - return array_val - - else: - print("missing argument for return type") - - - # Add the shared value - def add(self, value, type_name= "value"): - # Send the data to shared regions - if type_name=="value": - try: - self.shm_region = SharedMemory(self.shm_name) - self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) - self.shm_region.close_fd() - except ExistentialError: - self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=sizeof(c_float)) - self.shm_buf = mmap.mmap(self.shm_region.fd, self.shm_region.size) - self.shm_region.close_fd() - - self.value_lock.acquire() - self.shm_buf[:] = struct.pack('f', value) - self.value_lock.release() - elif type_name=="list": - byte_size = value.nbytes - self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=byte_size) - self.shm_buf = mmap.mmap(self.shm_region.fd, byte_size) - self.shm_region.close_fd() - self.value_lock.acquire() - self.shm_buf[:] = value.tobytes() - self.value_lock.release() - - # Destructor function to unlink and disconnect - def close(self): - self.value_lock.acquire() - self.shm_buf.close() - - try: - unlink_shared_memory(self.shm_name) - except ExistentialError: - pass - - self.value_lock.release() - self.value_lock.close() diff --git a/exercises/static/exercises/follow_road/web-template/shared/value.py b/exercises/static/exercises/follow_road/web-template/shared/value.py index e7bfad8a2..9315148d4 100755 --- a/exercises/static/exercises/follow_road/web-template/shared/value.py +++ b/exercises/static/exercises/follow_road/web-template/shared/value.py @@ -40,8 +40,10 @@ def get(self, type_name= "value"): return value elif type_name=="list": - self.shm_region = SharedMemory(self.shm_name) - self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) + mock_val_arr = np.array([0.0,0.0,0.0]) + byte_size = mock_val_arr.nbytes + self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=byte_size) + self.shm_buf = mmap.mmap(self.shm_region.fd, byte_size) self.shm_region.close_fd() self.value_lock.acquire() array_val = np.ndarray(shape=(3,), diff --git a/exercises/static/exercises/follow_turtlebot/web-template/shared/value.py b/exercises/static/exercises/follow_turtlebot/web-template/shared/value.py index 3ec51ca46..9315148d4 100755 --- a/exercises/static/exercises/follow_turtlebot/web-template/shared/value.py +++ b/exercises/static/exercises/follow_turtlebot/web-template/shared/value.py @@ -12,16 +12,6 @@ def __init__(self, name): self.shm_name = name; self.value_lock_name = name - # Initialize shared memory buffer - # try: - # self.shm_region = SharedMemory(self.shm_name) - # self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) - # self.shm_region.close_fd() - #except ExistentialError: - # self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=sizeof(c_float)) - # self.shm_buf = mmap.mmap(self.shm_region.fd, self.shm_region.size) - # self.shm_region.close_fd() - # Initialize or retreive Semaphore try: self.value_lock = Semaphore(self.value_lock_name, O_CREX) @@ -50,8 +40,10 @@ def get(self, type_name= "value"): return value elif type_name=="list": - self.shm_region = SharedMemory(self.shm_name) - self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) + mock_val_arr = np.array([0.0,0.0,0.0]) + byte_size = mock_val_arr.nbytes + self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=byte_size) + self.shm_buf = mmap.mmap(self.shm_region.fd, byte_size) self.shm_region.close_fd() self.value_lock.acquire() array_val = np.ndarray(shape=(3,), diff --git a/exercises/static/exercises/labyrinth_escape/web-template/shared/value.py b/exercises/static/exercises/labyrinth_escape/web-template/shared/value.py index e7bfad8a2..9315148d4 100755 --- a/exercises/static/exercises/labyrinth_escape/web-template/shared/value.py +++ b/exercises/static/exercises/labyrinth_escape/web-template/shared/value.py @@ -40,8 +40,10 @@ def get(self, type_name= "value"): return value elif type_name=="list": - self.shm_region = SharedMemory(self.shm_name) - self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) + mock_val_arr = np.array([0.0,0.0,0.0]) + byte_size = mock_val_arr.nbytes + self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=byte_size) + self.shm_buf = mmap.mmap(self.shm_region.fd, byte_size) self.shm_region.close_fd() self.value_lock.acquire() array_val = np.ndarray(shape=(3,), diff --git a/exercises/static/exercises/package_delivery/web-template/shared/value.py b/exercises/static/exercises/package_delivery/web-template/shared/value.py index e7bfad8a2..9315148d4 100755 --- a/exercises/static/exercises/package_delivery/web-template/shared/value.py +++ b/exercises/static/exercises/package_delivery/web-template/shared/value.py @@ -40,8 +40,10 @@ def get(self, type_name= "value"): return value elif type_name=="list": - self.shm_region = SharedMemory(self.shm_name) - self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) + mock_val_arr = np.array([0.0,0.0,0.0]) + byte_size = mock_val_arr.nbytes + self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=byte_size) + self.shm_buf = mmap.mmap(self.shm_region.fd, byte_size) self.shm_region.close_fd() self.value_lock.acquire() array_val = np.ndarray(shape=(3,), diff --git a/exercises/static/exercises/position_control/web-template/hal.py b/exercises/static/exercises/position_control/web-template/hal.py index eaf75d164..548978fb4 100755 --- a/exercises/static/exercises/position_control/web-template/hal.py +++ b/exercises/static/exercises/position_control/web-template/hal.py @@ -126,19 +126,12 @@ def land(self): self.drone.land() def init_beacons(self): - self.beacons = [] - self.beacons.append(Beacon('beacon1', np.array([0, 5, 0]), False, False)) - self.beacons.append(Beacon('beacon2', np.array([5, 0, 0]), False, False)) - self.beacons.append(Beacon('beacon3', np.array([0, -5, 0]), False, False)) - self.beacons.append(Beacon('beacon4', np.array([-5, 0, 0]), False, False)) - self.beacons.append(Beacon('beacon5', np.array([10, 0, 0]), False, False)) - self.beacons.append(Beacon('initial', np.array([0, 0, 0]), False, False)) - self.shared_beacons.add(self.beacons ,type_name="list") + self.beacons = self.shared_beacons.get(type_name = "list", n_elem = 6) def get_next_beacon(self): for beacon in self.beacons: if beacon.is_reached() == False: - self.shared_beacons.add(beacon ,type_name="list") + self.shared_beacons.add(np.array([beacon]) ,type_name="list") def update_hal(self): diff --git a/exercises/static/exercises/position_control/web-template/shared/value.py b/exercises/static/exercises/position_control/web-template/shared/value.py index 6d32cf2cd..4b902fd10 100755 --- a/exercises/static/exercises/position_control/web-template/shared/value.py +++ b/exercises/static/exercises/position_control/web-template/shared/value.py @@ -12,16 +12,6 @@ def __init__(self, name): self.shm_name = name; self.value_lock_name = name - # Initialize shared memory buffer - # try: - # self.shm_region = SharedMemory(self.shm_name) - # self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) - # self.shm_region.close_fd() - #except ExistentialError: - # self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=sizeof(c_float)) - # self.shm_buf = mmap.mmap(self.shm_region.fd, self.shm_region.size) - # self.shm_region.close_fd() - # Initialize or retreive Semaphore try: self.value_lock = Semaphore(self.value_lock_name, O_CREX) @@ -33,7 +23,7 @@ def __init__(self, name): self.value_lock.release() # Get the shared value - def get(self, type_name= "value"): + def get(self, type_name= "value", n_elem = 3): # Retreive the data from buffer if type_name=="value": try: @@ -50,12 +40,26 @@ def get(self, type_name= "value"): return value elif type_name=="list": - self.shm_region = SharedMemory(self.shm_name) - self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) + if n_elem == 1 : + mock_val_arr = np.array([0.0]) + elif n_elem == 3: + mock_val_arr = np.array([0.0, 0.0, 0.0]) + elif n_elem == 6: + mock_val_arr = np.array([0.0, 0.0, 0.0, 0.0, 0.0, 0.0]) + byte_size = mock_val_arr.nbytes + self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=byte_size) + self.shm_buf = mmap.mmap(self.shm_region.fd, byte_size) self.shm_region.close_fd() self.value_lock.acquire() - array_val = np.ndarray(shape=(6,), + if n_elem == 1 : + array_val = np.ndarray(shape=(1,), + dtype = 'O', buffer=self.shm_buf) + elif n_elem == 3: + array_val = np.ndarray(shape=(3,), dtype='float32', buffer=self.shm_buf) + elif n_elem == 6: + array_val = np.ndarray(shape=(6,), + dtype = 'O', buffer=self.shm_buf) self.value_lock.release() return array_val diff --git a/exercises/static/exercises/position_control/web-template/user_functions.py b/exercises/static/exercises/position_control/web-template/user_functions.py index c95cc2113..d0464d60e 100755 --- a/exercises/static/exercises/position_control/web-template/user_functions.py +++ b/exercises/static/exercises/position_control/web-template/user_functions.py @@ -106,13 +106,10 @@ def init_beacons(self): self.beacons.append(Beacon('beacon5', np.array([10, 0, 0]), False, False)) self.beacons.append(Beacon('initial', np.array([0, 0, 0]), False, False)) self.shared_beacons.add(self.beacons ,type_name="list") - + def get_next_beacon(self): - for beacon in self.beacons: - if beacon.is_reached() == False: - self.shared_beacons.add(beacon ,type_name="list") - beacon = self.shared_landed_state.get(type_name = "value") - return beacon + beacon = self.shared_beacons.get(type_name = "list", n_elem = 1) + return beacon[0] # Define GUI functions class GUIFunctions: diff --git a/exercises/static/exercises/power_tower_inspection/web-template/shared/value.py b/exercises/static/exercises/power_tower_inspection/web-template/shared/value.py index e7bfad8a2..9315148d4 100755 --- a/exercises/static/exercises/power_tower_inspection/web-template/shared/value.py +++ b/exercises/static/exercises/power_tower_inspection/web-template/shared/value.py @@ -40,8 +40,10 @@ def get(self, type_name= "value"): return value elif type_name=="list": - self.shm_region = SharedMemory(self.shm_name) - self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) + mock_val_arr = np.array([0.0,0.0,0.0]) + byte_size = mock_val_arr.nbytes + self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=byte_size) + self.shm_buf = mmap.mmap(self.shm_region.fd, byte_size) self.shm_region.close_fd() self.value_lock.acquire() array_val = np.ndarray(shape=(3,), diff --git a/exercises/static/exercises/rescue_people/web-template/shared/value.py b/exercises/static/exercises/rescue_people/web-template/shared/value.py index e7bfad8a2..9315148d4 100755 --- a/exercises/static/exercises/rescue_people/web-template/shared/value.py +++ b/exercises/static/exercises/rescue_people/web-template/shared/value.py @@ -40,8 +40,10 @@ def get(self, type_name= "value"): return value elif type_name=="list": - self.shm_region = SharedMemory(self.shm_name) - self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) + mock_val_arr = np.array([0.0,0.0,0.0]) + byte_size = mock_val_arr.nbytes + self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=byte_size) + self.shm_buf = mmap.mmap(self.shm_region.fd, byte_size) self.shm_region.close_fd() self.value_lock.acquire() array_val = np.ndarray(shape=(3,), diff --git a/exercises/static/exercises/visual_lander/web-template/shared/value.py b/exercises/static/exercises/visual_lander/web-template/shared/value.py index e7bfad8a2..9315148d4 100755 --- a/exercises/static/exercises/visual_lander/web-template/shared/value.py +++ b/exercises/static/exercises/visual_lander/web-template/shared/value.py @@ -40,8 +40,10 @@ def get(self, type_name= "value"): return value elif type_name=="list": - self.shm_region = SharedMemory(self.shm_name) - self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) + mock_val_arr = np.array([0.0,0.0,0.0]) + byte_size = mock_val_arr.nbytes + self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=byte_size) + self.shm_buf = mmap.mmap(self.shm_region.fd, byte_size) self.shm_region.close_fd() self.value_lock.acquire() array_val = np.ndarray(shape=(3,), From b66a07507f4a01f4f9027e0e5df79d618b2b8924 Mon Sep 17 00:00:00 2001 From: RUFFY-369 Date: Fri, 16 Sep 2022 19:10:58 +0530 Subject: [PATCH 35/42] commit removes one starting error due to rospy timer(irrelevant) --- .../exercises/follow_turtlebot/web-template/shared/turtlebot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/static/exercises/follow_turtlebot/web-template/shared/turtlebot.py b/exercises/static/exercises/follow_turtlebot/web-template/shared/turtlebot.py index 2187f3bd6..a6c165a92 100755 --- a/exercises/static/exercises/follow_turtlebot/web-template/shared/turtlebot.py +++ b/exercises/static/exercises/follow_turtlebot/web-template/shared/turtlebot.py @@ -28,7 +28,7 @@ def turtlebot_pose(self,data): self.x_v = random.uniform(0.0,0.2) self.y_v= random.uniform(0.0,0.2) if self.x_v<=0.15 else 0.05 - def tb_state_update_callback(self, event= None): + def tb_state_update_callback(self, event): if self.play == 1: self.x_v = random.uniform(0.0,0.2) self.y_v= random.uniform(0.0,0.2) if self.x_v<=0.15 else 0.05 From 8eb2ba19b713dd35943656c7c2dc3d7d56a13293 Mon Sep 17 00:00:00 2001 From: RUFFY-369 Date: Mon, 19 Sep 2022 13:51:50 +0530 Subject: [PATCH 36/42] reduced shared variable objects for optimisation --- .../drone_cat_mouse/web-template/hal.py | 62 ++++----- .../drone_cat_mouse/web-template/hal_guest.py | 53 +++----- .../web-template/shared/value.py | 114 ++++++++-------- .../web-template/user_functions.py | 45 ++++--- .../web-template/user_functions_guest.py | 48 ++++--- .../drone_gymkhana/web-template/hal.py | 53 ++++---- .../web-template/shared/value.py | 114 ++++++++-------- .../web-template/user_functions.py | 45 ++++--- .../drone_hangar/web-template/hal.py | 53 ++++---- .../drone_hangar/web-template/shared/value.py | 87 ++++++++++++ .../web-template/user_functions.py | 45 ++++--- .../exercises/follow_road/web-template/hal.py | 53 ++++---- .../follow_road/web-template/shared/value.py | 114 ++++++++-------- .../web-template/user_functions.py | 45 ++++--- .../follow_turtlebot/web-template/hal.py | 53 ++++---- .../web-template/shared/value.py | 114 ++++++++-------- .../web-template/user_functions.py | 45 ++++--- .../labyrinth_escape/web-template/hal.py | 53 ++++---- .../web-template/shared/value.py | 114 ++++++++-------- .../web-template/user_functions.py | 45 ++++--- .../package_delivery/web-template/hal.py | 62 ++++----- .../web-template/shared/value.py | 114 ++++++++-------- .../web-template/user_functions.py | 54 ++++---- .../position_control/web-template/hal.py | 78 ++++++----- .../web-template/shared/value.py | 126 ++++++++---------- .../web-template/user_functions.py | 58 ++++---- .../web-template/hal.py | 53 ++++---- .../web-template/shared/value.py | 114 ++++++++-------- .../web-template/user_functions.py | 45 ++++--- .../rescue_people/web-template/hal.py | 62 ++++----- .../web-template/shared/value.py | 114 ++++++++-------- .../web-template/user_functions.py | 49 ++++--- .../visual_lander/web-template/hal.py | 53 ++++---- .../web-template/shared/value.py | 114 ++++++++-------- .../web-template/user_functions.py | 45 ++++--- scripts/pylint_checker.py | 2 +- 36 files changed, 1254 insertions(+), 1244 deletions(-) create mode 100755 exercises/static/exercises/drone_hangar/web-template/shared/value.py diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/hal.py b/exercises/static/exercises/drone_cat_mouse/web-template/hal.py index 92690646a..c31e5582c 100644 --- a/exercises/static/exercises/drone_cat_mouse/web-template/hal.py +++ b/exercises/static/exercises/drone_cat_mouse/web-template/hal.py @@ -12,9 +12,9 @@ class HAL: IMG_WIDTH = 320 IMG_HEIGHT = 240 - + def __init__(self): - rospy.init_node("HAL_cat") + rospy.init_node("HAL") self.shared_frontal_image = SharedImage("halfrontalimage") self.shared_ventral_image = SharedImage("halventralimage") @@ -28,25 +28,29 @@ def __init__(self): self.shared_vy = SharedValue("vy") self.shared_vz = SharedValue("vz") self.shared_landed_state = SharedValue("landedstate") - self.shared_position = SharedValue("position") - self.shared_velocity = SharedValue("velocity") - self.shared_orientation = SharedValue("orientation") - self.shared_roll = SharedValue("roll") - self.shared_pitch = SharedValue("pitch") - self.shared_yaw = SharedValue("yaw") + self.shared_position = SharedValue("position",3) + self.shared_velocity = SharedValue("velocity",3) + self.shared_orientation = SharedValue("orientation",3) self.shared_yaw_rate = SharedValue("yawrate") + self.shared_CMD = SharedValue("CMD") + self.image = None self.cat = DroneWrapper(name="rqt", ns="/iris/") + # Update thread self.thread = ThreadHAL(self.update_hal) + + # Explicit initialization functions # Class method, so user can call it without instantiation + + # Function to start the update thread def start_thread(self): self.thread.start() - + # Get Image from ROS Driver Camera def get_frontal_image(self): image = self.cat.get_frontal_image() @@ -60,11 +64,11 @@ def get_ventral_image(self): def get_position(self): pos = self.cat.get_position() - self.shared_position.add(pos,type_name="list") + self.shared_position.add(pos) def get_velocity(self): vel = self.cat.get_velocity() - self.shared_velocity.add(vel ,type_name="list") + self.shared_velocity.add(vel ) def get_yaw_rate(self): yaw_rate = self.cat.get_yaw_rate() @@ -72,19 +76,7 @@ def get_yaw_rate(self): def get_orientation(self): orientation = self.cat.get_orientation() - self.shared_orientation.add(orientation ,type_name="list") - - def get_roll(self): - roll = self.cat.get_roll() - self.shared_roll.add(roll) - - def get_pitch(self): - pitch = self.cat.get_pitch() - self.shared_pitch.add(pitch) - - def get_yaw(self): - yaw = self.cat.get_yaw() - self.shared_yaw.add(yaw) + self.shared_orientation.add(orientation ) def get_landed_state(self): state = self.cat.get_landed_state() @@ -120,19 +112,26 @@ def land(self): self.cat.land() def update_hal(self): + CMD = self.shared_CMD.get() + self.get_frontal_image() self.get_ventral_image() self.get_position() self.get_velocity() self.get_yaw_rate() self.get_orientation() - self.get_pitch() - self.get_roll() - self.get_yaw() self.get_landed_state() - self.set_cmd_pos() - self.set_cmd_vel() - self.set_cmd_mix() + + if CMD == 0: # POS + self.set_cmd_pos() + elif CMD == 1: # VEL + self.set_cmd_vel() + elif CMD == 2: # MIX + self.set_cmd_mix() + elif CMD == 3: # TAKEOFF + self.takeoff() + elif CMD == 4: # LAND + self.land() # Destructor function to close all fds def __del__(self): @@ -151,9 +150,6 @@ def __del__(self): self.shared_position.close() self.shared_velocity.close() self.shared_orientation.close() - self.shared_roll.close() - self.shared_pitch.close() - self.shared_yaw.close() self.shared_yaw_rate.close() class ThreadHAL(threading.Thread): diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/hal_guest.py b/exercises/static/exercises/drone_cat_mouse/web-template/hal_guest.py index fcaf26cbe..8932582f3 100644 --- a/exercises/static/exercises/drone_cat_mouse/web-template/hal_guest.py +++ b/exercises/static/exercises/drone_cat_mouse/web-template/hal_guest.py @@ -29,14 +29,13 @@ def __init__(self): self.shared_vy = SharedValue("vyguest") self.shared_vz = SharedValue("vzguest") self.shared_landed_state = SharedValue("landedstateguest") - self.shared_position = SharedValue("positionguest") - self.shared_velocity = SharedValue("velocityguest") - self.shared_orientation = SharedValue("orientationguest") - self.shared_roll = SharedValue("rollguest") - self.shared_pitch = SharedValue("pitchguest") - self.shared_yaw = SharedValue("yawguest") + self.shared_position = SharedValue("positionguest",3) + self.shared_velocity = SharedValue("velocityguest",3) + self.shared_orientation = SharedValue("orientationguest",3) self.shared_yaw_rate = SharedValue("yawrateguest") + self.shared_CMD = SharedValue("CMDguest") + self.image = None self.mouse = DroneWrapper(name="rqt", ns="/iris1/") @@ -46,7 +45,7 @@ def __init__(self): # Function to start the update thread def start_thread(self): self.thread.start() - + # Get Image from ROS Driver Camera def get_frontal_image(self): image = self.mouse.get_frontal_image() @@ -60,11 +59,11 @@ def get_ventral_image(self): def get_position(self): pos = self.mouse.get_position() - self.shared_position.add(pos,type_name="list") + self.shared_position.add(pos) def get_velocity(self): vel = self.mouse.get_velocity() - self.shared_velocity.add(vel ,type_name="list") + self.shared_velocity.add(vel ) def get_yaw_rate(self): yaw_rate = self.mouse.get_yaw_rate() @@ -72,19 +71,7 @@ def get_yaw_rate(self): def get_orientation(self): orientation = self.mouse.get_orientation() - self.shared_orientation.add(orientation ,type_name="list") - - def get_roll(self): - roll = self.mouse.get_roll() - self.shared_roll.add(roll) - - def get_pitch(self): - pitch = self.mouse.get_pitch() - self.shared_pitch.add(pitch) - - def get_yaw(self): - yaw = self.mouse.get_yaw() - self.shared_yaw.add(yaw) + self.shared_orientation.add(orientation ) def get_landed_state(self): state = self.mouse.get_landed_state() @@ -120,19 +107,26 @@ def land(self): self.mouse.land() def update_hal(self): + CMD = self.shared_CMD.get() + self.get_frontal_image() self.get_ventral_image() self.get_position() self.get_velocity() self.get_yaw_rate() self.get_orientation() - self.get_pitch() - self.get_roll() - self.get_yaw() self.get_landed_state() - self.set_cmd_pos() - self.set_cmd_vel() - self.set_cmd_mix() + + if CMD == 0: # POS + self.set_cmd_pos() + elif CMD == 1: # VEL + self.set_cmd_vel() + elif CMD == 2: # MIX + self.set_cmd_mix() + elif CMD == 3: # TAKEOFF + self.takeoff() + elif CMD == 4: # LAND + self.land() # Destructor function to close all fds def __del__(self): @@ -151,9 +145,6 @@ def __del__(self): self.shared_position.close() self.shared_velocity.close() self.shared_orientation.close() - self.shared_roll.close() - self.shared_pitch.close() - self.shared_yaw.close() self.shared_yaw_rate.close() class ThreadHAL(threading.Thread): diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/shared/value.py b/exercises/static/exercises/drone_cat_mouse/web-template/shared/value.py index 9315148d4..0146309a2 100755 --- a/exercises/static/exercises/drone_cat_mouse/web-template/shared/value.py +++ b/exercises/static/exercises/drone_cat_mouse/web-template/shared/value.py @@ -5,91 +5,83 @@ import struct class SharedValue: - def __init__(self, name): + def __init__(self, name, n_elem = 1): # Initialize varaibles for memory regions and buffers and Semaphore - self.shm_buf = None; self.shm_region = None - self.value_lock = None + self.n_elem = n_elem + self.shm_buf = [None]*self.n_elem; self.shm_region = [None]*self.n_elem + self.value_lock = [None]*self.n_elem self.shm_name = name; self.value_lock_name = name # Initialize or retreive Semaphore - try: - self.value_lock = Semaphore(self.value_lock_name, O_CREX) - except ExistentialError: - value_lock = Semaphore(self.value_lock_name, O_CREAT) - value_lock.unlink() - self.value_lock = Semaphore(self.value_lock_name, O_CREX) + for i in range(self.n_elem): + try: + self.value_lock[i] = Semaphore(self.value_lock_name+str(i), O_CREX) + except ExistentialError: + value_lock = Semaphore(self.value_lock_name+str(i), O_CREAT) + value_lock.unlink() + self.value_lock[i] = Semaphore(self.value_lock_name+str(i), O_CREX) - self.value_lock.release() + self.value_lock[i].release() # Get the shared value - def get(self, type_name= "value"): + def get(self): # Retreive the data from buffer - if type_name=="value": + + value = [None]*self.n_elem + for i in range(self.n_elem): try: - self.shm_region = SharedMemory(self.shm_name) - self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) - self.shm_region.close_fd() + self.shm_region[i] = SharedMemory(self.shm_name+str(i)) + self.shm_buf[i] = mmap.mmap(self.shm_region[i].fd, sizeof(c_float)) + self.shm_region[i].close_fd() except ExistentialError: - self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=sizeof(c_float)) - self.shm_buf = mmap.mmap(self.shm_region.fd, self.shm_region.size) - self.shm_region.close_fd() - self.value_lock.acquire() - value = struct.unpack('f', self.shm_buf)[0] - self.value_lock.release() + self.shm_region[i] = SharedMemory(self.shm_name+str(i), O_CREAT, size=sizeof(c_float)) + self.shm_buf[i] = mmap.mmap(self.shm_region[i].fd, self.shm_region[i].size) + self.shm_region[i].close_fd() + self.value_lock[i].acquire() + value[i] = struct.unpack('f', self.shm_buf[i])[0] + self.value_lock[i].release() + if self.n_elem <=1: + return value[0] + else: return value - elif type_name=="list": - mock_val_arr = np.array([0.0,0.0,0.0]) - byte_size = mock_val_arr.nbytes - self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=byte_size) - self.shm_buf = mmap.mmap(self.shm_region.fd, byte_size) - self.shm_region.close_fd() - self.value_lock.acquire() - array_val = np.ndarray(shape=(3,), - dtype='float32', buffer=self.shm_buf) - self.value_lock.release() - return array_val - else: - print("missing argument for return type") # Add the shared value - def add(self, value, type_name= "value"): + def add(self, value): # Send the data to shared regions - if type_name=="value": + + for i in range(self.n_elem): try: - self.shm_region = SharedMemory(self.shm_name) - self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) - self.shm_region.close_fd() + self.shm_region[i] = SharedMemory(self.shm_name+str(i)) + self.shm_buf[i] = mmap.mmap(self.shm_region[i].fd, sizeof(c_float)) + self.shm_region[i].close_fd() except ExistentialError: - self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=sizeof(c_float)) - self.shm_buf = mmap.mmap(self.shm_region.fd, self.shm_region.size) - self.shm_region.close_fd() + self.shm_region[i] = SharedMemory(self.shm_name+str(i), O_CREAT, size=sizeof(c_float)) + self.shm_buf[i] = mmap.mmap(self.shm_region[i].fd, self.shm_region[i].size) + self.shm_region[i].close_fd() - self.value_lock.acquire() - self.shm_buf[:] = struct.pack('f', value) - self.value_lock.release() - elif type_name=="list": - byte_size = value.nbytes - self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=byte_size) - self.shm_buf = mmap.mmap(self.shm_region.fd, byte_size) - self.shm_region.close_fd() - self.value_lock.acquire() - self.shm_buf[:] = value.tobytes() - self.value_lock.release() + self.value_lock[i].acquire() + if self.n_elem <=1: + self.shm_buf[i][:] = struct.pack('f', value) + else: + self.shm_buf[i][:] = struct.pack('f', value[i]) + self.value_lock[i].release() + # Destructor function to unlink and disconnect def close(self): - self.value_lock.acquire() - self.shm_buf.close() + for i in range(self.n_elem): + self.value_lock[i].acquire() + self.shm_buf[i].close() - try: - unlink_shared_memory(self.shm_name) - except ExistentialError: - pass + try: + unlink_shared_memory(self.shm_name+str(i)) + except ExistentialError: + pass - self.value_lock.release() - self.value_lock.close() + self.value_lock[i].release() + self.value_lock[i].close() diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/user_functions.py b/exercises/static/exercises/drone_cat_mouse/web-template/user_functions.py index d741601fc..e595a5be1 100755 --- a/exercises/static/exercises/drone_cat_mouse/web-template/user_functions.py +++ b/exercises/static/exercises/drone_cat_mouse/web-template/user_functions.py @@ -19,14 +19,13 @@ def __init__(self): self.shared_vy = SharedValue("vy") self.shared_vz = SharedValue("vz") self.shared_landed_state = SharedValue("landedstate") - self.shared_position = SharedValue("position") - self.shared_velocity = SharedValue("velocity") - self.shared_orientation = SharedValue("orientation") - self.shared_roll = SharedValue("roll") - self.shared_pitch = SharedValue("pitch") - self.shared_yaw = SharedValue("yaw") + self.shared_position = SharedValue("position",3) + self.shared_velocity = SharedValue("velocity",3) + self.shared_orientation = SharedValue("orientation",3) self.shared_yaw_rate = SharedValue("yawrate") + self.shared_CMD = SharedValue("CMD") + # Get image function def get_frontal_image(self): @@ -41,58 +40,66 @@ def get_ventral_image(self): def takeoff(self, height): self.shared_takeoff_z.add(height) + self.shared_CMD.add(3) #TAKEOFF + def land(self): - pass + self.shared_CMD.add(4) #LAND def set_cmd_pos(self, x, y , z, az): self.shared_x.add(x) self.shared_y.add(y) self.shared_z.add(z) self.shared_az.add(az) + + self.shared_CMD.add(0) #POS def set_cmd_vel(self, vx, vy, vz, az): self.shared_vx.add(vx) self.shared_vy.add(vy) self.shared_vz.add(vz) self.shared_azt.add(az) + + self.shared_CMD.add(1) #VEL def set_cmd_mix(self, vx, vy, z, az): self.shared_vx.add(vx) self.shared_vy.add(vy) - self.shared_vz.add(z) + self.shared_z.add(z) self.shared_azt.add(az) + + self.shared_CMD.add(2) #MIX def get_position(self): - position = self.shared_position.get(type_name = "list") - return position + position = self.shared_position.get() + return np.array(position) def get_velocity(self): - velocity = self.shared_velocity.get(type_name = "list") - return velocity + velocity = self.shared_velocity.get() + return np.array(velocity) def get_yaw_rate(self): - yaw_rate = self.shared_yaw_rate.get(type_name = "value") + yaw_rate = self.shared_yaw_rate.get() return yaw_rate def get_orientation(self): - orientation = self.shared_orientation.get(type_name = "list") - return orientation + orientation = self.shared_orientation.get() + return np.array(orientation) def get_roll(self): - roll = self.shared_roll.get(type_name = "value") + roll = self.shared_orientation.get()[0] return roll def get_pitch(self): - pitch = self.shared_pitch.get(type_name = "value") + pitch = self.shared_orientation.get()[1] return pitch def get_yaw(self): - yaw = self.shared_yaw.get(type_name = "value") + yaw = self.shared_orientation.get()[2] return yaw def get_landed_state(self): - landed_state = self.shared_landed_state.get(type_name = "value") + landed_state = self.shared_landed_state.get() return landed_state # Define GUI functions diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/user_functions_guest.py b/exercises/static/exercises/drone_cat_mouse/web-template/user_functions_guest.py index dfe9c9873..add1d5c08 100644 --- a/exercises/static/exercises/drone_cat_mouse/web-template/user_functions_guest.py +++ b/exercises/static/exercises/drone_cat_mouse/web-template/user_functions_guest.py @@ -3,7 +3,7 @@ import numpy as np import cv2 -# Define Guest HAL functions +# Define HAL functions class HALFunctions: def __init__(self): # Initialize image variable @@ -19,14 +19,14 @@ def __init__(self): self.shared_vy = SharedValue("vyguest") self.shared_vz = SharedValue("vzguest") self.shared_landed_state = SharedValue("landedstateguest") - self.shared_position = SharedValue("positionguest") - self.shared_velocity = SharedValue("velocityguest") - self.shared_orientation = SharedValue("orientationguest") - self.shared_roll = SharedValue("rollguest") - self.shared_pitch = SharedValue("pitchguest") - self.shared_yaw = SharedValue("yawguest") + self.shared_position = SharedValue("positionguest",3) + self.shared_velocity = SharedValue("velocityguest",3) + self.shared_orientation = SharedValue("orientationguest",3) self.shared_yaw_rate = SharedValue("yawrateguest") + self.shared_CMD = SharedValue("CMDguest") + + # Get image function def get_frontal_image(self): image = self.shared_frontal_image.get() @@ -40,58 +40,66 @@ def get_ventral_image(self): def takeoff(self, height): self.shared_takeoff_z.add(height) + self.shared_CMD.add(3) #TAKEOFF + def land(self): - pass + self.shared_CMD.add(4) #LAND def set_cmd_pos(self, x, y , z, az): self.shared_x.add(x) self.shared_y.add(y) self.shared_z.add(z) self.shared_az.add(az) + + self.shared_CMD.add(0) #POS def set_cmd_vel(self, vx, vy, vz, az): self.shared_vx.add(vx) self.shared_vy.add(vy) self.shared_vz.add(vz) self.shared_azt.add(az) + + self.shared_CMD.add(1) #VEL def set_cmd_mix(self, vx, vy, z, az): self.shared_vx.add(vx) self.shared_vy.add(vy) - self.shared_vz.add(z) + self.shared_z.add(z) self.shared_azt.add(az) + + self.shared_CMD.add(2) #MIX def get_position(self): - position = self.shared_position.get(type_name = "list") - return position + position = self.shared_position.get() + return np.array(position) def get_velocity(self): - velocity = self.shared_velocity.get(type_name = "list") - return velocity + velocity = self.shared_velocity.get() + return np.array(velocity) def get_yaw_rate(self): - yaw_rate = self.shared_yaw_rate.get(type_name = "value") + yaw_rate = self.shared_yaw_rate.get() return yaw_rate def get_orientation(self): - orientation = self.shared_orientation.get(type_name = "list") - return orientation + orientation = self.shared_orientation.get() + return np.array(orientation) def get_roll(self): - roll = self.shared_roll.get(type_name = "value") + roll = self.shared_orientation.get()[0] return roll def get_pitch(self): - pitch = self.shared_pitch.get(type_name = "value") + pitch = self.shared_orientation.get()[1] return pitch def get_yaw(self): - yaw = self.shared_yaw.get(type_name = "value") + yaw = self.shared_orientation.get()[2] return yaw def get_landed_state(self): - landed_state = self.shared_landed_state.get(type_name = "value") + landed_state = self.shared_landed_state.get() return landed_state # Define GUI functions diff --git a/exercises/static/exercises/drone_gymkhana/web-template/hal.py b/exercises/static/exercises/drone_gymkhana/web-template/hal.py index 17c39cd6b..09850aeb6 100755 --- a/exercises/static/exercises/drone_gymkhana/web-template/hal.py +++ b/exercises/static/exercises/drone_gymkhana/web-template/hal.py @@ -28,20 +28,21 @@ def __init__(self): self.shared_vy = SharedValue("vy") self.shared_vz = SharedValue("vz") self.shared_landed_state = SharedValue("landedstate") - self.shared_position = SharedValue("position") - self.shared_velocity = SharedValue("velocity") - self.shared_orientation = SharedValue("orientation") - self.shared_roll = SharedValue("roll") - self.shared_pitch = SharedValue("pitch") - self.shared_yaw = SharedValue("yaw") + self.shared_position = SharedValue("position",3) + self.shared_velocity = SharedValue("velocity",3) + self.shared_orientation = SharedValue("orientation",3) self.shared_yaw_rate = SharedValue("yawrate") + self.shared_CMD = SharedValue("CMD") + self.image = None self.drone = DroneWrapper(name="rqt",ns="/iris/") # Update thread self.thread = ThreadHAL(self.update_hal) + + # Explicit initialization functions # Class method, so user can call it without instantiation @@ -63,11 +64,11 @@ def get_ventral_image(self): def get_position(self): pos = self.drone.get_position() - self.shared_position.add(pos,type_name="list") + self.shared_position.add(pos) def get_velocity(self): vel = self.drone.get_velocity() - self.shared_velocity.add(vel ,type_name="list") + self.shared_velocity.add(vel ) def get_yaw_rate(self): yaw_rate = self.drone.get_yaw_rate() @@ -75,19 +76,7 @@ def get_yaw_rate(self): def get_orientation(self): orientation = self.drone.get_orientation() - self.shared_orientation.add(orientation ,type_name="list") - - def get_roll(self): - roll = self.drone.get_roll() - self.shared_roll.add(roll) - - def get_pitch(self): - pitch = self.drone.get_pitch() - self.shared_pitch.add(pitch) - - def get_yaw(self): - yaw = self.drone.get_yaw() - self.shared_yaw.add(yaw) + self.shared_orientation.add(orientation ) def get_landed_state(self): state = self.drone.get_landed_state() @@ -123,19 +112,26 @@ def land(self): self.drone.land() def update_hal(self): + CMD = self.shared_CMD.get() + self.get_frontal_image() self.get_ventral_image() self.get_position() self.get_velocity() self.get_yaw_rate() self.get_orientation() - self.get_pitch() - self.get_roll() - self.get_yaw() self.get_landed_state() - self.set_cmd_pos() - self.set_cmd_vel() - self.set_cmd_mix() + + if CMD == 0: # POS + self.set_cmd_pos() + elif CMD == 1: # VEL + self.set_cmd_vel() + elif CMD == 2: # MIX + self.set_cmd_mix() + elif CMD == 3: # TAKEOFF + self.takeoff() + elif CMD == 4: # LAND + self.land() # Destructor function to close all fds def __del__(self): @@ -154,9 +150,6 @@ def __del__(self): self.shared_position.close() self.shared_velocity.close() self.shared_orientation.close() - self.shared_roll.close() - self.shared_pitch.close() - self.shared_yaw.close() self.shared_yaw_rate.close() class ThreadHAL(threading.Thread): diff --git a/exercises/static/exercises/drone_gymkhana/web-template/shared/value.py b/exercises/static/exercises/drone_gymkhana/web-template/shared/value.py index 9315148d4..0146309a2 100755 --- a/exercises/static/exercises/drone_gymkhana/web-template/shared/value.py +++ b/exercises/static/exercises/drone_gymkhana/web-template/shared/value.py @@ -5,91 +5,83 @@ import struct class SharedValue: - def __init__(self, name): + def __init__(self, name, n_elem = 1): # Initialize varaibles for memory regions and buffers and Semaphore - self.shm_buf = None; self.shm_region = None - self.value_lock = None + self.n_elem = n_elem + self.shm_buf = [None]*self.n_elem; self.shm_region = [None]*self.n_elem + self.value_lock = [None]*self.n_elem self.shm_name = name; self.value_lock_name = name # Initialize or retreive Semaphore - try: - self.value_lock = Semaphore(self.value_lock_name, O_CREX) - except ExistentialError: - value_lock = Semaphore(self.value_lock_name, O_CREAT) - value_lock.unlink() - self.value_lock = Semaphore(self.value_lock_name, O_CREX) + for i in range(self.n_elem): + try: + self.value_lock[i] = Semaphore(self.value_lock_name+str(i), O_CREX) + except ExistentialError: + value_lock = Semaphore(self.value_lock_name+str(i), O_CREAT) + value_lock.unlink() + self.value_lock[i] = Semaphore(self.value_lock_name+str(i), O_CREX) - self.value_lock.release() + self.value_lock[i].release() # Get the shared value - def get(self, type_name= "value"): + def get(self): # Retreive the data from buffer - if type_name=="value": + + value = [None]*self.n_elem + for i in range(self.n_elem): try: - self.shm_region = SharedMemory(self.shm_name) - self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) - self.shm_region.close_fd() + self.shm_region[i] = SharedMemory(self.shm_name+str(i)) + self.shm_buf[i] = mmap.mmap(self.shm_region[i].fd, sizeof(c_float)) + self.shm_region[i].close_fd() except ExistentialError: - self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=sizeof(c_float)) - self.shm_buf = mmap.mmap(self.shm_region.fd, self.shm_region.size) - self.shm_region.close_fd() - self.value_lock.acquire() - value = struct.unpack('f', self.shm_buf)[0] - self.value_lock.release() + self.shm_region[i] = SharedMemory(self.shm_name+str(i), O_CREAT, size=sizeof(c_float)) + self.shm_buf[i] = mmap.mmap(self.shm_region[i].fd, self.shm_region[i].size) + self.shm_region[i].close_fd() + self.value_lock[i].acquire() + value[i] = struct.unpack('f', self.shm_buf[i])[0] + self.value_lock[i].release() + if self.n_elem <=1: + return value[0] + else: return value - elif type_name=="list": - mock_val_arr = np.array([0.0,0.0,0.0]) - byte_size = mock_val_arr.nbytes - self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=byte_size) - self.shm_buf = mmap.mmap(self.shm_region.fd, byte_size) - self.shm_region.close_fd() - self.value_lock.acquire() - array_val = np.ndarray(shape=(3,), - dtype='float32', buffer=self.shm_buf) - self.value_lock.release() - return array_val - else: - print("missing argument for return type") # Add the shared value - def add(self, value, type_name= "value"): + def add(self, value): # Send the data to shared regions - if type_name=="value": + + for i in range(self.n_elem): try: - self.shm_region = SharedMemory(self.shm_name) - self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) - self.shm_region.close_fd() + self.shm_region[i] = SharedMemory(self.shm_name+str(i)) + self.shm_buf[i] = mmap.mmap(self.shm_region[i].fd, sizeof(c_float)) + self.shm_region[i].close_fd() except ExistentialError: - self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=sizeof(c_float)) - self.shm_buf = mmap.mmap(self.shm_region.fd, self.shm_region.size) - self.shm_region.close_fd() + self.shm_region[i] = SharedMemory(self.shm_name+str(i), O_CREAT, size=sizeof(c_float)) + self.shm_buf[i] = mmap.mmap(self.shm_region[i].fd, self.shm_region[i].size) + self.shm_region[i].close_fd() - self.value_lock.acquire() - self.shm_buf[:] = struct.pack('f', value) - self.value_lock.release() - elif type_name=="list": - byte_size = value.nbytes - self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=byte_size) - self.shm_buf = mmap.mmap(self.shm_region.fd, byte_size) - self.shm_region.close_fd() - self.value_lock.acquire() - self.shm_buf[:] = value.tobytes() - self.value_lock.release() + self.value_lock[i].acquire() + if self.n_elem <=1: + self.shm_buf[i][:] = struct.pack('f', value) + else: + self.shm_buf[i][:] = struct.pack('f', value[i]) + self.value_lock[i].release() + # Destructor function to unlink and disconnect def close(self): - self.value_lock.acquire() - self.shm_buf.close() + for i in range(self.n_elem): + self.value_lock[i].acquire() + self.shm_buf[i].close() - try: - unlink_shared_memory(self.shm_name) - except ExistentialError: - pass + try: + unlink_shared_memory(self.shm_name+str(i)) + except ExistentialError: + pass - self.value_lock.release() - self.value_lock.close() + self.value_lock[i].release() + self.value_lock[i].close() diff --git a/exercises/static/exercises/drone_gymkhana/web-template/user_functions.py b/exercises/static/exercises/drone_gymkhana/web-template/user_functions.py index d741601fc..e595a5be1 100755 --- a/exercises/static/exercises/drone_gymkhana/web-template/user_functions.py +++ b/exercises/static/exercises/drone_gymkhana/web-template/user_functions.py @@ -19,14 +19,13 @@ def __init__(self): self.shared_vy = SharedValue("vy") self.shared_vz = SharedValue("vz") self.shared_landed_state = SharedValue("landedstate") - self.shared_position = SharedValue("position") - self.shared_velocity = SharedValue("velocity") - self.shared_orientation = SharedValue("orientation") - self.shared_roll = SharedValue("roll") - self.shared_pitch = SharedValue("pitch") - self.shared_yaw = SharedValue("yaw") + self.shared_position = SharedValue("position",3) + self.shared_velocity = SharedValue("velocity",3) + self.shared_orientation = SharedValue("orientation",3) self.shared_yaw_rate = SharedValue("yawrate") + self.shared_CMD = SharedValue("CMD") + # Get image function def get_frontal_image(self): @@ -41,58 +40,66 @@ def get_ventral_image(self): def takeoff(self, height): self.shared_takeoff_z.add(height) + self.shared_CMD.add(3) #TAKEOFF + def land(self): - pass + self.shared_CMD.add(4) #LAND def set_cmd_pos(self, x, y , z, az): self.shared_x.add(x) self.shared_y.add(y) self.shared_z.add(z) self.shared_az.add(az) + + self.shared_CMD.add(0) #POS def set_cmd_vel(self, vx, vy, vz, az): self.shared_vx.add(vx) self.shared_vy.add(vy) self.shared_vz.add(vz) self.shared_azt.add(az) + + self.shared_CMD.add(1) #VEL def set_cmd_mix(self, vx, vy, z, az): self.shared_vx.add(vx) self.shared_vy.add(vy) - self.shared_vz.add(z) + self.shared_z.add(z) self.shared_azt.add(az) + + self.shared_CMD.add(2) #MIX def get_position(self): - position = self.shared_position.get(type_name = "list") - return position + position = self.shared_position.get() + return np.array(position) def get_velocity(self): - velocity = self.shared_velocity.get(type_name = "list") - return velocity + velocity = self.shared_velocity.get() + return np.array(velocity) def get_yaw_rate(self): - yaw_rate = self.shared_yaw_rate.get(type_name = "value") + yaw_rate = self.shared_yaw_rate.get() return yaw_rate def get_orientation(self): - orientation = self.shared_orientation.get(type_name = "list") - return orientation + orientation = self.shared_orientation.get() + return np.array(orientation) def get_roll(self): - roll = self.shared_roll.get(type_name = "value") + roll = self.shared_orientation.get()[0] return roll def get_pitch(self): - pitch = self.shared_pitch.get(type_name = "value") + pitch = self.shared_orientation.get()[1] return pitch def get_yaw(self): - yaw = self.shared_yaw.get(type_name = "value") + yaw = self.shared_orientation.get()[2] return yaw def get_landed_state(self): - landed_state = self.shared_landed_state.get(type_name = "value") + landed_state = self.shared_landed_state.get() return landed_state # Define GUI functions diff --git a/exercises/static/exercises/drone_hangar/web-template/hal.py b/exercises/static/exercises/drone_hangar/web-template/hal.py index 17c39cd6b..09850aeb6 100755 --- a/exercises/static/exercises/drone_hangar/web-template/hal.py +++ b/exercises/static/exercises/drone_hangar/web-template/hal.py @@ -28,20 +28,21 @@ def __init__(self): self.shared_vy = SharedValue("vy") self.shared_vz = SharedValue("vz") self.shared_landed_state = SharedValue("landedstate") - self.shared_position = SharedValue("position") - self.shared_velocity = SharedValue("velocity") - self.shared_orientation = SharedValue("orientation") - self.shared_roll = SharedValue("roll") - self.shared_pitch = SharedValue("pitch") - self.shared_yaw = SharedValue("yaw") + self.shared_position = SharedValue("position",3) + self.shared_velocity = SharedValue("velocity",3) + self.shared_orientation = SharedValue("orientation",3) self.shared_yaw_rate = SharedValue("yawrate") + self.shared_CMD = SharedValue("CMD") + self.image = None self.drone = DroneWrapper(name="rqt",ns="/iris/") # Update thread self.thread = ThreadHAL(self.update_hal) + + # Explicit initialization functions # Class method, so user can call it without instantiation @@ -63,11 +64,11 @@ def get_ventral_image(self): def get_position(self): pos = self.drone.get_position() - self.shared_position.add(pos,type_name="list") + self.shared_position.add(pos) def get_velocity(self): vel = self.drone.get_velocity() - self.shared_velocity.add(vel ,type_name="list") + self.shared_velocity.add(vel ) def get_yaw_rate(self): yaw_rate = self.drone.get_yaw_rate() @@ -75,19 +76,7 @@ def get_yaw_rate(self): def get_orientation(self): orientation = self.drone.get_orientation() - self.shared_orientation.add(orientation ,type_name="list") - - def get_roll(self): - roll = self.drone.get_roll() - self.shared_roll.add(roll) - - def get_pitch(self): - pitch = self.drone.get_pitch() - self.shared_pitch.add(pitch) - - def get_yaw(self): - yaw = self.drone.get_yaw() - self.shared_yaw.add(yaw) + self.shared_orientation.add(orientation ) def get_landed_state(self): state = self.drone.get_landed_state() @@ -123,19 +112,26 @@ def land(self): self.drone.land() def update_hal(self): + CMD = self.shared_CMD.get() + self.get_frontal_image() self.get_ventral_image() self.get_position() self.get_velocity() self.get_yaw_rate() self.get_orientation() - self.get_pitch() - self.get_roll() - self.get_yaw() self.get_landed_state() - self.set_cmd_pos() - self.set_cmd_vel() - self.set_cmd_mix() + + if CMD == 0: # POS + self.set_cmd_pos() + elif CMD == 1: # VEL + self.set_cmd_vel() + elif CMD == 2: # MIX + self.set_cmd_mix() + elif CMD == 3: # TAKEOFF + self.takeoff() + elif CMD == 4: # LAND + self.land() # Destructor function to close all fds def __del__(self): @@ -154,9 +150,6 @@ def __del__(self): self.shared_position.close() self.shared_velocity.close() self.shared_orientation.close() - self.shared_roll.close() - self.shared_pitch.close() - self.shared_yaw.close() self.shared_yaw_rate.close() class ThreadHAL(threading.Thread): diff --git a/exercises/static/exercises/drone_hangar/web-template/shared/value.py b/exercises/static/exercises/drone_hangar/web-template/shared/value.py new file mode 100755 index 000000000..0146309a2 --- /dev/null +++ b/exercises/static/exercises/drone_hangar/web-template/shared/value.py @@ -0,0 +1,87 @@ +import numpy as np +import mmap +from posix_ipc import Semaphore, O_CREX, ExistentialError, O_CREAT, SharedMemory, unlink_shared_memory +from ctypes import sizeof, memmove, addressof, create_string_buffer, c_float +import struct + +class SharedValue: + def __init__(self, name, n_elem = 1): + # Initialize varaibles for memory regions and buffers and Semaphore + self.n_elem = n_elem + self.shm_buf = [None]*self.n_elem; self.shm_region = [None]*self.n_elem + self.value_lock = [None]*self.n_elem + + self.shm_name = name; self.value_lock_name = name + + # Initialize or retreive Semaphore + for i in range(self.n_elem): + try: + self.value_lock[i] = Semaphore(self.value_lock_name+str(i), O_CREX) + except ExistentialError: + value_lock = Semaphore(self.value_lock_name+str(i), O_CREAT) + value_lock.unlink() + self.value_lock[i] = Semaphore(self.value_lock_name+str(i), O_CREX) + + self.value_lock[i].release() + + # Get the shared value + def get(self): + # Retreive the data from buffer + + value = [None]*self.n_elem + for i in range(self.n_elem): + try: + self.shm_region[i] = SharedMemory(self.shm_name+str(i)) + self.shm_buf[i] = mmap.mmap(self.shm_region[i].fd, sizeof(c_float)) + self.shm_region[i].close_fd() + except ExistentialError: + self.shm_region[i] = SharedMemory(self.shm_name+str(i), O_CREAT, size=sizeof(c_float)) + self.shm_buf[i] = mmap.mmap(self.shm_region[i].fd, self.shm_region[i].size) + self.shm_region[i].close_fd() + self.value_lock[i].acquire() + value[i] = struct.unpack('f', self.shm_buf[i])[0] + self.value_lock[i].release() + + if self.n_elem <=1: + return value[0] + else: + return value + + + + + # Add the shared value + def add(self, value): + # Send the data to shared regions + + for i in range(self.n_elem): + try: + self.shm_region[i] = SharedMemory(self.shm_name+str(i)) + self.shm_buf[i] = mmap.mmap(self.shm_region[i].fd, sizeof(c_float)) + self.shm_region[i].close_fd() + except ExistentialError: + self.shm_region[i] = SharedMemory(self.shm_name+str(i), O_CREAT, size=sizeof(c_float)) + self.shm_buf[i] = mmap.mmap(self.shm_region[i].fd, self.shm_region[i].size) + self.shm_region[i].close_fd() + + self.value_lock[i].acquire() + if self.n_elem <=1: + self.shm_buf[i][:] = struct.pack('f', value) + else: + self.shm_buf[i][:] = struct.pack('f', value[i]) + self.value_lock[i].release() + + + # Destructor function to unlink and disconnect + def close(self): + for i in range(self.n_elem): + self.value_lock[i].acquire() + self.shm_buf[i].close() + + try: + unlink_shared_memory(self.shm_name+str(i)) + except ExistentialError: + pass + + self.value_lock[i].release() + self.value_lock[i].close() diff --git a/exercises/static/exercises/drone_hangar/web-template/user_functions.py b/exercises/static/exercises/drone_hangar/web-template/user_functions.py index d741601fc..e595a5be1 100755 --- a/exercises/static/exercises/drone_hangar/web-template/user_functions.py +++ b/exercises/static/exercises/drone_hangar/web-template/user_functions.py @@ -19,14 +19,13 @@ def __init__(self): self.shared_vy = SharedValue("vy") self.shared_vz = SharedValue("vz") self.shared_landed_state = SharedValue("landedstate") - self.shared_position = SharedValue("position") - self.shared_velocity = SharedValue("velocity") - self.shared_orientation = SharedValue("orientation") - self.shared_roll = SharedValue("roll") - self.shared_pitch = SharedValue("pitch") - self.shared_yaw = SharedValue("yaw") + self.shared_position = SharedValue("position",3) + self.shared_velocity = SharedValue("velocity",3) + self.shared_orientation = SharedValue("orientation",3) self.shared_yaw_rate = SharedValue("yawrate") + self.shared_CMD = SharedValue("CMD") + # Get image function def get_frontal_image(self): @@ -41,58 +40,66 @@ def get_ventral_image(self): def takeoff(self, height): self.shared_takeoff_z.add(height) + self.shared_CMD.add(3) #TAKEOFF + def land(self): - pass + self.shared_CMD.add(4) #LAND def set_cmd_pos(self, x, y , z, az): self.shared_x.add(x) self.shared_y.add(y) self.shared_z.add(z) self.shared_az.add(az) + + self.shared_CMD.add(0) #POS def set_cmd_vel(self, vx, vy, vz, az): self.shared_vx.add(vx) self.shared_vy.add(vy) self.shared_vz.add(vz) self.shared_azt.add(az) + + self.shared_CMD.add(1) #VEL def set_cmd_mix(self, vx, vy, z, az): self.shared_vx.add(vx) self.shared_vy.add(vy) - self.shared_vz.add(z) + self.shared_z.add(z) self.shared_azt.add(az) + + self.shared_CMD.add(2) #MIX def get_position(self): - position = self.shared_position.get(type_name = "list") - return position + position = self.shared_position.get() + return np.array(position) def get_velocity(self): - velocity = self.shared_velocity.get(type_name = "list") - return velocity + velocity = self.shared_velocity.get() + return np.array(velocity) def get_yaw_rate(self): - yaw_rate = self.shared_yaw_rate.get(type_name = "value") + yaw_rate = self.shared_yaw_rate.get() return yaw_rate def get_orientation(self): - orientation = self.shared_orientation.get(type_name = "list") - return orientation + orientation = self.shared_orientation.get() + return np.array(orientation) def get_roll(self): - roll = self.shared_roll.get(type_name = "value") + roll = self.shared_orientation.get()[0] return roll def get_pitch(self): - pitch = self.shared_pitch.get(type_name = "value") + pitch = self.shared_orientation.get()[1] return pitch def get_yaw(self): - yaw = self.shared_yaw.get(type_name = "value") + yaw = self.shared_orientation.get()[2] return yaw def get_landed_state(self): - landed_state = self.shared_landed_state.get(type_name = "value") + landed_state = self.shared_landed_state.get() return landed_state # Define GUI functions diff --git a/exercises/static/exercises/follow_road/web-template/hal.py b/exercises/static/exercises/follow_road/web-template/hal.py index 17c39cd6b..09850aeb6 100755 --- a/exercises/static/exercises/follow_road/web-template/hal.py +++ b/exercises/static/exercises/follow_road/web-template/hal.py @@ -28,20 +28,21 @@ def __init__(self): self.shared_vy = SharedValue("vy") self.shared_vz = SharedValue("vz") self.shared_landed_state = SharedValue("landedstate") - self.shared_position = SharedValue("position") - self.shared_velocity = SharedValue("velocity") - self.shared_orientation = SharedValue("orientation") - self.shared_roll = SharedValue("roll") - self.shared_pitch = SharedValue("pitch") - self.shared_yaw = SharedValue("yaw") + self.shared_position = SharedValue("position",3) + self.shared_velocity = SharedValue("velocity",3) + self.shared_orientation = SharedValue("orientation",3) self.shared_yaw_rate = SharedValue("yawrate") + self.shared_CMD = SharedValue("CMD") + self.image = None self.drone = DroneWrapper(name="rqt",ns="/iris/") # Update thread self.thread = ThreadHAL(self.update_hal) + + # Explicit initialization functions # Class method, so user can call it without instantiation @@ -63,11 +64,11 @@ def get_ventral_image(self): def get_position(self): pos = self.drone.get_position() - self.shared_position.add(pos,type_name="list") + self.shared_position.add(pos) def get_velocity(self): vel = self.drone.get_velocity() - self.shared_velocity.add(vel ,type_name="list") + self.shared_velocity.add(vel ) def get_yaw_rate(self): yaw_rate = self.drone.get_yaw_rate() @@ -75,19 +76,7 @@ def get_yaw_rate(self): def get_orientation(self): orientation = self.drone.get_orientation() - self.shared_orientation.add(orientation ,type_name="list") - - def get_roll(self): - roll = self.drone.get_roll() - self.shared_roll.add(roll) - - def get_pitch(self): - pitch = self.drone.get_pitch() - self.shared_pitch.add(pitch) - - def get_yaw(self): - yaw = self.drone.get_yaw() - self.shared_yaw.add(yaw) + self.shared_orientation.add(orientation ) def get_landed_state(self): state = self.drone.get_landed_state() @@ -123,19 +112,26 @@ def land(self): self.drone.land() def update_hal(self): + CMD = self.shared_CMD.get() + self.get_frontal_image() self.get_ventral_image() self.get_position() self.get_velocity() self.get_yaw_rate() self.get_orientation() - self.get_pitch() - self.get_roll() - self.get_yaw() self.get_landed_state() - self.set_cmd_pos() - self.set_cmd_vel() - self.set_cmd_mix() + + if CMD == 0: # POS + self.set_cmd_pos() + elif CMD == 1: # VEL + self.set_cmd_vel() + elif CMD == 2: # MIX + self.set_cmd_mix() + elif CMD == 3: # TAKEOFF + self.takeoff() + elif CMD == 4: # LAND + self.land() # Destructor function to close all fds def __del__(self): @@ -154,9 +150,6 @@ def __del__(self): self.shared_position.close() self.shared_velocity.close() self.shared_orientation.close() - self.shared_roll.close() - self.shared_pitch.close() - self.shared_yaw.close() self.shared_yaw_rate.close() class ThreadHAL(threading.Thread): diff --git a/exercises/static/exercises/follow_road/web-template/shared/value.py b/exercises/static/exercises/follow_road/web-template/shared/value.py index 9315148d4..0146309a2 100755 --- a/exercises/static/exercises/follow_road/web-template/shared/value.py +++ b/exercises/static/exercises/follow_road/web-template/shared/value.py @@ -5,91 +5,83 @@ import struct class SharedValue: - def __init__(self, name): + def __init__(self, name, n_elem = 1): # Initialize varaibles for memory regions and buffers and Semaphore - self.shm_buf = None; self.shm_region = None - self.value_lock = None + self.n_elem = n_elem + self.shm_buf = [None]*self.n_elem; self.shm_region = [None]*self.n_elem + self.value_lock = [None]*self.n_elem self.shm_name = name; self.value_lock_name = name # Initialize or retreive Semaphore - try: - self.value_lock = Semaphore(self.value_lock_name, O_CREX) - except ExistentialError: - value_lock = Semaphore(self.value_lock_name, O_CREAT) - value_lock.unlink() - self.value_lock = Semaphore(self.value_lock_name, O_CREX) + for i in range(self.n_elem): + try: + self.value_lock[i] = Semaphore(self.value_lock_name+str(i), O_CREX) + except ExistentialError: + value_lock = Semaphore(self.value_lock_name+str(i), O_CREAT) + value_lock.unlink() + self.value_lock[i] = Semaphore(self.value_lock_name+str(i), O_CREX) - self.value_lock.release() + self.value_lock[i].release() # Get the shared value - def get(self, type_name= "value"): + def get(self): # Retreive the data from buffer - if type_name=="value": + + value = [None]*self.n_elem + for i in range(self.n_elem): try: - self.shm_region = SharedMemory(self.shm_name) - self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) - self.shm_region.close_fd() + self.shm_region[i] = SharedMemory(self.shm_name+str(i)) + self.shm_buf[i] = mmap.mmap(self.shm_region[i].fd, sizeof(c_float)) + self.shm_region[i].close_fd() except ExistentialError: - self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=sizeof(c_float)) - self.shm_buf = mmap.mmap(self.shm_region.fd, self.shm_region.size) - self.shm_region.close_fd() - self.value_lock.acquire() - value = struct.unpack('f', self.shm_buf)[0] - self.value_lock.release() + self.shm_region[i] = SharedMemory(self.shm_name+str(i), O_CREAT, size=sizeof(c_float)) + self.shm_buf[i] = mmap.mmap(self.shm_region[i].fd, self.shm_region[i].size) + self.shm_region[i].close_fd() + self.value_lock[i].acquire() + value[i] = struct.unpack('f', self.shm_buf[i])[0] + self.value_lock[i].release() + if self.n_elem <=1: + return value[0] + else: return value - elif type_name=="list": - mock_val_arr = np.array([0.0,0.0,0.0]) - byte_size = mock_val_arr.nbytes - self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=byte_size) - self.shm_buf = mmap.mmap(self.shm_region.fd, byte_size) - self.shm_region.close_fd() - self.value_lock.acquire() - array_val = np.ndarray(shape=(3,), - dtype='float32', buffer=self.shm_buf) - self.value_lock.release() - return array_val - else: - print("missing argument for return type") # Add the shared value - def add(self, value, type_name= "value"): + def add(self, value): # Send the data to shared regions - if type_name=="value": + + for i in range(self.n_elem): try: - self.shm_region = SharedMemory(self.shm_name) - self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) - self.shm_region.close_fd() + self.shm_region[i] = SharedMemory(self.shm_name+str(i)) + self.shm_buf[i] = mmap.mmap(self.shm_region[i].fd, sizeof(c_float)) + self.shm_region[i].close_fd() except ExistentialError: - self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=sizeof(c_float)) - self.shm_buf = mmap.mmap(self.shm_region.fd, self.shm_region.size) - self.shm_region.close_fd() + self.shm_region[i] = SharedMemory(self.shm_name+str(i), O_CREAT, size=sizeof(c_float)) + self.shm_buf[i] = mmap.mmap(self.shm_region[i].fd, self.shm_region[i].size) + self.shm_region[i].close_fd() - self.value_lock.acquire() - self.shm_buf[:] = struct.pack('f', value) - self.value_lock.release() - elif type_name=="list": - byte_size = value.nbytes - self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=byte_size) - self.shm_buf = mmap.mmap(self.shm_region.fd, byte_size) - self.shm_region.close_fd() - self.value_lock.acquire() - self.shm_buf[:] = value.tobytes() - self.value_lock.release() + self.value_lock[i].acquire() + if self.n_elem <=1: + self.shm_buf[i][:] = struct.pack('f', value) + else: + self.shm_buf[i][:] = struct.pack('f', value[i]) + self.value_lock[i].release() + # Destructor function to unlink and disconnect def close(self): - self.value_lock.acquire() - self.shm_buf.close() + for i in range(self.n_elem): + self.value_lock[i].acquire() + self.shm_buf[i].close() - try: - unlink_shared_memory(self.shm_name) - except ExistentialError: - pass + try: + unlink_shared_memory(self.shm_name+str(i)) + except ExistentialError: + pass - self.value_lock.release() - self.value_lock.close() + self.value_lock[i].release() + self.value_lock[i].close() diff --git a/exercises/static/exercises/follow_road/web-template/user_functions.py b/exercises/static/exercises/follow_road/web-template/user_functions.py index d741601fc..e595a5be1 100755 --- a/exercises/static/exercises/follow_road/web-template/user_functions.py +++ b/exercises/static/exercises/follow_road/web-template/user_functions.py @@ -19,14 +19,13 @@ def __init__(self): self.shared_vy = SharedValue("vy") self.shared_vz = SharedValue("vz") self.shared_landed_state = SharedValue("landedstate") - self.shared_position = SharedValue("position") - self.shared_velocity = SharedValue("velocity") - self.shared_orientation = SharedValue("orientation") - self.shared_roll = SharedValue("roll") - self.shared_pitch = SharedValue("pitch") - self.shared_yaw = SharedValue("yaw") + self.shared_position = SharedValue("position",3) + self.shared_velocity = SharedValue("velocity",3) + self.shared_orientation = SharedValue("orientation",3) self.shared_yaw_rate = SharedValue("yawrate") + self.shared_CMD = SharedValue("CMD") + # Get image function def get_frontal_image(self): @@ -41,58 +40,66 @@ def get_ventral_image(self): def takeoff(self, height): self.shared_takeoff_z.add(height) + self.shared_CMD.add(3) #TAKEOFF + def land(self): - pass + self.shared_CMD.add(4) #LAND def set_cmd_pos(self, x, y , z, az): self.shared_x.add(x) self.shared_y.add(y) self.shared_z.add(z) self.shared_az.add(az) + + self.shared_CMD.add(0) #POS def set_cmd_vel(self, vx, vy, vz, az): self.shared_vx.add(vx) self.shared_vy.add(vy) self.shared_vz.add(vz) self.shared_azt.add(az) + + self.shared_CMD.add(1) #VEL def set_cmd_mix(self, vx, vy, z, az): self.shared_vx.add(vx) self.shared_vy.add(vy) - self.shared_vz.add(z) + self.shared_z.add(z) self.shared_azt.add(az) + + self.shared_CMD.add(2) #MIX def get_position(self): - position = self.shared_position.get(type_name = "list") - return position + position = self.shared_position.get() + return np.array(position) def get_velocity(self): - velocity = self.shared_velocity.get(type_name = "list") - return velocity + velocity = self.shared_velocity.get() + return np.array(velocity) def get_yaw_rate(self): - yaw_rate = self.shared_yaw_rate.get(type_name = "value") + yaw_rate = self.shared_yaw_rate.get() return yaw_rate def get_orientation(self): - orientation = self.shared_orientation.get(type_name = "list") - return orientation + orientation = self.shared_orientation.get() + return np.array(orientation) def get_roll(self): - roll = self.shared_roll.get(type_name = "value") + roll = self.shared_orientation.get()[0] return roll def get_pitch(self): - pitch = self.shared_pitch.get(type_name = "value") + pitch = self.shared_orientation.get()[1] return pitch def get_yaw(self): - yaw = self.shared_yaw.get(type_name = "value") + yaw = self.shared_orientation.get()[2] return yaw def get_landed_state(self): - landed_state = self.shared_landed_state.get(type_name = "value") + landed_state = self.shared_landed_state.get() return landed_state # Define GUI functions diff --git a/exercises/static/exercises/follow_turtlebot/web-template/hal.py b/exercises/static/exercises/follow_turtlebot/web-template/hal.py index 17c39cd6b..09850aeb6 100755 --- a/exercises/static/exercises/follow_turtlebot/web-template/hal.py +++ b/exercises/static/exercises/follow_turtlebot/web-template/hal.py @@ -28,20 +28,21 @@ def __init__(self): self.shared_vy = SharedValue("vy") self.shared_vz = SharedValue("vz") self.shared_landed_state = SharedValue("landedstate") - self.shared_position = SharedValue("position") - self.shared_velocity = SharedValue("velocity") - self.shared_orientation = SharedValue("orientation") - self.shared_roll = SharedValue("roll") - self.shared_pitch = SharedValue("pitch") - self.shared_yaw = SharedValue("yaw") + self.shared_position = SharedValue("position",3) + self.shared_velocity = SharedValue("velocity",3) + self.shared_orientation = SharedValue("orientation",3) self.shared_yaw_rate = SharedValue("yawrate") + self.shared_CMD = SharedValue("CMD") + self.image = None self.drone = DroneWrapper(name="rqt",ns="/iris/") # Update thread self.thread = ThreadHAL(self.update_hal) + + # Explicit initialization functions # Class method, so user can call it without instantiation @@ -63,11 +64,11 @@ def get_ventral_image(self): def get_position(self): pos = self.drone.get_position() - self.shared_position.add(pos,type_name="list") + self.shared_position.add(pos) def get_velocity(self): vel = self.drone.get_velocity() - self.shared_velocity.add(vel ,type_name="list") + self.shared_velocity.add(vel ) def get_yaw_rate(self): yaw_rate = self.drone.get_yaw_rate() @@ -75,19 +76,7 @@ def get_yaw_rate(self): def get_orientation(self): orientation = self.drone.get_orientation() - self.shared_orientation.add(orientation ,type_name="list") - - def get_roll(self): - roll = self.drone.get_roll() - self.shared_roll.add(roll) - - def get_pitch(self): - pitch = self.drone.get_pitch() - self.shared_pitch.add(pitch) - - def get_yaw(self): - yaw = self.drone.get_yaw() - self.shared_yaw.add(yaw) + self.shared_orientation.add(orientation ) def get_landed_state(self): state = self.drone.get_landed_state() @@ -123,19 +112,26 @@ def land(self): self.drone.land() def update_hal(self): + CMD = self.shared_CMD.get() + self.get_frontal_image() self.get_ventral_image() self.get_position() self.get_velocity() self.get_yaw_rate() self.get_orientation() - self.get_pitch() - self.get_roll() - self.get_yaw() self.get_landed_state() - self.set_cmd_pos() - self.set_cmd_vel() - self.set_cmd_mix() + + if CMD == 0: # POS + self.set_cmd_pos() + elif CMD == 1: # VEL + self.set_cmd_vel() + elif CMD == 2: # MIX + self.set_cmd_mix() + elif CMD == 3: # TAKEOFF + self.takeoff() + elif CMD == 4: # LAND + self.land() # Destructor function to close all fds def __del__(self): @@ -154,9 +150,6 @@ def __del__(self): self.shared_position.close() self.shared_velocity.close() self.shared_orientation.close() - self.shared_roll.close() - self.shared_pitch.close() - self.shared_yaw.close() self.shared_yaw_rate.close() class ThreadHAL(threading.Thread): diff --git a/exercises/static/exercises/follow_turtlebot/web-template/shared/value.py b/exercises/static/exercises/follow_turtlebot/web-template/shared/value.py index 9315148d4..0146309a2 100755 --- a/exercises/static/exercises/follow_turtlebot/web-template/shared/value.py +++ b/exercises/static/exercises/follow_turtlebot/web-template/shared/value.py @@ -5,91 +5,83 @@ import struct class SharedValue: - def __init__(self, name): + def __init__(self, name, n_elem = 1): # Initialize varaibles for memory regions and buffers and Semaphore - self.shm_buf = None; self.shm_region = None - self.value_lock = None + self.n_elem = n_elem + self.shm_buf = [None]*self.n_elem; self.shm_region = [None]*self.n_elem + self.value_lock = [None]*self.n_elem self.shm_name = name; self.value_lock_name = name # Initialize or retreive Semaphore - try: - self.value_lock = Semaphore(self.value_lock_name, O_CREX) - except ExistentialError: - value_lock = Semaphore(self.value_lock_name, O_CREAT) - value_lock.unlink() - self.value_lock = Semaphore(self.value_lock_name, O_CREX) + for i in range(self.n_elem): + try: + self.value_lock[i] = Semaphore(self.value_lock_name+str(i), O_CREX) + except ExistentialError: + value_lock = Semaphore(self.value_lock_name+str(i), O_CREAT) + value_lock.unlink() + self.value_lock[i] = Semaphore(self.value_lock_name+str(i), O_CREX) - self.value_lock.release() + self.value_lock[i].release() # Get the shared value - def get(self, type_name= "value"): + def get(self): # Retreive the data from buffer - if type_name=="value": + + value = [None]*self.n_elem + for i in range(self.n_elem): try: - self.shm_region = SharedMemory(self.shm_name) - self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) - self.shm_region.close_fd() + self.shm_region[i] = SharedMemory(self.shm_name+str(i)) + self.shm_buf[i] = mmap.mmap(self.shm_region[i].fd, sizeof(c_float)) + self.shm_region[i].close_fd() except ExistentialError: - self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=sizeof(c_float)) - self.shm_buf = mmap.mmap(self.shm_region.fd, self.shm_region.size) - self.shm_region.close_fd() - self.value_lock.acquire() - value = struct.unpack('f', self.shm_buf)[0] - self.value_lock.release() + self.shm_region[i] = SharedMemory(self.shm_name+str(i), O_CREAT, size=sizeof(c_float)) + self.shm_buf[i] = mmap.mmap(self.shm_region[i].fd, self.shm_region[i].size) + self.shm_region[i].close_fd() + self.value_lock[i].acquire() + value[i] = struct.unpack('f', self.shm_buf[i])[0] + self.value_lock[i].release() + if self.n_elem <=1: + return value[0] + else: return value - elif type_name=="list": - mock_val_arr = np.array([0.0,0.0,0.0]) - byte_size = mock_val_arr.nbytes - self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=byte_size) - self.shm_buf = mmap.mmap(self.shm_region.fd, byte_size) - self.shm_region.close_fd() - self.value_lock.acquire() - array_val = np.ndarray(shape=(3,), - dtype='float32', buffer=self.shm_buf) - self.value_lock.release() - return array_val - else: - print("missing argument for return type") # Add the shared value - def add(self, value, type_name= "value"): + def add(self, value): # Send the data to shared regions - if type_name=="value": + + for i in range(self.n_elem): try: - self.shm_region = SharedMemory(self.shm_name) - self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) - self.shm_region.close_fd() + self.shm_region[i] = SharedMemory(self.shm_name+str(i)) + self.shm_buf[i] = mmap.mmap(self.shm_region[i].fd, sizeof(c_float)) + self.shm_region[i].close_fd() except ExistentialError: - self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=sizeof(c_float)) - self.shm_buf = mmap.mmap(self.shm_region.fd, self.shm_region.size) - self.shm_region.close_fd() + self.shm_region[i] = SharedMemory(self.shm_name+str(i), O_CREAT, size=sizeof(c_float)) + self.shm_buf[i] = mmap.mmap(self.shm_region[i].fd, self.shm_region[i].size) + self.shm_region[i].close_fd() - self.value_lock.acquire() - self.shm_buf[:] = struct.pack('f', value) - self.value_lock.release() - elif type_name=="list": - byte_size = value.nbytes - self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=byte_size) - self.shm_buf = mmap.mmap(self.shm_region.fd, byte_size) - self.shm_region.close_fd() - self.value_lock.acquire() - self.shm_buf[:] = value.tobytes() - self.value_lock.release() + self.value_lock[i].acquire() + if self.n_elem <=1: + self.shm_buf[i][:] = struct.pack('f', value) + else: + self.shm_buf[i][:] = struct.pack('f', value[i]) + self.value_lock[i].release() + # Destructor function to unlink and disconnect def close(self): - self.value_lock.acquire() - self.shm_buf.close() + for i in range(self.n_elem): + self.value_lock[i].acquire() + self.shm_buf[i].close() - try: - unlink_shared_memory(self.shm_name) - except ExistentialError: - pass + try: + unlink_shared_memory(self.shm_name+str(i)) + except ExistentialError: + pass - self.value_lock.release() - self.value_lock.close() + self.value_lock[i].release() + self.value_lock[i].close() diff --git a/exercises/static/exercises/follow_turtlebot/web-template/user_functions.py b/exercises/static/exercises/follow_turtlebot/web-template/user_functions.py index d741601fc..e595a5be1 100755 --- a/exercises/static/exercises/follow_turtlebot/web-template/user_functions.py +++ b/exercises/static/exercises/follow_turtlebot/web-template/user_functions.py @@ -19,14 +19,13 @@ def __init__(self): self.shared_vy = SharedValue("vy") self.shared_vz = SharedValue("vz") self.shared_landed_state = SharedValue("landedstate") - self.shared_position = SharedValue("position") - self.shared_velocity = SharedValue("velocity") - self.shared_orientation = SharedValue("orientation") - self.shared_roll = SharedValue("roll") - self.shared_pitch = SharedValue("pitch") - self.shared_yaw = SharedValue("yaw") + self.shared_position = SharedValue("position",3) + self.shared_velocity = SharedValue("velocity",3) + self.shared_orientation = SharedValue("orientation",3) self.shared_yaw_rate = SharedValue("yawrate") + self.shared_CMD = SharedValue("CMD") + # Get image function def get_frontal_image(self): @@ -41,58 +40,66 @@ def get_ventral_image(self): def takeoff(self, height): self.shared_takeoff_z.add(height) + self.shared_CMD.add(3) #TAKEOFF + def land(self): - pass + self.shared_CMD.add(4) #LAND def set_cmd_pos(self, x, y , z, az): self.shared_x.add(x) self.shared_y.add(y) self.shared_z.add(z) self.shared_az.add(az) + + self.shared_CMD.add(0) #POS def set_cmd_vel(self, vx, vy, vz, az): self.shared_vx.add(vx) self.shared_vy.add(vy) self.shared_vz.add(vz) self.shared_azt.add(az) + + self.shared_CMD.add(1) #VEL def set_cmd_mix(self, vx, vy, z, az): self.shared_vx.add(vx) self.shared_vy.add(vy) - self.shared_vz.add(z) + self.shared_z.add(z) self.shared_azt.add(az) + + self.shared_CMD.add(2) #MIX def get_position(self): - position = self.shared_position.get(type_name = "list") - return position + position = self.shared_position.get() + return np.array(position) def get_velocity(self): - velocity = self.shared_velocity.get(type_name = "list") - return velocity + velocity = self.shared_velocity.get() + return np.array(velocity) def get_yaw_rate(self): - yaw_rate = self.shared_yaw_rate.get(type_name = "value") + yaw_rate = self.shared_yaw_rate.get() return yaw_rate def get_orientation(self): - orientation = self.shared_orientation.get(type_name = "list") - return orientation + orientation = self.shared_orientation.get() + return np.array(orientation) def get_roll(self): - roll = self.shared_roll.get(type_name = "value") + roll = self.shared_orientation.get()[0] return roll def get_pitch(self): - pitch = self.shared_pitch.get(type_name = "value") + pitch = self.shared_orientation.get()[1] return pitch def get_yaw(self): - yaw = self.shared_yaw.get(type_name = "value") + yaw = self.shared_orientation.get()[2] return yaw def get_landed_state(self): - landed_state = self.shared_landed_state.get(type_name = "value") + landed_state = self.shared_landed_state.get() return landed_state # Define GUI functions diff --git a/exercises/static/exercises/labyrinth_escape/web-template/hal.py b/exercises/static/exercises/labyrinth_escape/web-template/hal.py index 17c39cd6b..09850aeb6 100755 --- a/exercises/static/exercises/labyrinth_escape/web-template/hal.py +++ b/exercises/static/exercises/labyrinth_escape/web-template/hal.py @@ -28,20 +28,21 @@ def __init__(self): self.shared_vy = SharedValue("vy") self.shared_vz = SharedValue("vz") self.shared_landed_state = SharedValue("landedstate") - self.shared_position = SharedValue("position") - self.shared_velocity = SharedValue("velocity") - self.shared_orientation = SharedValue("orientation") - self.shared_roll = SharedValue("roll") - self.shared_pitch = SharedValue("pitch") - self.shared_yaw = SharedValue("yaw") + self.shared_position = SharedValue("position",3) + self.shared_velocity = SharedValue("velocity",3) + self.shared_orientation = SharedValue("orientation",3) self.shared_yaw_rate = SharedValue("yawrate") + self.shared_CMD = SharedValue("CMD") + self.image = None self.drone = DroneWrapper(name="rqt",ns="/iris/") # Update thread self.thread = ThreadHAL(self.update_hal) + + # Explicit initialization functions # Class method, so user can call it without instantiation @@ -63,11 +64,11 @@ def get_ventral_image(self): def get_position(self): pos = self.drone.get_position() - self.shared_position.add(pos,type_name="list") + self.shared_position.add(pos) def get_velocity(self): vel = self.drone.get_velocity() - self.shared_velocity.add(vel ,type_name="list") + self.shared_velocity.add(vel ) def get_yaw_rate(self): yaw_rate = self.drone.get_yaw_rate() @@ -75,19 +76,7 @@ def get_yaw_rate(self): def get_orientation(self): orientation = self.drone.get_orientation() - self.shared_orientation.add(orientation ,type_name="list") - - def get_roll(self): - roll = self.drone.get_roll() - self.shared_roll.add(roll) - - def get_pitch(self): - pitch = self.drone.get_pitch() - self.shared_pitch.add(pitch) - - def get_yaw(self): - yaw = self.drone.get_yaw() - self.shared_yaw.add(yaw) + self.shared_orientation.add(orientation ) def get_landed_state(self): state = self.drone.get_landed_state() @@ -123,19 +112,26 @@ def land(self): self.drone.land() def update_hal(self): + CMD = self.shared_CMD.get() + self.get_frontal_image() self.get_ventral_image() self.get_position() self.get_velocity() self.get_yaw_rate() self.get_orientation() - self.get_pitch() - self.get_roll() - self.get_yaw() self.get_landed_state() - self.set_cmd_pos() - self.set_cmd_vel() - self.set_cmd_mix() + + if CMD == 0: # POS + self.set_cmd_pos() + elif CMD == 1: # VEL + self.set_cmd_vel() + elif CMD == 2: # MIX + self.set_cmd_mix() + elif CMD == 3: # TAKEOFF + self.takeoff() + elif CMD == 4: # LAND + self.land() # Destructor function to close all fds def __del__(self): @@ -154,9 +150,6 @@ def __del__(self): self.shared_position.close() self.shared_velocity.close() self.shared_orientation.close() - self.shared_roll.close() - self.shared_pitch.close() - self.shared_yaw.close() self.shared_yaw_rate.close() class ThreadHAL(threading.Thread): diff --git a/exercises/static/exercises/labyrinth_escape/web-template/shared/value.py b/exercises/static/exercises/labyrinth_escape/web-template/shared/value.py index 9315148d4..0146309a2 100755 --- a/exercises/static/exercises/labyrinth_escape/web-template/shared/value.py +++ b/exercises/static/exercises/labyrinth_escape/web-template/shared/value.py @@ -5,91 +5,83 @@ import struct class SharedValue: - def __init__(self, name): + def __init__(self, name, n_elem = 1): # Initialize varaibles for memory regions and buffers and Semaphore - self.shm_buf = None; self.shm_region = None - self.value_lock = None + self.n_elem = n_elem + self.shm_buf = [None]*self.n_elem; self.shm_region = [None]*self.n_elem + self.value_lock = [None]*self.n_elem self.shm_name = name; self.value_lock_name = name # Initialize or retreive Semaphore - try: - self.value_lock = Semaphore(self.value_lock_name, O_CREX) - except ExistentialError: - value_lock = Semaphore(self.value_lock_name, O_CREAT) - value_lock.unlink() - self.value_lock = Semaphore(self.value_lock_name, O_CREX) + for i in range(self.n_elem): + try: + self.value_lock[i] = Semaphore(self.value_lock_name+str(i), O_CREX) + except ExistentialError: + value_lock = Semaphore(self.value_lock_name+str(i), O_CREAT) + value_lock.unlink() + self.value_lock[i] = Semaphore(self.value_lock_name+str(i), O_CREX) - self.value_lock.release() + self.value_lock[i].release() # Get the shared value - def get(self, type_name= "value"): + def get(self): # Retreive the data from buffer - if type_name=="value": + + value = [None]*self.n_elem + for i in range(self.n_elem): try: - self.shm_region = SharedMemory(self.shm_name) - self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) - self.shm_region.close_fd() + self.shm_region[i] = SharedMemory(self.shm_name+str(i)) + self.shm_buf[i] = mmap.mmap(self.shm_region[i].fd, sizeof(c_float)) + self.shm_region[i].close_fd() except ExistentialError: - self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=sizeof(c_float)) - self.shm_buf = mmap.mmap(self.shm_region.fd, self.shm_region.size) - self.shm_region.close_fd() - self.value_lock.acquire() - value = struct.unpack('f', self.shm_buf)[0] - self.value_lock.release() + self.shm_region[i] = SharedMemory(self.shm_name+str(i), O_CREAT, size=sizeof(c_float)) + self.shm_buf[i] = mmap.mmap(self.shm_region[i].fd, self.shm_region[i].size) + self.shm_region[i].close_fd() + self.value_lock[i].acquire() + value[i] = struct.unpack('f', self.shm_buf[i])[0] + self.value_lock[i].release() + if self.n_elem <=1: + return value[0] + else: return value - elif type_name=="list": - mock_val_arr = np.array([0.0,0.0,0.0]) - byte_size = mock_val_arr.nbytes - self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=byte_size) - self.shm_buf = mmap.mmap(self.shm_region.fd, byte_size) - self.shm_region.close_fd() - self.value_lock.acquire() - array_val = np.ndarray(shape=(3,), - dtype='float32', buffer=self.shm_buf) - self.value_lock.release() - return array_val - else: - print("missing argument for return type") # Add the shared value - def add(self, value, type_name= "value"): + def add(self, value): # Send the data to shared regions - if type_name=="value": + + for i in range(self.n_elem): try: - self.shm_region = SharedMemory(self.shm_name) - self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) - self.shm_region.close_fd() + self.shm_region[i] = SharedMemory(self.shm_name+str(i)) + self.shm_buf[i] = mmap.mmap(self.shm_region[i].fd, sizeof(c_float)) + self.shm_region[i].close_fd() except ExistentialError: - self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=sizeof(c_float)) - self.shm_buf = mmap.mmap(self.shm_region.fd, self.shm_region.size) - self.shm_region.close_fd() + self.shm_region[i] = SharedMemory(self.shm_name+str(i), O_CREAT, size=sizeof(c_float)) + self.shm_buf[i] = mmap.mmap(self.shm_region[i].fd, self.shm_region[i].size) + self.shm_region[i].close_fd() - self.value_lock.acquire() - self.shm_buf[:] = struct.pack('f', value) - self.value_lock.release() - elif type_name=="list": - byte_size = value.nbytes - self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=byte_size) - self.shm_buf = mmap.mmap(self.shm_region.fd, byte_size) - self.shm_region.close_fd() - self.value_lock.acquire() - self.shm_buf[:] = value.tobytes() - self.value_lock.release() + self.value_lock[i].acquire() + if self.n_elem <=1: + self.shm_buf[i][:] = struct.pack('f', value) + else: + self.shm_buf[i][:] = struct.pack('f', value[i]) + self.value_lock[i].release() + # Destructor function to unlink and disconnect def close(self): - self.value_lock.acquire() - self.shm_buf.close() + for i in range(self.n_elem): + self.value_lock[i].acquire() + self.shm_buf[i].close() - try: - unlink_shared_memory(self.shm_name) - except ExistentialError: - pass + try: + unlink_shared_memory(self.shm_name+str(i)) + except ExistentialError: + pass - self.value_lock.release() - self.value_lock.close() + self.value_lock[i].release() + self.value_lock[i].close() diff --git a/exercises/static/exercises/labyrinth_escape/web-template/user_functions.py b/exercises/static/exercises/labyrinth_escape/web-template/user_functions.py index d741601fc..e595a5be1 100755 --- a/exercises/static/exercises/labyrinth_escape/web-template/user_functions.py +++ b/exercises/static/exercises/labyrinth_escape/web-template/user_functions.py @@ -19,14 +19,13 @@ def __init__(self): self.shared_vy = SharedValue("vy") self.shared_vz = SharedValue("vz") self.shared_landed_state = SharedValue("landedstate") - self.shared_position = SharedValue("position") - self.shared_velocity = SharedValue("velocity") - self.shared_orientation = SharedValue("orientation") - self.shared_roll = SharedValue("roll") - self.shared_pitch = SharedValue("pitch") - self.shared_yaw = SharedValue("yaw") + self.shared_position = SharedValue("position",3) + self.shared_velocity = SharedValue("velocity",3) + self.shared_orientation = SharedValue("orientation",3) self.shared_yaw_rate = SharedValue("yawrate") + self.shared_CMD = SharedValue("CMD") + # Get image function def get_frontal_image(self): @@ -41,58 +40,66 @@ def get_ventral_image(self): def takeoff(self, height): self.shared_takeoff_z.add(height) + self.shared_CMD.add(3) #TAKEOFF + def land(self): - pass + self.shared_CMD.add(4) #LAND def set_cmd_pos(self, x, y , z, az): self.shared_x.add(x) self.shared_y.add(y) self.shared_z.add(z) self.shared_az.add(az) + + self.shared_CMD.add(0) #POS def set_cmd_vel(self, vx, vy, vz, az): self.shared_vx.add(vx) self.shared_vy.add(vy) self.shared_vz.add(vz) self.shared_azt.add(az) + + self.shared_CMD.add(1) #VEL def set_cmd_mix(self, vx, vy, z, az): self.shared_vx.add(vx) self.shared_vy.add(vy) - self.shared_vz.add(z) + self.shared_z.add(z) self.shared_azt.add(az) + + self.shared_CMD.add(2) #MIX def get_position(self): - position = self.shared_position.get(type_name = "list") - return position + position = self.shared_position.get() + return np.array(position) def get_velocity(self): - velocity = self.shared_velocity.get(type_name = "list") - return velocity + velocity = self.shared_velocity.get() + return np.array(velocity) def get_yaw_rate(self): - yaw_rate = self.shared_yaw_rate.get(type_name = "value") + yaw_rate = self.shared_yaw_rate.get() return yaw_rate def get_orientation(self): - orientation = self.shared_orientation.get(type_name = "list") - return orientation + orientation = self.shared_orientation.get() + return np.array(orientation) def get_roll(self): - roll = self.shared_roll.get(type_name = "value") + roll = self.shared_orientation.get()[0] return roll def get_pitch(self): - pitch = self.shared_pitch.get(type_name = "value") + pitch = self.shared_orientation.get()[1] return pitch def get_yaw(self): - yaw = self.shared_yaw.get(type_name = "value") + yaw = self.shared_orientation.get()[2] return yaw def get_landed_state(self): - landed_state = self.shared_landed_state.get(type_name = "value") + landed_state = self.shared_landed_state.get() return landed_state # Define GUI functions diff --git a/exercises/static/exercises/package_delivery/web-template/hal.py b/exercises/static/exercises/package_delivery/web-template/hal.py index 62d0b9e42..751be35c8 100755 --- a/exercises/static/exercises/package_delivery/web-template/hal.py +++ b/exercises/static/exercises/package_delivery/web-template/hal.py @@ -3,9 +3,9 @@ import threading import time from datetime import datetime +from shared.magnet import Magnet from drone_wrapper import DroneWrapper -from shared.magnet import Magnet from shared.image import SharedImage from shared.value import SharedValue @@ -29,15 +29,14 @@ def __init__(self): self.shared_vy = SharedValue("vy") self.shared_vz = SharedValue("vz") self.shared_landed_state = SharedValue("landedstate") - self.shared_position = SharedValue("position") - self.shared_velocity = SharedValue("velocity") - self.shared_orientation = SharedValue("orientation") - self.shared_roll = SharedValue("roll") - self.shared_pitch = SharedValue("pitch") - self.shared_yaw = SharedValue("yaw") + self.shared_position = SharedValue("position",3) + self.shared_velocity = SharedValue("velocity",3) + self.shared_orientation = SharedValue("orientation",3) self.shared_yaw_rate = SharedValue("yawrate") self.shared_package_state = SharedValue("packagestate") + self.shared_CMD = SharedValue("CMD") + self.image = None self.drone = DroneWrapper(name="rqt",ns="/iris/") self.magnet = Magnet() @@ -45,6 +44,8 @@ def __init__(self): # Update thread self.thread = ThreadHAL(self.update_hal) + + # Explicit initialization functions # Class method, so user can call it without instantiation @@ -66,11 +67,11 @@ def get_ventral_image(self): def get_position(self): pos = self.drone.get_position() - self.shared_position.add(pos,type_name="list") + self.shared_position.add(pos) def get_velocity(self): vel = self.drone.get_velocity() - self.shared_velocity.add(vel ,type_name="list") + self.shared_velocity.add(vel ) def get_yaw_rate(self): yaw_rate = self.drone.get_yaw_rate() @@ -78,19 +79,7 @@ def get_yaw_rate(self): def get_orientation(self): orientation = self.drone.get_orientation() - self.shared_orientation.add(orientation ,type_name="list") - - def get_roll(self): - roll = self.drone.get_roll() - self.shared_roll.add(roll) - - def get_pitch(self): - pitch = self.drone.get_pitch() - self.shared_pitch.add(pitch) - - def get_yaw(self): - yaw = self.drone.get_yaw() - self.shared_yaw.add(yaw) + self.shared_orientation.add(orientation ) def get_landed_state(self): state = self.drone.get_landed_state() @@ -134,25 +123,33 @@ def set_cmd_drop(self): def get_pkg_state(self): state = self.magnet.get_pkg_state() self.shared_package_state.add(state) - def update_hal(self): + CMD = self.shared_CMD.get() + self.get_frontal_image() self.get_ventral_image() self.get_position() self.get_velocity() self.get_yaw_rate() self.get_orientation() - self.get_pitch() - self.get_roll() - self.get_yaw() self.get_landed_state() - self.set_cmd_pos() - self.set_cmd_vel() - self.set_cmd_mix() - self.set_cmd_pick() - self.set_cmd_drop() self.get_pkg_state() + + if CMD == 0: # POS + self.set_cmd_pos() + elif CMD == 1: # VEL + self.set_cmd_vel() + elif CMD == 2: # MIX + self.set_cmd_mix() + elif CMD == 3: # TAKEOFF + self.takeoff() + elif CMD == 4: # LAND + self.land() + elif CMD == 5: # PKG PICK + self.set_cmd_pick() + elif CMD == 6: # PKG PICK + self.set_cmd_drop() # Destructor function to close all fds def __del__(self): @@ -171,9 +168,6 @@ def __del__(self): self.shared_position.close() self.shared_velocity.close() self.shared_orientation.close() - self.shared_roll.close() - self.shared_pitch.close() - self.shared_yaw.close() self.shared_yaw_rate.close() self.shared_package_state.close() diff --git a/exercises/static/exercises/package_delivery/web-template/shared/value.py b/exercises/static/exercises/package_delivery/web-template/shared/value.py index 9315148d4..0146309a2 100755 --- a/exercises/static/exercises/package_delivery/web-template/shared/value.py +++ b/exercises/static/exercises/package_delivery/web-template/shared/value.py @@ -5,91 +5,83 @@ import struct class SharedValue: - def __init__(self, name): + def __init__(self, name, n_elem = 1): # Initialize varaibles for memory regions and buffers and Semaphore - self.shm_buf = None; self.shm_region = None - self.value_lock = None + self.n_elem = n_elem + self.shm_buf = [None]*self.n_elem; self.shm_region = [None]*self.n_elem + self.value_lock = [None]*self.n_elem self.shm_name = name; self.value_lock_name = name # Initialize or retreive Semaphore - try: - self.value_lock = Semaphore(self.value_lock_name, O_CREX) - except ExistentialError: - value_lock = Semaphore(self.value_lock_name, O_CREAT) - value_lock.unlink() - self.value_lock = Semaphore(self.value_lock_name, O_CREX) + for i in range(self.n_elem): + try: + self.value_lock[i] = Semaphore(self.value_lock_name+str(i), O_CREX) + except ExistentialError: + value_lock = Semaphore(self.value_lock_name+str(i), O_CREAT) + value_lock.unlink() + self.value_lock[i] = Semaphore(self.value_lock_name+str(i), O_CREX) - self.value_lock.release() + self.value_lock[i].release() # Get the shared value - def get(self, type_name= "value"): + def get(self): # Retreive the data from buffer - if type_name=="value": + + value = [None]*self.n_elem + for i in range(self.n_elem): try: - self.shm_region = SharedMemory(self.shm_name) - self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) - self.shm_region.close_fd() + self.shm_region[i] = SharedMemory(self.shm_name+str(i)) + self.shm_buf[i] = mmap.mmap(self.shm_region[i].fd, sizeof(c_float)) + self.shm_region[i].close_fd() except ExistentialError: - self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=sizeof(c_float)) - self.shm_buf = mmap.mmap(self.shm_region.fd, self.shm_region.size) - self.shm_region.close_fd() - self.value_lock.acquire() - value = struct.unpack('f', self.shm_buf)[0] - self.value_lock.release() + self.shm_region[i] = SharedMemory(self.shm_name+str(i), O_CREAT, size=sizeof(c_float)) + self.shm_buf[i] = mmap.mmap(self.shm_region[i].fd, self.shm_region[i].size) + self.shm_region[i].close_fd() + self.value_lock[i].acquire() + value[i] = struct.unpack('f', self.shm_buf[i])[0] + self.value_lock[i].release() + if self.n_elem <=1: + return value[0] + else: return value - elif type_name=="list": - mock_val_arr = np.array([0.0,0.0,0.0]) - byte_size = mock_val_arr.nbytes - self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=byte_size) - self.shm_buf = mmap.mmap(self.shm_region.fd, byte_size) - self.shm_region.close_fd() - self.value_lock.acquire() - array_val = np.ndarray(shape=(3,), - dtype='float32', buffer=self.shm_buf) - self.value_lock.release() - return array_val - else: - print("missing argument for return type") # Add the shared value - def add(self, value, type_name= "value"): + def add(self, value): # Send the data to shared regions - if type_name=="value": + + for i in range(self.n_elem): try: - self.shm_region = SharedMemory(self.shm_name) - self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) - self.shm_region.close_fd() + self.shm_region[i] = SharedMemory(self.shm_name+str(i)) + self.shm_buf[i] = mmap.mmap(self.shm_region[i].fd, sizeof(c_float)) + self.shm_region[i].close_fd() except ExistentialError: - self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=sizeof(c_float)) - self.shm_buf = mmap.mmap(self.shm_region.fd, self.shm_region.size) - self.shm_region.close_fd() + self.shm_region[i] = SharedMemory(self.shm_name+str(i), O_CREAT, size=sizeof(c_float)) + self.shm_buf[i] = mmap.mmap(self.shm_region[i].fd, self.shm_region[i].size) + self.shm_region[i].close_fd() - self.value_lock.acquire() - self.shm_buf[:] = struct.pack('f', value) - self.value_lock.release() - elif type_name=="list": - byte_size = value.nbytes - self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=byte_size) - self.shm_buf = mmap.mmap(self.shm_region.fd, byte_size) - self.shm_region.close_fd() - self.value_lock.acquire() - self.shm_buf[:] = value.tobytes() - self.value_lock.release() + self.value_lock[i].acquire() + if self.n_elem <=1: + self.shm_buf[i][:] = struct.pack('f', value) + else: + self.shm_buf[i][:] = struct.pack('f', value[i]) + self.value_lock[i].release() + # Destructor function to unlink and disconnect def close(self): - self.value_lock.acquire() - self.shm_buf.close() + for i in range(self.n_elem): + self.value_lock[i].acquire() + self.shm_buf[i].close() - try: - unlink_shared_memory(self.shm_name) - except ExistentialError: - pass + try: + unlink_shared_memory(self.shm_name+str(i)) + except ExistentialError: + pass - self.value_lock.release() - self.value_lock.close() + self.value_lock[i].release() + self.value_lock[i].close() diff --git a/exercises/static/exercises/package_delivery/web-template/user_functions.py b/exercises/static/exercises/package_delivery/web-template/user_functions.py index 185690838..edb88628e 100755 --- a/exercises/static/exercises/package_delivery/web-template/user_functions.py +++ b/exercises/static/exercises/package_delivery/web-template/user_functions.py @@ -19,16 +19,16 @@ def __init__(self): self.shared_vy = SharedValue("vy") self.shared_vz = SharedValue("vz") self.shared_landed_state = SharedValue("landedstate") - self.shared_position = SharedValue("position") - self.shared_velocity = SharedValue("velocity") - self.shared_orientation = SharedValue("orientation") - self.shared_roll = SharedValue("roll") - self.shared_pitch = SharedValue("pitch") - self.shared_yaw = SharedValue("yaw") + self.shared_position = SharedValue("position",3) + self.shared_velocity = SharedValue("velocity",3) + self.shared_orientation = SharedValue("orientation",3) self.shared_yaw_rate = SharedValue("yawrate") self.shared_package_state = SharedValue("packagestate") + self.shared_CMD = SharedValue("CMD") + + # Get image function def get_frontal_image(self): image = self.shared_frontal_image.get() @@ -42,68 +42,76 @@ def get_ventral_image(self): def takeoff(self, height): self.shared_takeoff_z.add(height) + self.shared_CMD.add(3) #TAKEOFF + def land(self): - pass + self.shared_CMD.add(4) #LAND def set_cmd_pos(self, x, y , z, az): self.shared_x.add(x) self.shared_y.add(y) self.shared_z.add(z) self.shared_az.add(az) + + self.shared_CMD.add(0) #POS def set_cmd_vel(self, vx, vy, vz, az): self.shared_vx.add(vx) self.shared_vy.add(vy) self.shared_vz.add(vz) self.shared_azt.add(az) + + self.shared_CMD.add(1) #VEL def set_cmd_mix(self, vx, vy, z, az): self.shared_vx.add(vx) self.shared_vy.add(vy) - self.shared_vz.add(z) + self.shared_z.add(z) self.shared_azt.add(az) + + self.shared_CMD.add(2) #MIX def get_position(self): - position = self.shared_position.get(type_name = "list") - return position + position = self.shared_position.get() + return np.array(position) def get_velocity(self): - velocity = self.shared_velocity.get(type_name = "list") - return velocity + velocity = self.shared_velocity.get() + return np.array(velocity) def get_yaw_rate(self): - yaw_rate = self.shared_yaw_rate.get(type_name = "value") + yaw_rate = self.shared_yaw_rate.get() return yaw_rate def get_orientation(self): - orientation = self.shared_orientation.get(type_name = "list") - return orientation + orientation = self.shared_orientation.get() + return np.array(orientation) def get_roll(self): - roll = self.shared_roll.get(type_name = "value") + roll = self.shared_orientation.get()[0] return roll def get_pitch(self): - pitch = self.shared_pitch.get(type_name = "value") + pitch = self.shared_orientation.get()[1] return pitch def get_yaw(self): - yaw = self.shared_yaw.get(type_name = "value") + yaw = self.shared_orientation.get()[2] return yaw def get_landed_state(self): - landed_state = self.shared_landed_state.get(type_name = "value") + landed_state = self.shared_landed_state.get() return landed_state - + def set_cmd_pick(self): - pass + self.shared_CMD.add(5) #PKG PICK def set_cmd_drop(self): - pass + self.shared_CMD.add(6) #PKG DROP def get_pkg_state(self): - package_state = self.shared_package_state.get(type_name = "value") + package_state = self.shared_package_state.get() return package_state # Define GUI functions diff --git a/exercises/static/exercises/position_control/web-template/hal.py b/exercises/static/exercises/position_control/web-template/hal.py index 548978fb4..46465cdaf 100755 --- a/exercises/static/exercises/position_control/web-template/hal.py +++ b/exercises/static/exercises/position_control/web-template/hal.py @@ -3,10 +3,9 @@ import threading import time from datetime import datetime -import numpy as np +from shared.Beacon import Beacon from drone_wrapper import DroneWrapper -from shared.Beacon import Beacon from shared.image import SharedImage from shared.value import SharedValue @@ -30,14 +29,15 @@ def __init__(self): self.shared_vy = SharedValue("vy") self.shared_vz = SharedValue("vz") self.shared_landed_state = SharedValue("landedstate") - self.shared_position = SharedValue("position") - self.shared_velocity = SharedValue("velocity") - self.shared_orientation = SharedValue("orientation") - self.shared_roll = SharedValue("roll") - self.shared_pitch = SharedValue("pitch") - self.shared_yaw = SharedValue("yaw") + self.shared_position = SharedValue("position",3) + self.shared_velocity = SharedValue("velocity",3) + self.shared_orientation = SharedValue("orientation",3) self.shared_yaw_rate = SharedValue("yawrate") - self.shared_beacons = SharedValue("beacons") + self.shared_beacons = SharedValue("beacons", 6) + self.shared_beacon = SharedValue("beacon") + + + self.shared_CMD = SharedValue("CMD") self.image = None self.drone = DroneWrapper(name="rqt",ns="/iris/") @@ -45,6 +45,8 @@ def __init__(self): # Update thread self.thread = ThreadHAL(self.update_hal) + + # Explicit initialization functions # Class method, so user can call it without instantiation @@ -66,11 +68,11 @@ def get_ventral_image(self): def get_position(self): pos = self.drone.get_position() - self.shared_position.add(pos,type_name="list") + self.shared_position.add(pos) def get_velocity(self): vel = self.drone.get_velocity() - self.shared_velocity.add(vel ,type_name="list") + self.shared_velocity.add(vel ) def get_yaw_rate(self): yaw_rate = self.drone.get_yaw_rate() @@ -78,19 +80,7 @@ def get_yaw_rate(self): def get_orientation(self): orientation = self.drone.get_orientation() - self.shared_orientation.add(orientation ,type_name="list") - - def get_roll(self): - roll = self.drone.get_roll() - self.shared_roll.add(roll) - - def get_pitch(self): - pitch = self.drone.get_pitch() - self.shared_pitch.add(pitch) - - def get_yaw(self): - yaw = self.drone.get_yaw() - self.shared_yaw.add(yaw) + self.shared_orientation.add(orientation ) def get_landed_state(self): state = self.drone.get_landed_state() @@ -124,33 +114,43 @@ def takeoff(self): def land(self): self.drone.land() - + def init_beacons(self): - self.beacons = self.shared_beacons.get(type_name = "list", n_elem = 6) + self.beacons = self.shared_beacons.get() def get_next_beacon(self): for beacon in self.beacons: if beacon.is_reached() == False: - self.shared_beacons.add(np.array([beacon]) ,type_name="list") - + self.shared_beacon.add(beacon) def update_hal(self): + CMD = self.shared_CMD.get() + self.get_frontal_image() self.get_ventral_image() self.get_position() self.get_velocity() self.get_yaw_rate() self.get_orientation() - self.get_pitch() - self.get_roll() - self.get_yaw() self.get_landed_state() - self.set_cmd_pos() - self.set_cmd_vel() - self.set_cmd_mix() - self.init_beacons() - self.get_next_beacon() - + + + + if CMD == 0: # POS + self.set_cmd_pos() + elif CMD == 1: # VEL + self.set_cmd_vel() + elif CMD == 2: # MIX + self.set_cmd_mix() + elif CMD == 3: # TAKEOFF + self.takeoff() + elif CMD == 4: # LAND + self.land() + elif CMD == 5: # INIT BEACON + self.init_beacons() + elif CMD == 6: # NEXT BEACON + self.get_next_beacon() + # Destructor function to close all fds def __del__(self): @@ -169,11 +169,9 @@ def __del__(self): self.shared_position.close() self.shared_velocity.close() self.shared_orientation.close() - self.shared_roll.close() - self.shared_pitch.close() - self.shared_yaw.close() self.shared_yaw_rate.close() self.shared_beacons.close() + self.shared_beacon.close() class ThreadHAL(threading.Thread): def __init__(self, update_function): diff --git a/exercises/static/exercises/position_control/web-template/shared/value.py b/exercises/static/exercises/position_control/web-template/shared/value.py index 4b902fd10..0146309a2 100755 --- a/exercises/static/exercises/position_control/web-template/shared/value.py +++ b/exercises/static/exercises/position_control/web-template/shared/value.py @@ -5,103 +5,83 @@ import struct class SharedValue: - def __init__(self, name): + def __init__(self, name, n_elem = 1): # Initialize varaibles for memory regions and buffers and Semaphore - self.shm_buf = None; self.shm_region = None - self.value_lock = None + self.n_elem = n_elem + self.shm_buf = [None]*self.n_elem; self.shm_region = [None]*self.n_elem + self.value_lock = [None]*self.n_elem self.shm_name = name; self.value_lock_name = name # Initialize or retreive Semaphore - try: - self.value_lock = Semaphore(self.value_lock_name, O_CREX) - except ExistentialError: - value_lock = Semaphore(self.value_lock_name, O_CREAT) - value_lock.unlink() - self.value_lock = Semaphore(self.value_lock_name, O_CREX) + for i in range(self.n_elem): + try: + self.value_lock[i] = Semaphore(self.value_lock_name+str(i), O_CREX) + except ExistentialError: + value_lock = Semaphore(self.value_lock_name+str(i), O_CREAT) + value_lock.unlink() + self.value_lock[i] = Semaphore(self.value_lock_name+str(i), O_CREX) - self.value_lock.release() + self.value_lock[i].release() # Get the shared value - def get(self, type_name= "value", n_elem = 3): + def get(self): # Retreive the data from buffer - if type_name=="value": + + value = [None]*self.n_elem + for i in range(self.n_elem): try: - self.shm_region = SharedMemory(self.shm_name) - self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) - self.shm_region.close_fd() + self.shm_region[i] = SharedMemory(self.shm_name+str(i)) + self.shm_buf[i] = mmap.mmap(self.shm_region[i].fd, sizeof(c_float)) + self.shm_region[i].close_fd() except ExistentialError: - self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=sizeof(c_float)) - self.shm_buf = mmap.mmap(self.shm_region.fd, self.shm_region.size) - self.shm_region.close_fd() - self.value_lock.acquire() - value = struct.unpack('f', self.shm_buf)[0] - self.value_lock.release() + self.shm_region[i] = SharedMemory(self.shm_name+str(i), O_CREAT, size=sizeof(c_float)) + self.shm_buf[i] = mmap.mmap(self.shm_region[i].fd, self.shm_region[i].size) + self.shm_region[i].close_fd() + self.value_lock[i].acquire() + value[i] = struct.unpack('f', self.shm_buf[i])[0] + self.value_lock[i].release() + if self.n_elem <=1: + return value[0] + else: return value - elif type_name=="list": - if n_elem == 1 : - mock_val_arr = np.array([0.0]) - elif n_elem == 3: - mock_val_arr = np.array([0.0, 0.0, 0.0]) - elif n_elem == 6: - mock_val_arr = np.array([0.0, 0.0, 0.0, 0.0, 0.0, 0.0]) - byte_size = mock_val_arr.nbytes - self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=byte_size) - self.shm_buf = mmap.mmap(self.shm_region.fd, byte_size) - self.shm_region.close_fd() - self.value_lock.acquire() - if n_elem == 1 : - array_val = np.ndarray(shape=(1,), - dtype = 'O', buffer=self.shm_buf) - elif n_elem == 3: - array_val = np.ndarray(shape=(3,), - dtype='float32', buffer=self.shm_buf) - elif n_elem == 6: - array_val = np.ndarray(shape=(6,), - dtype = 'O', buffer=self.shm_buf) - self.value_lock.release() - return array_val - else: - print("missing argument for return type") # Add the shared value - def add(self, value, type_name= "value"): + def add(self, value): # Send the data to shared regions - if type_name=="value": + + for i in range(self.n_elem): try: - self.shm_region = SharedMemory(self.shm_name) - self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) - self.shm_region.close_fd() + self.shm_region[i] = SharedMemory(self.shm_name+str(i)) + self.shm_buf[i] = mmap.mmap(self.shm_region[i].fd, sizeof(c_float)) + self.shm_region[i].close_fd() except ExistentialError: - self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=sizeof(c_float)) - self.shm_buf = mmap.mmap(self.shm_region.fd, self.shm_region.size) - self.shm_region.close_fd() + self.shm_region[i] = SharedMemory(self.shm_name+str(i), O_CREAT, size=sizeof(c_float)) + self.shm_buf[i] = mmap.mmap(self.shm_region[i].fd, self.shm_region[i].size) + self.shm_region[i].close_fd() - self.value_lock.acquire() - self.shm_buf[:] = struct.pack('f', value) - self.value_lock.release() - elif type_name=="list": - byte_size = value.nbytes - self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=byte_size) - self.shm_buf = mmap.mmap(self.shm_region.fd, byte_size) - self.shm_region.close_fd() - self.value_lock.acquire() - self.shm_buf[:] = value.tobytes() - self.value_lock.release() + self.value_lock[i].acquire() + if self.n_elem <=1: + self.shm_buf[i][:] = struct.pack('f', value) + else: + self.shm_buf[i][:] = struct.pack('f', value[i]) + self.value_lock[i].release() + # Destructor function to unlink and disconnect def close(self): - self.value_lock.acquire() - self.shm_buf.close() + for i in range(self.n_elem): + self.value_lock[i].acquire() + self.shm_buf[i].close() - try: - unlink_shared_memory(self.shm_name) - except ExistentialError: - pass + try: + unlink_shared_memory(self.shm_name+str(i)) + except ExistentialError: + pass - self.value_lock.release() - self.value_lock.close() + self.value_lock[i].release() + self.value_lock[i].close() diff --git a/exercises/static/exercises/position_control/web-template/user_functions.py b/exercises/static/exercises/position_control/web-template/user_functions.py index d0464d60e..3b3ac58cd 100755 --- a/exercises/static/exercises/position_control/web-template/user_functions.py +++ b/exercises/static/exercises/position_control/web-template/user_functions.py @@ -20,14 +20,14 @@ def __init__(self): self.shared_vy = SharedValue("vy") self.shared_vz = SharedValue("vz") self.shared_landed_state = SharedValue("landedstate") - self.shared_position = SharedValue("position") - self.shared_velocity = SharedValue("velocity") - self.shared_orientation = SharedValue("orientation") - self.shared_roll = SharedValue("roll") - self.shared_pitch = SharedValue("pitch") - self.shared_yaw = SharedValue("yaw") + self.shared_position = SharedValue("position",3) + self.shared_velocity = SharedValue("velocity",3) + self.shared_orientation = SharedValue("orientation",3) self.shared_yaw_rate = SharedValue("yawrate") - self.shared_beacons = SharedValue("beacons") + self.shared_beacons = SharedValue("beacons", 6) + self.shared_beacon = SharedValue("beacon") + + self.shared_CMD = SharedValue("CMD") # Get image function @@ -43,60 +43,68 @@ def get_ventral_image(self): def takeoff(self, height): self.shared_takeoff_z.add(height) + self.shared_CMD.add(3) #TAKEOFF + def land(self): - pass + self.shared_CMD.add(4) #LAND def set_cmd_pos(self, x, y , z, az): self.shared_x.add(x) self.shared_y.add(y) self.shared_z.add(z) self.shared_az.add(az) + + self.shared_CMD.add(0) #POS def set_cmd_vel(self, vx, vy, vz, az): self.shared_vx.add(vx) self.shared_vy.add(vy) self.shared_vz.add(vz) self.shared_azt.add(az) + + self.shared_CMD.add(1) #VEL def set_cmd_mix(self, vx, vy, z, az): self.shared_vx.add(vx) self.shared_vy.add(vy) - self.shared_vz.add(z) + self.shared_z.add(z) self.shared_azt.add(az) + + self.shared_CMD.add(2) #MIX def get_position(self): - position = self.shared_position.get(type_name = "list") - return position + position = self.shared_position.get() + return np.array(position) def get_velocity(self): - velocity = self.shared_velocity.get(type_name = "list") - return velocity + velocity = self.shared_velocity.get() + return np.array(velocity) def get_yaw_rate(self): - yaw_rate = self.shared_yaw_rate.get(type_name = "value") + yaw_rate = self.shared_yaw_rate.get() return yaw_rate def get_orientation(self): - orientation = self.shared_orientation.get(type_name = "list") - return orientation + orientation = self.shared_orientation.get() + return np.array(orientation) def get_roll(self): - roll = self.shared_roll.get(type_name = "value") + roll = self.shared_orientation.get()[0] return roll def get_pitch(self): - pitch = self.shared_pitch.get(type_name = "value") + pitch = self.shared_orientation.get()[1] return pitch def get_yaw(self): - yaw = self.shared_yaw.get(type_name = "value") + yaw = self.shared_orientation.get()[2] return yaw def get_landed_state(self): - landed_state = self.shared_landed_state.get(type_name = "value") + landed_state = self.shared_landed_state.get() return landed_state - + def init_beacons(self): self.beacons = [] self.beacons.append(Beacon('beacon1', np.array([0, 5, 0]), False, False)) @@ -105,11 +113,13 @@ def init_beacons(self): self.beacons.append(Beacon('beacon4', np.array([-5, 0, 0]), False, False)) self.beacons.append(Beacon('beacon5', np.array([10, 0, 0]), False, False)) self.beacons.append(Beacon('initial', np.array([0, 0, 0]), False, False)) - self.shared_beacons.add(self.beacons ,type_name="list") + self.shared_beacons.add(self.beacons) + self.shared_CMD.add(5) # INIT BEACON def get_next_beacon(self): - beacon = self.shared_beacons.get(type_name = "list", n_elem = 1) - return beacon[0] + beacon = self.shared_beacon.get() + self.shared_CMD.add(6) # NEXT BEACON + return beacon # Define GUI functions class GUIFunctions: diff --git a/exercises/static/exercises/power_tower_inspection/web-template/hal.py b/exercises/static/exercises/power_tower_inspection/web-template/hal.py index 17c39cd6b..09850aeb6 100755 --- a/exercises/static/exercises/power_tower_inspection/web-template/hal.py +++ b/exercises/static/exercises/power_tower_inspection/web-template/hal.py @@ -28,20 +28,21 @@ def __init__(self): self.shared_vy = SharedValue("vy") self.shared_vz = SharedValue("vz") self.shared_landed_state = SharedValue("landedstate") - self.shared_position = SharedValue("position") - self.shared_velocity = SharedValue("velocity") - self.shared_orientation = SharedValue("orientation") - self.shared_roll = SharedValue("roll") - self.shared_pitch = SharedValue("pitch") - self.shared_yaw = SharedValue("yaw") + self.shared_position = SharedValue("position",3) + self.shared_velocity = SharedValue("velocity",3) + self.shared_orientation = SharedValue("orientation",3) self.shared_yaw_rate = SharedValue("yawrate") + self.shared_CMD = SharedValue("CMD") + self.image = None self.drone = DroneWrapper(name="rqt",ns="/iris/") # Update thread self.thread = ThreadHAL(self.update_hal) + + # Explicit initialization functions # Class method, so user can call it without instantiation @@ -63,11 +64,11 @@ def get_ventral_image(self): def get_position(self): pos = self.drone.get_position() - self.shared_position.add(pos,type_name="list") + self.shared_position.add(pos) def get_velocity(self): vel = self.drone.get_velocity() - self.shared_velocity.add(vel ,type_name="list") + self.shared_velocity.add(vel ) def get_yaw_rate(self): yaw_rate = self.drone.get_yaw_rate() @@ -75,19 +76,7 @@ def get_yaw_rate(self): def get_orientation(self): orientation = self.drone.get_orientation() - self.shared_orientation.add(orientation ,type_name="list") - - def get_roll(self): - roll = self.drone.get_roll() - self.shared_roll.add(roll) - - def get_pitch(self): - pitch = self.drone.get_pitch() - self.shared_pitch.add(pitch) - - def get_yaw(self): - yaw = self.drone.get_yaw() - self.shared_yaw.add(yaw) + self.shared_orientation.add(orientation ) def get_landed_state(self): state = self.drone.get_landed_state() @@ -123,19 +112,26 @@ def land(self): self.drone.land() def update_hal(self): + CMD = self.shared_CMD.get() + self.get_frontal_image() self.get_ventral_image() self.get_position() self.get_velocity() self.get_yaw_rate() self.get_orientation() - self.get_pitch() - self.get_roll() - self.get_yaw() self.get_landed_state() - self.set_cmd_pos() - self.set_cmd_vel() - self.set_cmd_mix() + + if CMD == 0: # POS + self.set_cmd_pos() + elif CMD == 1: # VEL + self.set_cmd_vel() + elif CMD == 2: # MIX + self.set_cmd_mix() + elif CMD == 3: # TAKEOFF + self.takeoff() + elif CMD == 4: # LAND + self.land() # Destructor function to close all fds def __del__(self): @@ -154,9 +150,6 @@ def __del__(self): self.shared_position.close() self.shared_velocity.close() self.shared_orientation.close() - self.shared_roll.close() - self.shared_pitch.close() - self.shared_yaw.close() self.shared_yaw_rate.close() class ThreadHAL(threading.Thread): diff --git a/exercises/static/exercises/power_tower_inspection/web-template/shared/value.py b/exercises/static/exercises/power_tower_inspection/web-template/shared/value.py index 9315148d4..0146309a2 100755 --- a/exercises/static/exercises/power_tower_inspection/web-template/shared/value.py +++ b/exercises/static/exercises/power_tower_inspection/web-template/shared/value.py @@ -5,91 +5,83 @@ import struct class SharedValue: - def __init__(self, name): + def __init__(self, name, n_elem = 1): # Initialize varaibles for memory regions and buffers and Semaphore - self.shm_buf = None; self.shm_region = None - self.value_lock = None + self.n_elem = n_elem + self.shm_buf = [None]*self.n_elem; self.shm_region = [None]*self.n_elem + self.value_lock = [None]*self.n_elem self.shm_name = name; self.value_lock_name = name # Initialize or retreive Semaphore - try: - self.value_lock = Semaphore(self.value_lock_name, O_CREX) - except ExistentialError: - value_lock = Semaphore(self.value_lock_name, O_CREAT) - value_lock.unlink() - self.value_lock = Semaphore(self.value_lock_name, O_CREX) + for i in range(self.n_elem): + try: + self.value_lock[i] = Semaphore(self.value_lock_name+str(i), O_CREX) + except ExistentialError: + value_lock = Semaphore(self.value_lock_name+str(i), O_CREAT) + value_lock.unlink() + self.value_lock[i] = Semaphore(self.value_lock_name+str(i), O_CREX) - self.value_lock.release() + self.value_lock[i].release() # Get the shared value - def get(self, type_name= "value"): + def get(self): # Retreive the data from buffer - if type_name=="value": + + value = [None]*self.n_elem + for i in range(self.n_elem): try: - self.shm_region = SharedMemory(self.shm_name) - self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) - self.shm_region.close_fd() + self.shm_region[i] = SharedMemory(self.shm_name+str(i)) + self.shm_buf[i] = mmap.mmap(self.shm_region[i].fd, sizeof(c_float)) + self.shm_region[i].close_fd() except ExistentialError: - self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=sizeof(c_float)) - self.shm_buf = mmap.mmap(self.shm_region.fd, self.shm_region.size) - self.shm_region.close_fd() - self.value_lock.acquire() - value = struct.unpack('f', self.shm_buf)[0] - self.value_lock.release() + self.shm_region[i] = SharedMemory(self.shm_name+str(i), O_CREAT, size=sizeof(c_float)) + self.shm_buf[i] = mmap.mmap(self.shm_region[i].fd, self.shm_region[i].size) + self.shm_region[i].close_fd() + self.value_lock[i].acquire() + value[i] = struct.unpack('f', self.shm_buf[i])[0] + self.value_lock[i].release() + if self.n_elem <=1: + return value[0] + else: return value - elif type_name=="list": - mock_val_arr = np.array([0.0,0.0,0.0]) - byte_size = mock_val_arr.nbytes - self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=byte_size) - self.shm_buf = mmap.mmap(self.shm_region.fd, byte_size) - self.shm_region.close_fd() - self.value_lock.acquire() - array_val = np.ndarray(shape=(3,), - dtype='float32', buffer=self.shm_buf) - self.value_lock.release() - return array_val - else: - print("missing argument for return type") # Add the shared value - def add(self, value, type_name= "value"): + def add(self, value): # Send the data to shared regions - if type_name=="value": + + for i in range(self.n_elem): try: - self.shm_region = SharedMemory(self.shm_name) - self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) - self.shm_region.close_fd() + self.shm_region[i] = SharedMemory(self.shm_name+str(i)) + self.shm_buf[i] = mmap.mmap(self.shm_region[i].fd, sizeof(c_float)) + self.shm_region[i].close_fd() except ExistentialError: - self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=sizeof(c_float)) - self.shm_buf = mmap.mmap(self.shm_region.fd, self.shm_region.size) - self.shm_region.close_fd() + self.shm_region[i] = SharedMemory(self.shm_name+str(i), O_CREAT, size=sizeof(c_float)) + self.shm_buf[i] = mmap.mmap(self.shm_region[i].fd, self.shm_region[i].size) + self.shm_region[i].close_fd() - self.value_lock.acquire() - self.shm_buf[:] = struct.pack('f', value) - self.value_lock.release() - elif type_name=="list": - byte_size = value.nbytes - self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=byte_size) - self.shm_buf = mmap.mmap(self.shm_region.fd, byte_size) - self.shm_region.close_fd() - self.value_lock.acquire() - self.shm_buf[:] = value.tobytes() - self.value_lock.release() + self.value_lock[i].acquire() + if self.n_elem <=1: + self.shm_buf[i][:] = struct.pack('f', value) + else: + self.shm_buf[i][:] = struct.pack('f', value[i]) + self.value_lock[i].release() + # Destructor function to unlink and disconnect def close(self): - self.value_lock.acquire() - self.shm_buf.close() + for i in range(self.n_elem): + self.value_lock[i].acquire() + self.shm_buf[i].close() - try: - unlink_shared_memory(self.shm_name) - except ExistentialError: - pass + try: + unlink_shared_memory(self.shm_name+str(i)) + except ExistentialError: + pass - self.value_lock.release() - self.value_lock.close() + self.value_lock[i].release() + self.value_lock[i].close() diff --git a/exercises/static/exercises/power_tower_inspection/web-template/user_functions.py b/exercises/static/exercises/power_tower_inspection/web-template/user_functions.py index d741601fc..e595a5be1 100755 --- a/exercises/static/exercises/power_tower_inspection/web-template/user_functions.py +++ b/exercises/static/exercises/power_tower_inspection/web-template/user_functions.py @@ -19,14 +19,13 @@ def __init__(self): self.shared_vy = SharedValue("vy") self.shared_vz = SharedValue("vz") self.shared_landed_state = SharedValue("landedstate") - self.shared_position = SharedValue("position") - self.shared_velocity = SharedValue("velocity") - self.shared_orientation = SharedValue("orientation") - self.shared_roll = SharedValue("roll") - self.shared_pitch = SharedValue("pitch") - self.shared_yaw = SharedValue("yaw") + self.shared_position = SharedValue("position",3) + self.shared_velocity = SharedValue("velocity",3) + self.shared_orientation = SharedValue("orientation",3) self.shared_yaw_rate = SharedValue("yawrate") + self.shared_CMD = SharedValue("CMD") + # Get image function def get_frontal_image(self): @@ -41,58 +40,66 @@ def get_ventral_image(self): def takeoff(self, height): self.shared_takeoff_z.add(height) + self.shared_CMD.add(3) #TAKEOFF + def land(self): - pass + self.shared_CMD.add(4) #LAND def set_cmd_pos(self, x, y , z, az): self.shared_x.add(x) self.shared_y.add(y) self.shared_z.add(z) self.shared_az.add(az) + + self.shared_CMD.add(0) #POS def set_cmd_vel(self, vx, vy, vz, az): self.shared_vx.add(vx) self.shared_vy.add(vy) self.shared_vz.add(vz) self.shared_azt.add(az) + + self.shared_CMD.add(1) #VEL def set_cmd_mix(self, vx, vy, z, az): self.shared_vx.add(vx) self.shared_vy.add(vy) - self.shared_vz.add(z) + self.shared_z.add(z) self.shared_azt.add(az) + + self.shared_CMD.add(2) #MIX def get_position(self): - position = self.shared_position.get(type_name = "list") - return position + position = self.shared_position.get() + return np.array(position) def get_velocity(self): - velocity = self.shared_velocity.get(type_name = "list") - return velocity + velocity = self.shared_velocity.get() + return np.array(velocity) def get_yaw_rate(self): - yaw_rate = self.shared_yaw_rate.get(type_name = "value") + yaw_rate = self.shared_yaw_rate.get() return yaw_rate def get_orientation(self): - orientation = self.shared_orientation.get(type_name = "list") - return orientation + orientation = self.shared_orientation.get() + return np.array(orientation) def get_roll(self): - roll = self.shared_roll.get(type_name = "value") + roll = self.shared_orientation.get()[0] return roll def get_pitch(self): - pitch = self.shared_pitch.get(type_name = "value") + pitch = self.shared_orientation.get()[1] return pitch def get_yaw(self): - yaw = self.shared_yaw.get(type_name = "value") + yaw = self.shared_orientation.get()[2] return yaw def get_landed_state(self): - landed_state = self.shared_landed_state.get(type_name = "value") + landed_state = self.shared_landed_state.get() return landed_state # Define GUI functions diff --git a/exercises/static/exercises/rescue_people/web-template/hal.py b/exercises/static/exercises/rescue_people/web-template/hal.py index a0cf18640..252673552 100755 --- a/exercises/static/exercises/rescue_people/web-template/hal.py +++ b/exercises/static/exercises/rescue_people/web-template/hal.py @@ -3,9 +3,9 @@ import threading import time from datetime import datetime +from shared.light import Light from drone_wrapper import DroneWrapper -from shared.light import Light from shared.image import SharedImage from shared.value import SharedValue @@ -29,21 +29,23 @@ def __init__(self): self.shared_vy = SharedValue("vy") self.shared_vz = SharedValue("vz") self.shared_landed_state = SharedValue("landedstate") - self.shared_position = SharedValue("position") - self.shared_velocity = SharedValue("velocity") - self.shared_orientation = SharedValue("orientation") - self.shared_roll = SharedValue("roll") - self.shared_pitch = SharedValue("pitch") - self.shared_yaw = SharedValue("yaw") + self.shared_position = SharedValue("position",3) + self.shared_velocity = SharedValue("velocity",3) + self.shared_orientation = SharedValue("orientation",3) self.shared_yaw_rate = SharedValue("yawrate") + self.shared_CMD = SharedValue("CMD") + self.image = None self.drone = DroneWrapper(name="rqt",ns="/iris/") self.light = Light() + # Update thread self.thread = ThreadHAL(self.update_hal) + + # Explicit initialization functions # Class method, so user can call it without instantiation @@ -65,11 +67,11 @@ def get_ventral_image(self): def get_position(self): pos = self.drone.get_position() - self.shared_position.add(pos,type_name="list") + self.shared_position.add(pos) def get_velocity(self): vel = self.drone.get_velocity() - self.shared_velocity.add(vel ,type_name="list") + self.shared_velocity.add(vel ) def get_yaw_rate(self): yaw_rate = self.drone.get_yaw_rate() @@ -77,19 +79,7 @@ def get_yaw_rate(self): def get_orientation(self): orientation = self.drone.get_orientation() - self.shared_orientation.add(orientation ,type_name="list") - - def get_roll(self): - roll = self.drone.get_roll() - self.shared_roll.add(roll) - - def get_pitch(self): - pitch = self.drone.get_pitch() - self.shared_pitch.add(pitch) - - def get_yaw(self): - yaw = self.drone.get_yaw() - self.shared_yaw.add(yaw) + self.shared_orientation.add(orientation ) def get_landed_state(self): state = self.drone.get_landed_state() @@ -131,21 +121,30 @@ def set_cmd_off(self): self.light.set_cmd_off() def update_hal(self): + CMD = self.shared_CMD.get() + self.get_frontal_image() self.get_ventral_image() self.get_position() self.get_velocity() self.get_yaw_rate() self.get_orientation() - self.get_pitch() - self.get_roll() - self.get_yaw() self.get_landed_state() - self.set_cmd_pos() - self.set_cmd_vel() - self.set_cmd_mix() - self.set_cmd_on() - self.set_cmd_off() + + if CMD == 0: # POS + self.set_cmd_pos() + elif CMD == 1: # VEL + self.set_cmd_vel() + elif CMD == 2: # MIX + self.set_cmd_mix() + elif CMD == 3: # TAKEOFF + self.takeoff() + elif CMD == 4: # LAND + self.land() + elif CMD == 5: # LIGHT ON + self.set_cmd_on() + elif CMD == 6: # LIGHT OFF + self.set_cmd_off() # Destructor function to close all fds def __del__(self): @@ -164,9 +163,6 @@ def __del__(self): self.shared_position.close() self.shared_velocity.close() self.shared_orientation.close() - self.shared_roll.close() - self.shared_pitch.close() - self.shared_yaw.close() self.shared_yaw_rate.close() class ThreadHAL(threading.Thread): diff --git a/exercises/static/exercises/rescue_people/web-template/shared/value.py b/exercises/static/exercises/rescue_people/web-template/shared/value.py index 9315148d4..0146309a2 100755 --- a/exercises/static/exercises/rescue_people/web-template/shared/value.py +++ b/exercises/static/exercises/rescue_people/web-template/shared/value.py @@ -5,91 +5,83 @@ import struct class SharedValue: - def __init__(self, name): + def __init__(self, name, n_elem = 1): # Initialize varaibles for memory regions and buffers and Semaphore - self.shm_buf = None; self.shm_region = None - self.value_lock = None + self.n_elem = n_elem + self.shm_buf = [None]*self.n_elem; self.shm_region = [None]*self.n_elem + self.value_lock = [None]*self.n_elem self.shm_name = name; self.value_lock_name = name # Initialize or retreive Semaphore - try: - self.value_lock = Semaphore(self.value_lock_name, O_CREX) - except ExistentialError: - value_lock = Semaphore(self.value_lock_name, O_CREAT) - value_lock.unlink() - self.value_lock = Semaphore(self.value_lock_name, O_CREX) + for i in range(self.n_elem): + try: + self.value_lock[i] = Semaphore(self.value_lock_name+str(i), O_CREX) + except ExistentialError: + value_lock = Semaphore(self.value_lock_name+str(i), O_CREAT) + value_lock.unlink() + self.value_lock[i] = Semaphore(self.value_lock_name+str(i), O_CREX) - self.value_lock.release() + self.value_lock[i].release() # Get the shared value - def get(self, type_name= "value"): + def get(self): # Retreive the data from buffer - if type_name=="value": + + value = [None]*self.n_elem + for i in range(self.n_elem): try: - self.shm_region = SharedMemory(self.shm_name) - self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) - self.shm_region.close_fd() + self.shm_region[i] = SharedMemory(self.shm_name+str(i)) + self.shm_buf[i] = mmap.mmap(self.shm_region[i].fd, sizeof(c_float)) + self.shm_region[i].close_fd() except ExistentialError: - self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=sizeof(c_float)) - self.shm_buf = mmap.mmap(self.shm_region.fd, self.shm_region.size) - self.shm_region.close_fd() - self.value_lock.acquire() - value = struct.unpack('f', self.shm_buf)[0] - self.value_lock.release() + self.shm_region[i] = SharedMemory(self.shm_name+str(i), O_CREAT, size=sizeof(c_float)) + self.shm_buf[i] = mmap.mmap(self.shm_region[i].fd, self.shm_region[i].size) + self.shm_region[i].close_fd() + self.value_lock[i].acquire() + value[i] = struct.unpack('f', self.shm_buf[i])[0] + self.value_lock[i].release() + if self.n_elem <=1: + return value[0] + else: return value - elif type_name=="list": - mock_val_arr = np.array([0.0,0.0,0.0]) - byte_size = mock_val_arr.nbytes - self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=byte_size) - self.shm_buf = mmap.mmap(self.shm_region.fd, byte_size) - self.shm_region.close_fd() - self.value_lock.acquire() - array_val = np.ndarray(shape=(3,), - dtype='float32', buffer=self.shm_buf) - self.value_lock.release() - return array_val - else: - print("missing argument for return type") # Add the shared value - def add(self, value, type_name= "value"): + def add(self, value): # Send the data to shared regions - if type_name=="value": + + for i in range(self.n_elem): try: - self.shm_region = SharedMemory(self.shm_name) - self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) - self.shm_region.close_fd() + self.shm_region[i] = SharedMemory(self.shm_name+str(i)) + self.shm_buf[i] = mmap.mmap(self.shm_region[i].fd, sizeof(c_float)) + self.shm_region[i].close_fd() except ExistentialError: - self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=sizeof(c_float)) - self.shm_buf = mmap.mmap(self.shm_region.fd, self.shm_region.size) - self.shm_region.close_fd() + self.shm_region[i] = SharedMemory(self.shm_name+str(i), O_CREAT, size=sizeof(c_float)) + self.shm_buf[i] = mmap.mmap(self.shm_region[i].fd, self.shm_region[i].size) + self.shm_region[i].close_fd() - self.value_lock.acquire() - self.shm_buf[:] = struct.pack('f', value) - self.value_lock.release() - elif type_name=="list": - byte_size = value.nbytes - self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=byte_size) - self.shm_buf = mmap.mmap(self.shm_region.fd, byte_size) - self.shm_region.close_fd() - self.value_lock.acquire() - self.shm_buf[:] = value.tobytes() - self.value_lock.release() + self.value_lock[i].acquire() + if self.n_elem <=1: + self.shm_buf[i][:] = struct.pack('f', value) + else: + self.shm_buf[i][:] = struct.pack('f', value[i]) + self.value_lock[i].release() + # Destructor function to unlink and disconnect def close(self): - self.value_lock.acquire() - self.shm_buf.close() + for i in range(self.n_elem): + self.value_lock[i].acquire() + self.shm_buf[i].close() - try: - unlink_shared_memory(self.shm_name) - except ExistentialError: - pass + try: + unlink_shared_memory(self.shm_name+str(i)) + except ExistentialError: + pass - self.value_lock.release() - self.value_lock.close() + self.value_lock[i].release() + self.value_lock[i].close() diff --git a/exercises/static/exercises/rescue_people/web-template/user_functions.py b/exercises/static/exercises/rescue_people/web-template/user_functions.py index c6b2b30a2..69a47b344 100755 --- a/exercises/static/exercises/rescue_people/web-template/user_functions.py +++ b/exercises/static/exercises/rescue_people/web-template/user_functions.py @@ -19,14 +19,13 @@ def __init__(self): self.shared_vy = SharedValue("vy") self.shared_vz = SharedValue("vz") self.shared_landed_state = SharedValue("landedstate") - self.shared_position = SharedValue("position") - self.shared_velocity = SharedValue("velocity") - self.shared_orientation = SharedValue("orientation") - self.shared_roll = SharedValue("roll") - self.shared_pitch = SharedValue("pitch") - self.shared_yaw = SharedValue("yaw") + self.shared_position = SharedValue("position",3) + self.shared_velocity = SharedValue("velocity",3) + self.shared_orientation = SharedValue("orientation",3) self.shared_yaw_rate = SharedValue("yawrate") + self.shared_CMD = SharedValue("CMD") + # Get image function def get_frontal_image(self): @@ -41,65 +40,73 @@ def get_ventral_image(self): def takeoff(self, height): self.shared_takeoff_z.add(height) + self.shared_CMD.add(3) #TAKEOFF + def land(self): - pass + self.shared_CMD.add(4) #LAND def set_cmd_pos(self, x, y , z, az): self.shared_x.add(x) self.shared_y.add(y) self.shared_z.add(z) self.shared_az.add(az) + + self.shared_CMD.add(0) #POS def set_cmd_vel(self, vx, vy, vz, az): self.shared_vx.add(vx) self.shared_vy.add(vy) self.shared_vz.add(vz) self.shared_azt.add(az) + + self.shared_CMD.add(1) #VEL def set_cmd_mix(self, vx, vy, z, az): self.shared_vx.add(vx) self.shared_vy.add(vy) - self.shared_vz.add(z) + self.shared_z.add(z) self.shared_azt.add(az) + + self.shared_CMD.add(2) #MIX def get_position(self): - position = self.shared_position.get(type_name = "list") - return position + position = self.shared_position.get() + return np.array(position) def get_velocity(self): - velocity = self.shared_velocity.get(type_name = "list") - return velocity + velocity = self.shared_velocity.get() + return np.array(velocity) def get_yaw_rate(self): - yaw_rate = self.shared_yaw_rate.get(type_name = "value") + yaw_rate = self.shared_yaw_rate.get() return yaw_rate def get_orientation(self): - orientation = self.shared_orientation.get(type_name = "list") - return orientation + orientation = self.shared_orientation.get() + return np.array(orientation) def get_roll(self): - roll = self.shared_roll.get(type_name = "value") + roll = self.shared_orientation.get()[0] return roll def get_pitch(self): - pitch = self.shared_pitch.get(type_name = "value") + pitch = self.shared_orientation.get()[1] return pitch def get_yaw(self): - yaw = self.shared_yaw.get(type_name = "value") + yaw = self.shared_orientation.get()[2] return yaw def get_landed_state(self): - landed_state = self.shared_landed_state.get(type_name = "value") + landed_state = self.shared_landed_state.get() return landed_state def set_cmd_on(self): - pass + self.shared_CMD.add(5) #LIGHT ON def set_cmd_off(self): - pass + self.shared_CMD.add(6) #LIGHT OFF # Define GUI functions class GUIFunctions: diff --git a/exercises/static/exercises/visual_lander/web-template/hal.py b/exercises/static/exercises/visual_lander/web-template/hal.py index 17c39cd6b..09850aeb6 100755 --- a/exercises/static/exercises/visual_lander/web-template/hal.py +++ b/exercises/static/exercises/visual_lander/web-template/hal.py @@ -28,20 +28,21 @@ def __init__(self): self.shared_vy = SharedValue("vy") self.shared_vz = SharedValue("vz") self.shared_landed_state = SharedValue("landedstate") - self.shared_position = SharedValue("position") - self.shared_velocity = SharedValue("velocity") - self.shared_orientation = SharedValue("orientation") - self.shared_roll = SharedValue("roll") - self.shared_pitch = SharedValue("pitch") - self.shared_yaw = SharedValue("yaw") + self.shared_position = SharedValue("position",3) + self.shared_velocity = SharedValue("velocity",3) + self.shared_orientation = SharedValue("orientation",3) self.shared_yaw_rate = SharedValue("yawrate") + self.shared_CMD = SharedValue("CMD") + self.image = None self.drone = DroneWrapper(name="rqt",ns="/iris/") # Update thread self.thread = ThreadHAL(self.update_hal) + + # Explicit initialization functions # Class method, so user can call it without instantiation @@ -63,11 +64,11 @@ def get_ventral_image(self): def get_position(self): pos = self.drone.get_position() - self.shared_position.add(pos,type_name="list") + self.shared_position.add(pos) def get_velocity(self): vel = self.drone.get_velocity() - self.shared_velocity.add(vel ,type_name="list") + self.shared_velocity.add(vel ) def get_yaw_rate(self): yaw_rate = self.drone.get_yaw_rate() @@ -75,19 +76,7 @@ def get_yaw_rate(self): def get_orientation(self): orientation = self.drone.get_orientation() - self.shared_orientation.add(orientation ,type_name="list") - - def get_roll(self): - roll = self.drone.get_roll() - self.shared_roll.add(roll) - - def get_pitch(self): - pitch = self.drone.get_pitch() - self.shared_pitch.add(pitch) - - def get_yaw(self): - yaw = self.drone.get_yaw() - self.shared_yaw.add(yaw) + self.shared_orientation.add(orientation ) def get_landed_state(self): state = self.drone.get_landed_state() @@ -123,19 +112,26 @@ def land(self): self.drone.land() def update_hal(self): + CMD = self.shared_CMD.get() + self.get_frontal_image() self.get_ventral_image() self.get_position() self.get_velocity() self.get_yaw_rate() self.get_orientation() - self.get_pitch() - self.get_roll() - self.get_yaw() self.get_landed_state() - self.set_cmd_pos() - self.set_cmd_vel() - self.set_cmd_mix() + + if CMD == 0: # POS + self.set_cmd_pos() + elif CMD == 1: # VEL + self.set_cmd_vel() + elif CMD == 2: # MIX + self.set_cmd_mix() + elif CMD == 3: # TAKEOFF + self.takeoff() + elif CMD == 4: # LAND + self.land() # Destructor function to close all fds def __del__(self): @@ -154,9 +150,6 @@ def __del__(self): self.shared_position.close() self.shared_velocity.close() self.shared_orientation.close() - self.shared_roll.close() - self.shared_pitch.close() - self.shared_yaw.close() self.shared_yaw_rate.close() class ThreadHAL(threading.Thread): diff --git a/exercises/static/exercises/visual_lander/web-template/shared/value.py b/exercises/static/exercises/visual_lander/web-template/shared/value.py index 9315148d4..0146309a2 100755 --- a/exercises/static/exercises/visual_lander/web-template/shared/value.py +++ b/exercises/static/exercises/visual_lander/web-template/shared/value.py @@ -5,91 +5,83 @@ import struct class SharedValue: - def __init__(self, name): + def __init__(self, name, n_elem = 1): # Initialize varaibles for memory regions and buffers and Semaphore - self.shm_buf = None; self.shm_region = None - self.value_lock = None + self.n_elem = n_elem + self.shm_buf = [None]*self.n_elem; self.shm_region = [None]*self.n_elem + self.value_lock = [None]*self.n_elem self.shm_name = name; self.value_lock_name = name # Initialize or retreive Semaphore - try: - self.value_lock = Semaphore(self.value_lock_name, O_CREX) - except ExistentialError: - value_lock = Semaphore(self.value_lock_name, O_CREAT) - value_lock.unlink() - self.value_lock = Semaphore(self.value_lock_name, O_CREX) + for i in range(self.n_elem): + try: + self.value_lock[i] = Semaphore(self.value_lock_name+str(i), O_CREX) + except ExistentialError: + value_lock = Semaphore(self.value_lock_name+str(i), O_CREAT) + value_lock.unlink() + self.value_lock[i] = Semaphore(self.value_lock_name+str(i), O_CREX) - self.value_lock.release() + self.value_lock[i].release() # Get the shared value - def get(self, type_name= "value"): + def get(self): # Retreive the data from buffer - if type_name=="value": + + value = [None]*self.n_elem + for i in range(self.n_elem): try: - self.shm_region = SharedMemory(self.shm_name) - self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) - self.shm_region.close_fd() + self.shm_region[i] = SharedMemory(self.shm_name+str(i)) + self.shm_buf[i] = mmap.mmap(self.shm_region[i].fd, sizeof(c_float)) + self.shm_region[i].close_fd() except ExistentialError: - self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=sizeof(c_float)) - self.shm_buf = mmap.mmap(self.shm_region.fd, self.shm_region.size) - self.shm_region.close_fd() - self.value_lock.acquire() - value = struct.unpack('f', self.shm_buf)[0] - self.value_lock.release() + self.shm_region[i] = SharedMemory(self.shm_name+str(i), O_CREAT, size=sizeof(c_float)) + self.shm_buf[i] = mmap.mmap(self.shm_region[i].fd, self.shm_region[i].size) + self.shm_region[i].close_fd() + self.value_lock[i].acquire() + value[i] = struct.unpack('f', self.shm_buf[i])[0] + self.value_lock[i].release() + if self.n_elem <=1: + return value[0] + else: return value - elif type_name=="list": - mock_val_arr = np.array([0.0,0.0,0.0]) - byte_size = mock_val_arr.nbytes - self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=byte_size) - self.shm_buf = mmap.mmap(self.shm_region.fd, byte_size) - self.shm_region.close_fd() - self.value_lock.acquire() - array_val = np.ndarray(shape=(3,), - dtype='float32', buffer=self.shm_buf) - self.value_lock.release() - return array_val - else: - print("missing argument for return type") # Add the shared value - def add(self, value, type_name= "value"): + def add(self, value): # Send the data to shared regions - if type_name=="value": + + for i in range(self.n_elem): try: - self.shm_region = SharedMemory(self.shm_name) - self.shm_buf = mmap.mmap(self.shm_region.fd, sizeof(c_float)) - self.shm_region.close_fd() + self.shm_region[i] = SharedMemory(self.shm_name+str(i)) + self.shm_buf[i] = mmap.mmap(self.shm_region[i].fd, sizeof(c_float)) + self.shm_region[i].close_fd() except ExistentialError: - self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=sizeof(c_float)) - self.shm_buf = mmap.mmap(self.shm_region.fd, self.shm_region.size) - self.shm_region.close_fd() + self.shm_region[i] = SharedMemory(self.shm_name+str(i), O_CREAT, size=sizeof(c_float)) + self.shm_buf[i] = mmap.mmap(self.shm_region[i].fd, self.shm_region[i].size) + self.shm_region[i].close_fd() - self.value_lock.acquire() - self.shm_buf[:] = struct.pack('f', value) - self.value_lock.release() - elif type_name=="list": - byte_size = value.nbytes - self.shm_region = SharedMemory(self.shm_name, O_CREAT, size=byte_size) - self.shm_buf = mmap.mmap(self.shm_region.fd, byte_size) - self.shm_region.close_fd() - self.value_lock.acquire() - self.shm_buf[:] = value.tobytes() - self.value_lock.release() + self.value_lock[i].acquire() + if self.n_elem <=1: + self.shm_buf[i][:] = struct.pack('f', value) + else: + self.shm_buf[i][:] = struct.pack('f', value[i]) + self.value_lock[i].release() + # Destructor function to unlink and disconnect def close(self): - self.value_lock.acquire() - self.shm_buf.close() + for i in range(self.n_elem): + self.value_lock[i].acquire() + self.shm_buf[i].close() - try: - unlink_shared_memory(self.shm_name) - except ExistentialError: - pass + try: + unlink_shared_memory(self.shm_name+str(i)) + except ExistentialError: + pass - self.value_lock.release() - self.value_lock.close() + self.value_lock[i].release() + self.value_lock[i].close() diff --git a/exercises/static/exercises/visual_lander/web-template/user_functions.py b/exercises/static/exercises/visual_lander/web-template/user_functions.py index d741601fc..e595a5be1 100755 --- a/exercises/static/exercises/visual_lander/web-template/user_functions.py +++ b/exercises/static/exercises/visual_lander/web-template/user_functions.py @@ -19,14 +19,13 @@ def __init__(self): self.shared_vy = SharedValue("vy") self.shared_vz = SharedValue("vz") self.shared_landed_state = SharedValue("landedstate") - self.shared_position = SharedValue("position") - self.shared_velocity = SharedValue("velocity") - self.shared_orientation = SharedValue("orientation") - self.shared_roll = SharedValue("roll") - self.shared_pitch = SharedValue("pitch") - self.shared_yaw = SharedValue("yaw") + self.shared_position = SharedValue("position",3) + self.shared_velocity = SharedValue("velocity",3) + self.shared_orientation = SharedValue("orientation",3) self.shared_yaw_rate = SharedValue("yawrate") + self.shared_CMD = SharedValue("CMD") + # Get image function def get_frontal_image(self): @@ -41,58 +40,66 @@ def get_ventral_image(self): def takeoff(self, height): self.shared_takeoff_z.add(height) + self.shared_CMD.add(3) #TAKEOFF + def land(self): - pass + self.shared_CMD.add(4) #LAND def set_cmd_pos(self, x, y , z, az): self.shared_x.add(x) self.shared_y.add(y) self.shared_z.add(z) self.shared_az.add(az) + + self.shared_CMD.add(0) #POS def set_cmd_vel(self, vx, vy, vz, az): self.shared_vx.add(vx) self.shared_vy.add(vy) self.shared_vz.add(vz) self.shared_azt.add(az) + + self.shared_CMD.add(1) #VEL def set_cmd_mix(self, vx, vy, z, az): self.shared_vx.add(vx) self.shared_vy.add(vy) - self.shared_vz.add(z) + self.shared_z.add(z) self.shared_azt.add(az) + + self.shared_CMD.add(2) #MIX def get_position(self): - position = self.shared_position.get(type_name = "list") - return position + position = self.shared_position.get() + return np.array(position) def get_velocity(self): - velocity = self.shared_velocity.get(type_name = "list") - return velocity + velocity = self.shared_velocity.get() + return np.array(velocity) def get_yaw_rate(self): - yaw_rate = self.shared_yaw_rate.get(type_name = "value") + yaw_rate = self.shared_yaw_rate.get() return yaw_rate def get_orientation(self): - orientation = self.shared_orientation.get(type_name = "list") - return orientation + orientation = self.shared_orientation.get() + return np.array(orientation) def get_roll(self): - roll = self.shared_roll.get(type_name = "value") + roll = self.shared_orientation.get()[0] return roll def get_pitch(self): - pitch = self.shared_pitch.get(type_name = "value") + pitch = self.shared_orientation.get()[1] return pitch def get_yaw(self): - yaw = self.shared_yaw.get(type_name = "value") + yaw = self.shared_orientation.get()[2] return yaw def get_landed_state(self): - landed_state = self.shared_landed_state.get(type_name = "value") + landed_state = self.shared_landed_state.get() return landed_state # Define GUI functions diff --git a/scripts/pylint_checker.py b/scripts/pylint_checker.py index 4c3bd6264..41b6e1bf0 100644 --- a/scripts/pylint_checker.py +++ b/scripts/pylint_checker.py @@ -9,7 +9,7 @@ code_file = tempfile.NamedTemporaryFile(delete=False) code_file.write(python_code.encode()) code_file.seek(0) -options = code_file.name + ' --enable=similarities' + " --disable=C0114,C0116" +options = code_file.name + ' --enable=similarities' + " --disable=C0114,C0116,E1121" (stdout, stderr) = lint.py_run(options, return_std=True) code_file.seek(0) code_file.close() From 9c51700fdeb2d90e16abf0a4ce21a604cadd574c Mon Sep 17 00:00:00 2001 From: RUFFY-369 Date: Mon, 19 Sep 2022 15:22:07 +0530 Subject: [PATCH 37/42] add rotors plugin in world file which initially got removed --- .../drone_gymkhana/web-template/drone_gymkhana.world | 4 ++++ .../exercises/drone_hangar/web-template/drone_hangar.world | 4 ++++ .../exercises/follow_road/web-template/follow_road.world | 4 ++++ .../labyrinth_escape/web-template/labyrinth_escape.world | 4 ++++ .../package_delivery/web-template/package_delivery.world | 4 ++++ .../position_control/web-template/position_control.world | 4 ++++ .../web-template/power_tower_inspection.world | 4 ++++ .../exercises/visual_lander/web-template/visual_lander.world | 4 ++++ 8 files changed, 32 insertions(+) diff --git a/exercises/static/exercises/drone_gymkhana/web-template/drone_gymkhana.world b/exercises/static/exercises/drone_gymkhana/web-template/drone_gymkhana.world index 30a4fea15..0a4c87cb0 100644 --- a/exercises/static/exercises/drone_gymkhana/web-template/drone_gymkhana.world +++ b/exercises/static/exercises/drone_gymkhana/web-template/drone_gymkhana.world @@ -131,6 +131,10 @@ model://sun + + + 0 0 -9.8066 diff --git a/exercises/static/exercises/drone_hangar/web-template/drone_hangar.world b/exercises/static/exercises/drone_hangar/web-template/drone_hangar.world index 78fc9ad80..8872eb763 100644 --- a/exercises/static/exercises/drone_hangar/web-template/drone_hangar.world +++ b/exercises/static/exercises/drone_hangar/web-template/drone_hangar.world @@ -178,6 +178,10 @@ + + + 0 0 -9.8066 diff --git a/exercises/static/exercises/follow_road/web-template/follow_road.world b/exercises/static/exercises/follow_road/web-template/follow_road.world index d3d168a68..64565cb65 100755 --- a/exercises/static/exercises/follow_road/web-template/follow_road.world +++ b/exercises/static/exercises/follow_road/web-template/follow_road.world @@ -126,6 +126,10 @@ 15 0 0.01 + + + 0 0 -9.8066 diff --git a/exercises/static/exercises/labyrinth_escape/web-template/labyrinth_escape.world b/exercises/static/exercises/labyrinth_escape/web-template/labyrinth_escape.world index 8712adcf2..102eac799 100644 --- a/exercises/static/exercises/labyrinth_escape/web-template/labyrinth_escape.world +++ b/exercises/static/exercises/labyrinth_escape/web-template/labyrinth_escape.world @@ -87,6 +87,10 @@ model://sun + + + 0 0 -9.8066 diff --git a/exercises/static/exercises/package_delivery/web-template/package_delivery.world b/exercises/static/exercises/package_delivery/web-template/package_delivery.world index e1867ff4e..9d24f2ed4 100644 --- a/exercises/static/exercises/package_delivery/web-template/package_delivery.world +++ b/exercises/static/exercises/package_delivery/web-template/package_delivery.world @@ -252,6 +252,10 @@ + + + 0 0 -9.8066 diff --git a/exercises/static/exercises/position_control/web-template/position_control.world b/exercises/static/exercises/position_control/web-template/position_control.world index f5e9a5955..74d915d57 100644 --- a/exercises/static/exercises/position_control/web-template/position_control.world +++ b/exercises/static/exercises/position_control/web-template/position_control.world @@ -63,6 +63,10 @@ model://sun + + + 0 0 -9.8066 diff --git a/exercises/static/exercises/power_tower_inspection/web-template/power_tower_inspection.world b/exercises/static/exercises/power_tower_inspection/web-template/power_tower_inspection.world index 1c9e346ce..41ceeb645 100644 --- a/exercises/static/exercises/power_tower_inspection/web-template/power_tower_inspection.world +++ b/exercises/static/exercises/power_tower_inspection/web-template/power_tower_inspection.world @@ -329,6 +329,10 @@ + + + 0 0 -9.8066 diff --git a/exercises/static/exercises/visual_lander/web-template/visual_lander.world b/exercises/static/exercises/visual_lander/web-template/visual_lander.world index d191d33a6..d203b8606 100644 --- a/exercises/static/exercises/visual_lander/web-template/visual_lander.world +++ b/exercises/static/exercises/visual_lander/web-template/visual_lander.world @@ -84,6 +84,10 @@ model://sun + + + 0 0 -9.8066 From c2f3d9a0893e4b9b7a24768926f48ceb35063fd2 Mon Sep 17 00:00:00 2001 From: Pedro Arias Date: Sun, 2 Oct 2022 13:13:53 +0200 Subject: [PATCH 38/42] follow_road_2 --- .../static/exercises/{follow_road => follow_road_2}/README.md | 0 .../exercises/{follow_road => follow_road_2}/follow_road.launch | 0 .../exercises/{follow_road => follow_road_2}/follow_road.world | 0 .../exercises/{follow_road => follow_road_2}/my_solution.py | 0 .../{follow_road => follow_road_2}/web-template/RADI-launch | 0 .../{follow_road => follow_road_2}/web-template/README.md | 0 .../{follow_road => follow_road_2}/web-template/brain.py | 0 .../{follow_road => follow_road_2}/web-template/code/academy.py | 0 .../{follow_road => follow_road_2}/web-template/console.py | 0 .../{follow_road => follow_road_2}/web-template/exercise.py | 0 .../{follow_road => follow_road_2}/web-template/follow_road.world | 0 .../exercises/{follow_road => follow_road_2}/web-template/gui.py | 0 .../exercises/{follow_road => follow_road_2}/web-template/hal.py | 0 .../web-template/interfaces/__init__.py | 0 .../web-template/interfaces/camera.py | 0 .../web-template/interfaces/motors.py | 0 .../web-template/interfaces/pose3d.py | 0 .../web-template/interfaces/threadPublisher.py | 0 .../web-template/launch/follow_road.launch | 0 .../web-template/shared/__init__.py | 0 .../{follow_road => follow_road_2}/web-template/shared/image.py | 0 .../web-template/shared/structure_img.py | 0 .../{follow_road => follow_road_2}/web-template/shared/value.py | 0 .../{follow_road => follow_road_2}/web-template/user_functions.py | 0 24 files changed, 0 insertions(+), 0 deletions(-) rename exercises/static/exercises/{follow_road => follow_road_2}/README.md (100%) rename exercises/static/exercises/{follow_road => follow_road_2}/follow_road.launch (100%) rename exercises/static/exercises/{follow_road => follow_road_2}/follow_road.world (100%) rename exercises/static/exercises/{follow_road => follow_road_2}/my_solution.py (100%) rename exercises/static/exercises/{follow_road => follow_road_2}/web-template/RADI-launch (100%) rename exercises/static/exercises/{follow_road => follow_road_2}/web-template/README.md (100%) rename exercises/static/exercises/{follow_road => follow_road_2}/web-template/brain.py (100%) rename exercises/static/exercises/{follow_road => follow_road_2}/web-template/code/academy.py (100%) rename exercises/static/exercises/{follow_road => follow_road_2}/web-template/console.py (100%) rename exercises/static/exercises/{follow_road => follow_road_2}/web-template/exercise.py (100%) rename exercises/static/exercises/{follow_road => follow_road_2}/web-template/follow_road.world (100%) rename exercises/static/exercises/{follow_road => follow_road_2}/web-template/gui.py (100%) rename exercises/static/exercises/{follow_road => follow_road_2}/web-template/hal.py (100%) rename exercises/static/exercises/{follow_road => follow_road_2}/web-template/interfaces/__init__.py (100%) rename exercises/static/exercises/{follow_road => follow_road_2}/web-template/interfaces/camera.py (100%) rename exercises/static/exercises/{follow_road => follow_road_2}/web-template/interfaces/motors.py (100%) rename exercises/static/exercises/{follow_road => follow_road_2}/web-template/interfaces/pose3d.py (100%) rename exercises/static/exercises/{follow_road => follow_road_2}/web-template/interfaces/threadPublisher.py (100%) rename exercises/static/exercises/{follow_road => follow_road_2}/web-template/launch/follow_road.launch (100%) rename exercises/static/exercises/{follow_road => follow_road_2}/web-template/shared/__init__.py (100%) rename exercises/static/exercises/{follow_road => follow_road_2}/web-template/shared/image.py (100%) rename exercises/static/exercises/{follow_road => follow_road_2}/web-template/shared/structure_img.py (100%) rename exercises/static/exercises/{follow_road => follow_road_2}/web-template/shared/value.py (100%) rename exercises/static/exercises/{follow_road => follow_road_2}/web-template/user_functions.py (100%) diff --git a/exercises/static/exercises/follow_road/README.md b/exercises/static/exercises/follow_road_2/README.md similarity index 100% rename from exercises/static/exercises/follow_road/README.md rename to exercises/static/exercises/follow_road_2/README.md diff --git a/exercises/static/exercises/follow_road/follow_road.launch b/exercises/static/exercises/follow_road_2/follow_road.launch similarity index 100% rename from exercises/static/exercises/follow_road/follow_road.launch rename to exercises/static/exercises/follow_road_2/follow_road.launch diff --git a/exercises/static/exercises/follow_road/follow_road.world b/exercises/static/exercises/follow_road_2/follow_road.world similarity index 100% rename from exercises/static/exercises/follow_road/follow_road.world rename to exercises/static/exercises/follow_road_2/follow_road.world diff --git a/exercises/static/exercises/follow_road/my_solution.py b/exercises/static/exercises/follow_road_2/my_solution.py similarity index 100% rename from exercises/static/exercises/follow_road/my_solution.py rename to exercises/static/exercises/follow_road_2/my_solution.py diff --git a/exercises/static/exercises/follow_road/web-template/RADI-launch b/exercises/static/exercises/follow_road_2/web-template/RADI-launch similarity index 100% rename from exercises/static/exercises/follow_road/web-template/RADI-launch rename to exercises/static/exercises/follow_road_2/web-template/RADI-launch diff --git a/exercises/static/exercises/follow_road/web-template/README.md b/exercises/static/exercises/follow_road_2/web-template/README.md similarity index 100% rename from exercises/static/exercises/follow_road/web-template/README.md rename to exercises/static/exercises/follow_road_2/web-template/README.md diff --git a/exercises/static/exercises/follow_road/web-template/brain.py b/exercises/static/exercises/follow_road_2/web-template/brain.py similarity index 100% rename from exercises/static/exercises/follow_road/web-template/brain.py rename to exercises/static/exercises/follow_road_2/web-template/brain.py diff --git a/exercises/static/exercises/follow_road/web-template/code/academy.py b/exercises/static/exercises/follow_road_2/web-template/code/academy.py similarity index 100% rename from exercises/static/exercises/follow_road/web-template/code/academy.py rename to exercises/static/exercises/follow_road_2/web-template/code/academy.py diff --git a/exercises/static/exercises/follow_road/web-template/console.py b/exercises/static/exercises/follow_road_2/web-template/console.py similarity index 100% rename from exercises/static/exercises/follow_road/web-template/console.py rename to exercises/static/exercises/follow_road_2/web-template/console.py diff --git a/exercises/static/exercises/follow_road/web-template/exercise.py b/exercises/static/exercises/follow_road_2/web-template/exercise.py similarity index 100% rename from exercises/static/exercises/follow_road/web-template/exercise.py rename to exercises/static/exercises/follow_road_2/web-template/exercise.py diff --git a/exercises/static/exercises/follow_road/web-template/follow_road.world b/exercises/static/exercises/follow_road_2/web-template/follow_road.world similarity index 100% rename from exercises/static/exercises/follow_road/web-template/follow_road.world rename to exercises/static/exercises/follow_road_2/web-template/follow_road.world diff --git a/exercises/static/exercises/follow_road/web-template/gui.py b/exercises/static/exercises/follow_road_2/web-template/gui.py similarity index 100% rename from exercises/static/exercises/follow_road/web-template/gui.py rename to exercises/static/exercises/follow_road_2/web-template/gui.py diff --git a/exercises/static/exercises/follow_road/web-template/hal.py b/exercises/static/exercises/follow_road_2/web-template/hal.py similarity index 100% rename from exercises/static/exercises/follow_road/web-template/hal.py rename to exercises/static/exercises/follow_road_2/web-template/hal.py diff --git a/exercises/static/exercises/follow_road/web-template/interfaces/__init__.py b/exercises/static/exercises/follow_road_2/web-template/interfaces/__init__.py similarity index 100% rename from exercises/static/exercises/follow_road/web-template/interfaces/__init__.py rename to exercises/static/exercises/follow_road_2/web-template/interfaces/__init__.py diff --git a/exercises/static/exercises/follow_road/web-template/interfaces/camera.py b/exercises/static/exercises/follow_road_2/web-template/interfaces/camera.py similarity index 100% rename from exercises/static/exercises/follow_road/web-template/interfaces/camera.py rename to exercises/static/exercises/follow_road_2/web-template/interfaces/camera.py diff --git a/exercises/static/exercises/follow_road/web-template/interfaces/motors.py b/exercises/static/exercises/follow_road_2/web-template/interfaces/motors.py similarity index 100% rename from exercises/static/exercises/follow_road/web-template/interfaces/motors.py rename to exercises/static/exercises/follow_road_2/web-template/interfaces/motors.py diff --git a/exercises/static/exercises/follow_road/web-template/interfaces/pose3d.py b/exercises/static/exercises/follow_road_2/web-template/interfaces/pose3d.py similarity index 100% rename from exercises/static/exercises/follow_road/web-template/interfaces/pose3d.py rename to exercises/static/exercises/follow_road_2/web-template/interfaces/pose3d.py diff --git a/exercises/static/exercises/follow_road/web-template/interfaces/threadPublisher.py b/exercises/static/exercises/follow_road_2/web-template/interfaces/threadPublisher.py similarity index 100% rename from exercises/static/exercises/follow_road/web-template/interfaces/threadPublisher.py rename to exercises/static/exercises/follow_road_2/web-template/interfaces/threadPublisher.py diff --git a/exercises/static/exercises/follow_road/web-template/launch/follow_road.launch b/exercises/static/exercises/follow_road_2/web-template/launch/follow_road.launch similarity index 100% rename from exercises/static/exercises/follow_road/web-template/launch/follow_road.launch rename to exercises/static/exercises/follow_road_2/web-template/launch/follow_road.launch diff --git a/exercises/static/exercises/follow_road/web-template/shared/__init__.py b/exercises/static/exercises/follow_road_2/web-template/shared/__init__.py similarity index 100% rename from exercises/static/exercises/follow_road/web-template/shared/__init__.py rename to exercises/static/exercises/follow_road_2/web-template/shared/__init__.py diff --git a/exercises/static/exercises/follow_road/web-template/shared/image.py b/exercises/static/exercises/follow_road_2/web-template/shared/image.py similarity index 100% rename from exercises/static/exercises/follow_road/web-template/shared/image.py rename to exercises/static/exercises/follow_road_2/web-template/shared/image.py diff --git a/exercises/static/exercises/follow_road/web-template/shared/structure_img.py b/exercises/static/exercises/follow_road_2/web-template/shared/structure_img.py similarity index 100% rename from exercises/static/exercises/follow_road/web-template/shared/structure_img.py rename to exercises/static/exercises/follow_road_2/web-template/shared/structure_img.py diff --git a/exercises/static/exercises/follow_road/web-template/shared/value.py b/exercises/static/exercises/follow_road_2/web-template/shared/value.py similarity index 100% rename from exercises/static/exercises/follow_road/web-template/shared/value.py rename to exercises/static/exercises/follow_road_2/web-template/shared/value.py diff --git a/exercises/static/exercises/follow_road/web-template/user_functions.py b/exercises/static/exercises/follow_road_2/web-template/user_functions.py similarity index 100% rename from exercises/static/exercises/follow_road/web-template/user_functions.py rename to exercises/static/exercises/follow_road_2/web-template/user_functions.py From 43452f2a2ac3c2f96d359e83fcd5c9767b8ca606 Mon Sep 17 00:00:00 2001 From: Pedro Arias Date: Sun, 2 Oct 2022 13:19:26 +0200 Subject: [PATCH 39/42] restore follow_road --- .../static/exercises/follow_road/README.md | 1 + .../exercises/follow_road/follow_road.launch | 18 + .../exercises/follow_road/follow_road.world | 157 ++++++++ .../exercises/follow_road/my_solution.py | 51 +++ .../follow_road/web-template/RADI-launch | 2 + .../follow_road/web-template/README.md | 1 + .../follow_road/web-template/code/academy.py | 8 + .../follow_road/web-template/console.py | 18 + .../follow_road/web-template/exercise.py | 362 ++++++++++++++++++ .../web-template/follow_road.world | 153 ++++++++ .../exercises/follow_road/web-template/gui.py | 250 ++++++++++++ .../exercises/follow_road/web-template/hal.py | 84 ++++ .../web-template/interfaces/__init__.py | 0 .../web-template/interfaces/camera.py | 89 +++++ .../web-template/interfaces/motors.py | 123 ++++++ .../web-template/interfaces/pose3d.py | 176 +++++++++ .../interfaces/threadPublisher.py | 46 +++ .../web-template/launch/gazebo.launch | 24 ++ .../follow_road/web-template/launch/launch.py | 131 +++++++ .../web-template/launch/mavros.launch | 13 + .../web-template/launch/px4.launch | 21 + 21 files changed, 1728 insertions(+) create mode 100755 exercises/static/exercises/follow_road/README.md create mode 100644 exercises/static/exercises/follow_road/follow_road.launch create mode 100644 exercises/static/exercises/follow_road/follow_road.world create mode 100755 exercises/static/exercises/follow_road/my_solution.py create mode 100755 exercises/static/exercises/follow_road/web-template/RADI-launch create mode 100644 exercises/static/exercises/follow_road/web-template/README.md create mode 100644 exercises/static/exercises/follow_road/web-template/code/academy.py create mode 100755 exercises/static/exercises/follow_road/web-template/console.py create mode 100755 exercises/static/exercises/follow_road/web-template/exercise.py create mode 100755 exercises/static/exercises/follow_road/web-template/follow_road.world create mode 100755 exercises/static/exercises/follow_road/web-template/gui.py create mode 100755 exercises/static/exercises/follow_road/web-template/hal.py create mode 100644 exercises/static/exercises/follow_road/web-template/interfaces/__init__.py create mode 100644 exercises/static/exercises/follow_road/web-template/interfaces/camera.py create mode 100644 exercises/static/exercises/follow_road/web-template/interfaces/motors.py create mode 100644 exercises/static/exercises/follow_road/web-template/interfaces/pose3d.py create mode 100644 exercises/static/exercises/follow_road/web-template/interfaces/threadPublisher.py create mode 100644 exercises/static/exercises/follow_road/web-template/launch/gazebo.launch create mode 100644 exercises/static/exercises/follow_road/web-template/launch/launch.py create mode 100644 exercises/static/exercises/follow_road/web-template/launch/mavros.launch create mode 100644 exercises/static/exercises/follow_road/web-template/launch/px4.launch diff --git a/exercises/static/exercises/follow_road/README.md b/exercises/static/exercises/follow_road/README.md new file mode 100755 index 000000000..8f8ff8c45 --- /dev/null +++ b/exercises/static/exercises/follow_road/README.md @@ -0,0 +1 @@ +[Exercise Documentation Website](https://jderobot.github.io/RoboticsAcademy/exercises/Drones/follow_road) diff --git a/exercises/static/exercises/follow_road/follow_road.launch b/exercises/static/exercises/follow_road/follow_road.launch new file mode 100644 index 000000000..d068ca128 --- /dev/null +++ b/exercises/static/exercises/follow_road/follow_road.launch @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/exercises/static/exercises/follow_road/follow_road.world b/exercises/static/exercises/follow_road/follow_road.world new file mode 100644 index 000000000..95d218a39 --- /dev/null +++ b/exercises/static/exercises/follow_road/follow_road.world @@ -0,0 +1,157 @@ + + + + + + false + + + + + model://sun + + + + model://grass_plane + + + + + + + + 12 + + + + + + + logo + model://logoJdeRobot + -6 -8 0 0 0 0 + + + + model://gas_station + 1 6.43 0 0 0 0 + + + + + + + model://polaris_ranger_ev + 4.48 -6 0.1 0 0 0 + true + + + + + + + lampost1 + model://lamp_post + 5 13 0 0 0 0 + + + + lampost2 + model://lamp_post + -4 13 0 0 0 0 + + + + 3 + 15 0 0.01 + 15 5 0.01 + 15 10 0.01 + 15 13 0.01 + 14 15 0.01 + 12 16 0.01 + -12 16 0.01 + -14 15 0.01 + -15 12 0.01 + -15 6 0.01 + -15 3 0.01 + -15 0 0.01 + -16 -2 0.01 + -17 -3 0.01 + -27 -3 0.01 + -28 -4 0.01 + -28 -6 0.01 + -28 -9 0.01 + -28 -12 0.01 + -28 -15 0.01 + -28 -18 0.01 + -28 -20 0.01 + -27 -21 0.01 + -25 -22 0.01 + -24 -22 0.01 + -21 -22 0.01 + -18 -22 0.01 + -15 -22 0.01 + -12 -22 0.01 + -9 -22 0.01 + -6 -22 0.01 + -3 -22 0.01 + 0 -22 0.01 + 6 -22 0.01 + 10 -22 0.01 + 12 -22 0.01 + 13 -21 0.01 + 14 -20 0.01 + 15 -19 0.01 + 15 -18 0.01 + 15 -17 0.01 + 15 -15 0.01 + 15 -12 0.01 + 15 -9 0.01 + 15 -6 0.01 + 15 -3 0.01 + 15 0 0.01 + + + + + 0 0 -9.8066 + + + quick + 10 + 1.3 + 0 + + + 0 + 0.2 + 100 + 0.001 + + + 0.004 + 1 + 250 + 6.0e-6 2.3e-5 -4.2e-5 + + + + diff --git a/exercises/static/exercises/follow_road/my_solution.py b/exercises/static/exercises/follow_road/my_solution.py new file mode 100755 index 000000000..cb71990a8 --- /dev/null +++ b/exercises/static/exercises/follow_road/my_solution.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python + +import rospy +import numpy as np +import cv2 +from drone_wrapper import DroneWrapper +from std_msgs.msg import Bool, Float64 +from sensor_msgs.msg import Image +from geometry_msgs.msg import Twist, Pose + +code_live_flag = False + +def gui_play_stop_cb(msg): + global code_live_flag, code_live_timer + if msg.data == True: + if not code_live_flag: + code_live_flag = True + code_live_timer = rospy.Timer(rospy.Duration(nsecs=50000000), execute) + else: + if code_live_flag: + code_live_flag = False + code_live_timer.shutdown() + +def set_image_filtered(img): + gui_filtered_img_pub.publish(HAL.bridge.cv2_to_imgmsg(img)) + +def set_image_threshed(img): + gui_threshed_img_pub.publish(HAL.bridge.cv2_to_imgmsg(img)) + +def execute(event): + global HAL + img_frontal = HAL.get_frontal_image() + img_ventral = HAL.get_ventral_image() + # Both the above images are cv2 images + ################# Insert your code here ################################# + + set_image_filtered(img_frontal) + set_image_threshed(img_ventral) + + ######################################################################### + +if __name__ == "__main__": + HAL = DroneWrapper() + rospy.Subscriber('gui/play_stop', Bool, gui_play_stop_cb) + gui_filtered_img_pub = rospy.Publisher('interface/filtered_img', Image, queue_size = 1) + gui_threshed_img_pub = rospy.Publisher('interface/threshed_img', Image, queue_size = 1) + code_live_flag = False + code_live_timer = rospy.Timer(rospy.Duration(nsecs=50000000), execute) + code_live_timer.shutdown() + while not rospy.is_shutdown(): + rospy.spin() diff --git a/exercises/static/exercises/follow_road/web-template/RADI-launch b/exercises/static/exercises/follow_road/web-template/RADI-launch new file mode 100755 index 000000000..afde9c081 --- /dev/null +++ b/exercises/static/exercises/follow_road/web-template/RADI-launch @@ -0,0 +1,2 @@ +#!/bin/sh +docker run -it --rm -p 8000:8000 -p 2303:2303 -p 1905:1905 -p 8765:8765 -p 6080:6080 -p 1108:1108 jderobot/robotics-academy:3.1.2 ./start-3.1.sh diff --git a/exercises/static/exercises/follow_road/web-template/README.md b/exercises/static/exercises/follow_road/web-template/README.md new file mode 100644 index 000000000..8f8ff8c45 --- /dev/null +++ b/exercises/static/exercises/follow_road/web-template/README.md @@ -0,0 +1 @@ +[Exercise Documentation Website](https://jderobot.github.io/RoboticsAcademy/exercises/Drones/follow_road) diff --git a/exercises/static/exercises/follow_road/web-template/code/academy.py b/exercises/static/exercises/follow_road/web-template/code/academy.py new file mode 100644 index 000000000..7a59ce7f5 --- /dev/null +++ b/exercises/static/exercises/follow_road/web-template/code/academy.py @@ -0,0 +1,8 @@ +# Enter sequential code! +from GUI import GUI +from HAL import HAL + +while True: + # Enter iterative code! + img = HAL.get_ventral_image() + GUI.showImage(img) \ No newline at end of file diff --git a/exercises/static/exercises/follow_road/web-template/console.py b/exercises/static/exercises/follow_road/web-template/console.py new file mode 100755 index 000000000..23d0efad3 --- /dev/null +++ b/exercises/static/exercises/follow_road/web-template/console.py @@ -0,0 +1,18 @@ +# Functions to start and close console +import os +import sys + +def start_console(): + # Get all the file descriptors and choose the latest one + fds = os.listdir("/dev/pts/") + fds.sort() + console_fd = fds[-2] + + sys.stderr = open('/dev/pts/' + console_fd, 'w') + sys.stdout = open('/dev/pts/' + console_fd, 'w') + sys.stdin = open('/dev/pts/' + console_fd, 'w') + +def close_console(): + sys.stderr.close() + sys.stdout.close() + sys.stdin.close() \ No newline at end of file diff --git a/exercises/static/exercises/follow_road/web-template/exercise.py b/exercises/static/exercises/follow_road/web-template/exercise.py new file mode 100755 index 000000000..71d5934a8 --- /dev/null +++ b/exercises/static/exercises/follow_road/web-template/exercise.py @@ -0,0 +1,362 @@ +#!/usr/bin/env python + +from __future__ import print_function + +from websocket_server import WebsocketServer +import time +import threading +import subprocess +import sys +from datetime import datetime +import re +import json +import importlib + +import rospy +from std_srvs.srv import Empty + +from gui import GUI, ThreadGUI +from hal import HAL +from console import start_console, close_console + + +class Template: + # Initialize class variables + # self.ideal_cycle to run an execution for at least 1 second + # self.process for the current running process + def __init__(self): + self.measure_thread = None + self.thread = None + self.reload = False + self.stop_brain = True + self.user_code = "" + + # Time variables + self.ideal_cycle = 80 + self.measured_cycle = 80 + self.iteration_counter = 0 + self.real_time_factor = 0 + self.frequency_message = {'brain': '', 'gui': '', 'rtf': ''} + + self.server = None + self.client = None + self.host = sys.argv[1] + + # Initialize the GUI, HAL and Console behind the scenes + self.hal = HAL() + self.gui = GUI(self.host) + + # Function to parse the code + # A few assumptions: + # 1. The user always passes sequential and iterative codes + # 2. Only a single infinite loop + def parse_code(self, source_code): + sequential_code, iterative_code = self.seperate_seq_iter(source_code) + return iterative_code, sequential_code + + # Function to separate the iterative and sequential code + def seperate_seq_iter(self, source_code): + if source_code == "": + return "", "" + + # Search for an instance of while True + infinite_loop = re.search(r'[^ ]while\s*\(\s*True\s*\)\s*:|[^ ]while\s*True\s*:|[^ ]while\s*1\s*:|[^ ]while\s*\(\s*1\s*\)\s*:', source_code) + + # Separate the content inside while True and the other + # (Separating the sequential and iterative part!) + try: + start_index = infinite_loop.start() + iterative_code = source_code[start_index:] + sequential_code = source_code[:start_index] + + # Remove while True: syntax from the code + # And remove the the 4 spaces indentation before each command + iterative_code = re.sub(r'[^ ]while\s*\(\s*True\s*\)\s*:|[^ ]while\s*True\s*:|[^ ]while\s*1\s*:|[^ ]while\s*\(\s*1\s*\)\s*:', '', iterative_code) + # Add newlines to match line on bug report + extra_lines = sequential_code.count('\n') + while (extra_lines >= 0): + iterative_code = '\n' + iterative_code + extra_lines -= 1 + iterative_code = re.sub(r'^[ ]{4}', '', iterative_code, flags=re.M) + + except: + sequential_code = source_code + iterative_code = "" + + return sequential_code, iterative_code + + # The process function + def process_code(self, source_code): + # Redirect the information to console + start_console() + + iterative_code, sequential_code = self.parse_code(source_code) + + # print(sequential_code) + # print(iterative_code) + + # The Python exec function + # Run the sequential part + gui_module, hal_module = self.generate_modules() + reference_environment = {"GUI": gui_module, "HAL": hal_module} + while (self.stop_brain == True): + if (self.reload == True): + return + time.sleep(0.1) + exec(sequential_code, reference_environment) + + # Run the iterative part inside template + # and keep the check for flag + while self.reload == False: + while (self.stop_brain == True): + if (self.reload == True): + return + time.sleep(0.1) + + start_time = datetime.now() + + # Execute the iterative portion + exec(iterative_code, reference_environment) + + # Template specifics to run! + finish_time = datetime.now() + dt = finish_time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + + # Keep updating the iteration counter + if (iterative_code == ""): + self.iteration_counter = 0 + else: + self.iteration_counter = self.iteration_counter + 1 + + # The code should be run for atleast the target time step + # If it's less put to sleep + if (ms < self.ideal_cycle): + time.sleep((self.ideal_cycle - ms) / 1000.0) + + close_console() + print("Current Thread Joined!") + + # Function to generate the modules for use in ACE Editor + def generate_modules(self): + # Define HAL module + hal_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) + hal_module.HAL = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) + # hal_module.drone = imp.new_module("drone") + # motors# hal_module.HAL.motors = imp.new_module("motors") + + # Add HAL functions + hal_module.HAL.get_frontal_image = self.hal.get_frontal_image + hal_module.HAL.get_ventral_image = self.hal.get_ventral_image + hal_module.HAL.get_position = self.hal.get_position + hal_module.HAL.get_velocity = self.hal.get_velocity + hal_module.HAL.get_yaw_rate = self.hal.get_yaw_rate + hal_module.HAL.get_orientation = self.hal.get_orientation + hal_module.HAL.get_roll = self.hal.get_roll + hal_module.HAL.get_pitch = self.hal.get_pitch + hal_module.HAL.get_yaw = self.hal.get_yaw + hal_module.HAL.get_landed_state = self.hal.get_landed_state + hal_module.HAL.set_cmd_pos = self.hal.set_cmd_pos + hal_module.HAL.set_cmd_vel = self.hal.set_cmd_vel + hal_module.HAL.set_cmd_mix = self.hal.set_cmd_mix + hal_module.HAL.takeoff = self.hal.takeoff + hal_module.HAL.land = self.hal.land + + # Define GUI module + gui_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) + gui_module.GUI = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) + + # Add GUI functions + gui_module.GUI.showImage = self.gui.showImage + gui_module.GUI.showLeftImage = self.gui.showLeftImage + + # Adding modules to system + # Protip: The names should be different from + # other modules, otherwise some errors + sys.modules["HAL"] = hal_module + sys.modules["GUI"] = gui_module + + return gui_module, hal_module + + # Function to measure the frequency of iterations + def measure_frequency(self): + previous_time = datetime.now() + # An infinite loop + while True: + # Sleep for 2 seconds + time.sleep(2) + + # Measure the current time and subtract from the previous time to get real time interval + current_time = datetime.now() + dt = current_time - previous_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + previous_time = current_time + + # Get the time period + try: + # Division by zero + self.measured_cycle = ms / self.iteration_counter + except: + self.measured_cycle = 0 + + # Reset the counter + self.iteration_counter = 0 + + # Send to client + self.send_frequency_message() + + # Function to generate and send frequency messages + def send_frequency_message(self): + # This function generates and sends frequency measures of the brain and gui + brain_frequency = 0; gui_frequency = 0 + try: + brain_frequency = round(1000 / self.measured_cycle, 1) + except ZeroDivisionError: + brain_frequency = 0 + + try: + gui_frequency = round(1000 / self.thread_gui.measured_cycle, 1) + except ZeroDivisionError: + gui_frequency = 0 + + self.frequency_message["brain"] = brain_frequency + self.frequency_message["gui"] = gui_frequency + self.frequency_message["rtf"] = self.real_time_factor + + message = "#freq" + json.dumps(self.frequency_message) + self.server.send_message(self.client, message) + + def send_ping_message(self): + self.server.send_message(self.client, "#ping") + + # Function to notify the front end that the code was received and sent to execution + def send_code_message(self): + self.server.send_message(self.client, "#exec") + + # Function to track the real time factor from Gazebo statistics + # https://stackoverflow.com/a/17698359 + # (For reference, Python3 solution specified in the same answer) + def track_stats(self): + args = ["gz", "stats", "-p"] + # Prints gz statistics. "-p": Output comma-separated values containing- + # real-time factor (percent), simtime (sec), realtime (sec), paused (T or F) + stats_process = subprocess.Popen(args, stdout=subprocess.PIPE, bufsize=1, universal_newlines=True) + # bufsize=1 enables line-bufferred mode (the input buffer is flushed + # automatically on newlines if you would write to process.stdin ) + with stats_process.stdout: + for line in iter(stats_process.stdout.readline, ''): + stats_list = [x.strip() for x in line.split(',')] + self.real_time_factor = stats_list[0] + + # Function to maintain thread execution + def execute_thread(self, source_code): + # Keep checking until the thread is alive + # The thread will die when the coming iteration reads the flag + if self.thread is not None: + while self.thread.is_alive(): + time.sleep(0.2) + + # Turn the flag down, the iteration has successfully stopped! + self.reload = False + # New thread execution + self.thread = threading.Thread(target=self.process_code, args=[source_code]) + self.thread.start() + self.send_code_message() + print("New Thread Started!") + + # Function to read and set frequency from incoming message + def read_frequency_message(self, message): + frequency_message = json.loads(message) + + # Set brain frequency + frequency = float(frequency_message["brain"]) + self.ideal_cycle = 1000.0 / frequency + + # Set gui frequency + frequency = float(frequency_message["gui"]) + self.thread_gui.ideal_cycle = 1000.0 / frequency + + return + + # The websocket function + # Gets called when there is an incoming message from the client + def handle(self, client, server, message): + if message[:5] == "#freq": + frequency_message = message[5:] + self.read_frequency_message(frequency_message) + time.sleep(1) + return + + elif(message[:5] == "#ping"): + time.sleep(1) + self.send_ping_message() + return + + elif (message[:5] == "#code"): + try: + # Once received turn the reload flag up and send it to execute_thread function + self.user_code = message[6:] + # print(repr(code)) + self.reload = True + self.execute_thread(self.user_code) + except: + pass + + elif (message[:5] == "#rest"): + try: + self.reload = True + self.stop_brain = True + self.execute_thread(self.user_code) + except: + pass + + elif (message[:5] == "#stop"): + self.stop_brain = True + + elif (message[:5] == "#play"): + self.stop_brain = False + + # Function that gets called when the server is connected + def connected(self, client, server): + self.client = client + # Start the GUI update thread + self.thread_gui = ThreadGUI(self.gui) + self.thread_gui.start() + + # Start the real time factor tracker thread + self.stats_thread = threading.Thread(target=self.track_stats) + self.stats_thread.start() + + # Start measure frequency + self.measure_thread = threading.Thread(target=self.measure_frequency) + self.measure_thread.start() + + print(client, 'connected') + + # Function that gets called when the connected closes + def handle_close(self, client, server): + print(client, 'closed') + + def run_server(self): + self.server = WebsocketServer(port=1905, host=self.host) + self.server.set_fn_new_client(self.connected) + self.server.set_fn_client_left(self.handle_close) + self.server.set_fn_message_received(self.handle) + + logged = False + while not logged: + try: + f = open("/ws_code.log", "w") + f.write("websocket_code=ready") + f.close() + logged = True + except: + time.sleep(0.1) + + self.server.run_forever() + + +# Execute! +if __name__ == "__main__": + server = Template() + server.run_server() \ No newline at end of file diff --git a/exercises/static/exercises/follow_road/web-template/follow_road.world b/exercises/static/exercises/follow_road/web-template/follow_road.world new file mode 100755 index 000000000..d3d168a68 --- /dev/null +++ b/exercises/static/exercises/follow_road/web-template/follow_road.world @@ -0,0 +1,153 @@ + + + + + + + + false + 0 + 0.4 0.4 0.4 1.0 + 0.7 0.7 0.7 1 + + + 12 + + + + + + + model://sun + + + + + model://grass_plane + + + + + logo + model://logoJdeRobot + -6 -8 0 0 0 0 + + + + model://gas_station + 1 6.43 0 0 0 0 + + + + + + model://polaris_ranger_ev + 4.48 -6 0.1 0 0 0 + true + + + + + + + lampost1 + model://lamp_post + 5 13 0 0 0 0 + + + + lampost2 + model://lamp_post + -4 13 0 0 0 0 + + + + 3 + 15 0 0.01 + 15 5 0.01 + 15 10 0.01 + 15 13 0.01 + 14 15 0.01 + 12 16 0.01 + -12 16 0.01 + -14 15 0.01 + -15 12 0.01 + -15 6 0.01 + -15 3 0.01 + -15 0 0.01 + -16 -2 0.01 + -17 -3 0.01 + -27 -3 0.01 + -28 -4 0.01 + -28 -6 0.01 + -28 -9 0.01 + -28 -12 0.01 + -28 -15 0.01 + -28 -18 0.01 + -28 -20 0.01 + -27 -21 0.01 + -25 -22 0.01 + -24 -22 0.01 + -21 -22 0.01 + -18 -22 0.01 + -15 -22 0.01 + -12 -22 0.01 + -9 -22 0.01 + -6 -22 0.01 + -3 -22 0.01 + 0 -22 0.01 + 6 -22 0.01 + 10 -22 0.01 + 12 -22 0.01 + 13 -21 0.01 + 14 -20 0.01 + 15 -19 0.01 + 15 -18 0.01 + 15 -17 0.01 + 15 -15 0.01 + 15 -12 0.01 + 15 -9 0.01 + 15 -6 0.01 + 15 -3 0.01 + 15 0 0.01 + + + + + 0 0 -9.8066 + + + quick + 10 + 1.3 + 0 + + + 0 + 0.2 + 100 + 0.001 + + + 0.004 + 1 + 250 + 6.0e-6 2.3e-5 -4.2e-5 + + + + diff --git a/exercises/static/exercises/follow_road/web-template/gui.py b/exercises/static/exercises/follow_road/web-template/gui.py new file mode 100755 index 000000000..f4fd34044 --- /dev/null +++ b/exercises/static/exercises/follow_road/web-template/gui.py @@ -0,0 +1,250 @@ +import json +import cv2 +import base64 +import threading +import time +from datetime import datetime +from websocket_server import WebsocketServer +import logging + + +# Graphical User Interface Class +class GUI: + # Initialization function + # The actual initialization + def __init__(self, host): + t = threading.Thread(target=self.run_server) + + self.payload = {'image': ''} + self.left_payload = {'image': ''} + self.server = None + self.client = None + + self.host = host + + # Image variables + self.image_to_be_shown = None + self.image_to_be_shown_updated = False + self.image_show_lock = threading.Lock() + + self.left_image_to_be_shown = None + self.left_image_to_be_shown_updated = False + self.left_image_show_lock = threading.Lock() + + self.acknowledge = False + self.acknowledge_lock = threading.Lock() + + # Take the console object to set the same websocket and client + t.start() + + # Explicit initialization function + # Class method, so user can call it without instantiation + @classmethod + def initGUI(cls, host): + # self.payload = {'image': '', 'shape': []} + new_instance = cls(host) + return new_instance + + # Function to prepare image payload + # Encodes the image as a JSON string and sends through the WS + def payloadImage(self): + self.image_show_lock.acquire() + image_to_be_shown_updated = self.image_to_be_shown_updated + image_to_be_shown = self.image_to_be_shown + self.image_show_lock.release() + + image = image_to_be_shown + payload = {'image': '', 'shape': ''} + + if not image_to_be_shown_updated: + return payload + + shape = image.shape + frame = cv2.imencode('.JPEG', image)[1] + encoded_image = base64.b64encode(frame) + + payload['image'] = encoded_image.decode('utf-8') + payload['shape'] = shape + + self.image_show_lock.acquire() + self.image_to_be_shown_updated = False + self.image_show_lock.release() + + return payload + + # Function to prepare image payload + # Encodes the image as a JSON string and sends through the WS + def payloadLeftImage(self): + self.left_image_show_lock.acquire() + left_image_to_be_shown_updated = self.left_image_to_be_shown_updated + left_image_to_be_shown = self.left_image_to_be_shown + self.left_image_show_lock.release() + + image = left_image_to_be_shown + payload = {'image': '', 'shape': ''} + + if not left_image_to_be_shown_updated: + return payload + + shape = image.shape + frame = cv2.imencode('.JPEG', image)[1] + encoded_image = base64.b64encode(frame) + + payload['image'] = encoded_image.decode('utf-8') + payload['shape'] = shape + + self.left_image_show_lock.acquire() + self.left_image_to_be_shown_updated = False + self.left_image_show_lock.release() + + return payload + + # Function for student to call + def showImage(self, image): + self.image_show_lock.acquire() + self.image_to_be_shown = image + self.image_to_be_shown_updated = True + self.image_show_lock.release() + + # Function for student to call + def showLeftImage(self, image): + self.left_image_show_lock.acquire() + self.left_image_to_be_shown = image + self.left_image_to_be_shown_updated = True + self.left_image_show_lock.release() + + # Function to get the client + # Called when a new client is received + def get_client(self, client, server): + self.client = client + + # Function to get value of Acknowledge + def get_acknowledge(self): + self.acknowledge_lock.acquire() + acknowledge = self.acknowledge + self.acknowledge_lock.release() + + return acknowledge + + # Function to get value of Acknowledge + def set_acknowledge(self, value): + self.acknowledge_lock.acquire() + self.acknowledge = value + self.acknowledge_lock.release() + + # Update the gui + def update_gui(self): + # Payload Image Message + payload = self.payloadImage() + self.payload["image"] = json.dumps(payload) + + message = "#gui" + json.dumps(self.payload) + self.server.send_message(self.client, message) + + # Payload Left Image Message + left_payload = self.payloadLeftImage() + self.left_payload["image"] = json.dumps(left_payload) + + message = "#gul" + json.dumps(self.left_payload) + self.server.send_message(self.client, message) + + # Function to read the message from websocket + # Gets called when there is an incoming message from the client + def get_message(self, client, server, message): + # Acknowledge Message for GUI Thread + if message[:4] == "#ack": + self.set_acknowledge(True) + + + # Activate the server + def run_server(self): + self.server = WebsocketServer(port=2303, host=self.host) + self.server.set_fn_new_client(self.get_client) + self.server.set_fn_message_received(self.get_message) + + logged = False + while not logged: + try: + f = open("/ws_gui.log", "w") + f.write("websocket_gui=ready") + f.close() + logged = True + except: + time.sleep(0.1) + + self.server.run_forever() + + # Function to reset + def reset_gui(self): + pass + + +# This class decouples the user thread +# and the GUI update thread +class ThreadGUI: + def __init__(self, gui): + self.gui = gui + + # Time variables + self.ideal_cycle = 80 + self.measured_cycle = 80 + self.iteration_counter = 0 + + # Function to start the execution of threads + def start(self): + self.measure_thread = threading.Thread(target=self.measure_thread) + self.thread = threading.Thread(target=self.run) + + self.measure_thread.start() + self.thread.start() + + print("GUI Thread Started!") + + # The measuring thread to measure frequency + def measure_thread(self): + while self.gui.client is None: + pass + + previous_time = datetime.now() + while True: + # Sleep for 2 seconds + time.sleep(2) + + # Measure the current time and subtract from previous time to get real time interval + current_time = datetime.now() + dt = current_time - previous_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + previous_time = current_time + + # Get the time period + try: + # Division by zero + self.measured_cycle = ms / self.iteration_counter + except: + self.measured_cycle = 0 + + # Reset the counter + self.iteration_counter = 0 + + # The main thread of execution + def run(self): + while self.gui.client is None: + pass + + while True: + start_time = datetime.now() + self.gui.update_gui() + acknowledge_message = self.gui.get_acknowledge() + + while not acknowledge_message: + acknowledge_message = self.gui.get_acknowledge() + + self.gui.set_acknowledge(False) + + finish_time = datetime.now() + self.iteration_counter = self.iteration_counter + 1 + + dt = finish_time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + if ms < self.ideal_cycle: + time.sleep((self.ideal_cycle-ms) / 1000.0) diff --git a/exercises/static/exercises/follow_road/web-template/hal.py b/exercises/static/exercises/follow_road/web-template/hal.py new file mode 100755 index 000000000..fd13904d6 --- /dev/null +++ b/exercises/static/exercises/follow_road/web-template/hal.py @@ -0,0 +1,84 @@ +import rospy +import cv2 +import threading +import time +from datetime import datetime + +from drone_wrapper import DroneWrapper + + +# Hardware Abstraction Layer +class HAL: + IMG_WIDTH = 320 + IMG_HEIGHT = 240 + + def __init__(self): + rospy.init_node("HAL") + + self.image = None + self.drone = DroneWrapper(name="rqt") + + # Explicit initialization functions + # Class method, so user can call it without instantiation + @classmethod + def initRobot(cls): + new_instance = cls() + return new_instance + + # Get Image from ROS Driver Camera + def get_frontal_image(self): + image = self.drone.get_frontal_image() + image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) + return image_rgb + + def get_ventral_image(self): + image = self.drone.get_ventral_image() + image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) + return image_rgb + + def get_position(self): + pos = self.drone.get_position() + return pos + + def get_velocity(self): + vel = self.drone.get_velocity() + return vel + + def get_yaw_rate(self): + yaw_rate = self.drone.get_yaw_rate() + return yaw_rate + + def get_orientation(self): + orientation = self.drone.get_orientation() + return orientation + + def get_roll(self): + roll = self.drone.get_roll() + return roll + + def get_pitch(self): + pitch = self.drone.get_pitch() + return pitch + + def get_yaw(self): + yaw = self.drone.get_yaw() + return yaw + + def get_landed_state(self): + state = self.drone.get_landed_state() + return state + + def set_cmd_pos(self, x, y, z, az): + self.drone.set_cmd_pos(x, y, z, az) + + def set_cmd_vel(self, vx, vy, vz, az): + self.drone.set_cmd_vel(vx, vy, vz, az) + + def set_cmd_mix(self, vx, vy, z, az): + self.drone.set_cmd_mix(vx, vy, z, az) + + def takeoff(self, h=3): + self.drone.takeoff(h) + + def land(self): + self.drone.land() diff --git a/exercises/static/exercises/follow_road/web-template/interfaces/__init__.py b/exercises/static/exercises/follow_road/web-template/interfaces/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/exercises/static/exercises/follow_road/web-template/interfaces/camera.py b/exercises/static/exercises/follow_road/web-template/interfaces/camera.py new file mode 100644 index 000000000..5a021a13e --- /dev/null +++ b/exercises/static/exercises/follow_road/web-template/interfaces/camera.py @@ -0,0 +1,89 @@ +import rospy +from sensor_msgs.msg import Image as ImageROS +import threading +from math import pi as PI +import cv2 +from cv_bridge import CvBridge, CvBridgeError + + +MAXRANGE = 8 # max length received from imageD +MINRANGE = 0 + + +def imageMsg2Image(img, bridge): + + image = Image() + + image.width = img.width + image.height = img.height + image.format = "BGR8" + image.timeStamp = img.header.stamp.secs + (img.header.stamp.nsecs * 1e-9) + cv_image = 0 + if img.encoding[-2:] == "C1": + gray_img_buff = bridge.imgmsg_to_cv2(img, img.encoding) + cv_image = depthToRGB8(gray_img_buff, img.encoding) + else: + cv_image = bridge.imgmsg_to_cv2(img, "bgr8") + image.data = cv_image + return image + + +import numpy as np + + +class Image: + + def __init__(self): + + self.height = 3 # Image height [pixels] + self.width = 3 # Image width [pixels] + self.timeStamp = 0 # Time stamp [s] */ + self.format = "" # Image format string (RGB8, BGR,...) + self.data = np.zeros((self.height, self.width, 3), np.uint8) # The image data itself + self.data.shape = self.height, self.width, 3 + + def __str__(self): + s = "Image: {\n height: " + str(self.height) + "\n width: " + str(self.width) + s = s + "\n format: " + self.format + "\n timeStamp: " + str(self.timeStamp) + s = s + "\n data: " + str(self.data) + "\n}" + return s + + +class ListenerCamera: + + def __init__(self, topic): + + self.topic = topic + self.data = Image() + self.sub = None + self.lock = threading.Lock() + + self.bridge = CvBridge() + self.start() + + def __callback(self, img): + + image = imageMsg2Image(img, self.bridge) + + self.lock.acquire() + self.data = image + self.lock.release() + + def stop(self): + + self.sub.unregister() + + def start(self): + self.sub = rospy.Subscriber(self.topic, ImageROS, self.__callback) + + def getImage(self): + + self.lock.acquire() + image = self.data + self.lock.release() + + return image + + def hasproxy(self): + + return hasattr(self, "sub") and self.sub diff --git a/exercises/static/exercises/follow_road/web-template/interfaces/motors.py b/exercises/static/exercises/follow_road/web-template/interfaces/motors.py new file mode 100644 index 000000000..70dca8a46 --- /dev/null +++ b/exercises/static/exercises/follow_road/web-template/interfaces/motors.py @@ -0,0 +1,123 @@ +import rospy +from geometry_msgs.msg import Twist +import threading +from math import pi as PI +from .threadPublisher import ThreadPublisher + + + +def cmdvel2Twist(vel): + + tw = Twist() + tw.linear.x = vel.vx + tw.linear.y = vel.vy + tw.linear.z = vel.vz + tw.angular.x = vel.ax + tw.angular.y = vel.ay + tw.angular.z = vel.az + + return tw + + +class CMDVel (): + + def __init__(self): + + self.vx = 0 # vel in x[m/s] (use this for V in wheeled robots) + self.vy = 0 # vel in y[m/s] + self.vz = 0 # vel in z[m/s] + self.ax = 0 # angular vel in X axis [rad/s] + self.ay = 0 # angular vel in X axis [rad/s] + self.az = 0 # angular vel in Z axis [rad/s] (use this for W in wheeled robots) + self.timeStamp = 0 # Time stamp [s] + + + def __str__(self): + s = "CMDVel: {\n vx: " + str(self.vx) + "\n vy: " + str(self.vy) + s = s + "\n vz: " + str(self.vz) + "\n ax: " + str(self.ax) + s = s + "\n ay: " + str(self.ay) + "\n az: " + str(self.az) + s = s + "\n timeStamp: " + str(self.timeStamp) + "\n}" + return s + +class PublisherMotors: + + def __init__(self, topic, maxV, maxW): + + self.maxW = maxW + self.maxV = maxV + + self.topic = topic + self.data = CMDVel() + self.pub = rospy.Publisher(self.topic, Twist, queue_size=1) + + self.lock = threading.Lock() + + self.kill_event = threading.Event() + self.thread = ThreadPublisher(self, self.kill_event) + + self.thread.daemon = True + self.start() + + def publish (self): + + self.lock.acquire() + tw = cmdvel2Twist(self.data) + self.lock.release() + self.pub.publish(tw) + + def stop(self): + + self.kill_event.set() + self.pub.unregister() + + def start (self): + + self.kill_event.clear() + self.thread.start() + + + + def getMaxW(self): + return self.maxW + + def getMaxV(self): + return self.maxV + + + def sendVelocities(self, vel): + + self.lock.acquire() + self.data = vel + self.lock.release() + + def sendV(self, v): + + self.sendVX(v) + + def sendL(self, l): + + self.sendVY(l) + + def sendW(self, w): + + self.sendAZ(w) + + def sendVX(self, vx): + + self.lock.acquire() + self.data.vx = vx + self.lock.release() + + def sendVY(self, vy): + + self.lock.acquire() + self.data.vy = vy + self.lock.release() + + def sendAZ(self, az): + + self.lock.acquire() + self.data.az = az + self.lock.release() + + diff --git a/exercises/static/exercises/follow_road/web-template/interfaces/pose3d.py b/exercises/static/exercises/follow_road/web-template/interfaces/pose3d.py new file mode 100644 index 000000000..fd0bfc37a --- /dev/null +++ b/exercises/static/exercises/follow_road/web-template/interfaces/pose3d.py @@ -0,0 +1,176 @@ +import rospy +import threading +from math import asin, atan2, pi +from nav_msgs.msg import Odometry + +def quat2Yaw(qw, qx, qy, qz): + ''' + Translates from Quaternion to Yaw. + + @param qw,qx,qy,qz: Quaternion values + + @type qw,qx,qy,qz: float + + @return Yaw value translated from Quaternion + + ''' + rotateZa0=2.0*(qx*qy + qw*qz) + rotateZa1=qw*qw + qx*qx - qy*qy - qz*qz + rotateZ=0.0 + if(rotateZa0 != 0.0 and rotateZa1 != 0.0): + rotateZ=atan2(rotateZa0,rotateZa1) + return rotateZ + +def quat2Pitch(qw, qx, qy, qz): + ''' + Translates from Quaternion to Pitch. + + @param qw,qx,qy,qz: Quaternion values + + @type qw,qx,qy,qz: float + + @return Pitch value translated from Quaternion + + ''' + + rotateYa0=-2.0*(qx*qz - qw*qy) + rotateY=0.0 + if(rotateYa0 >= 1.0): + rotateY = pi/2.0 + elif(rotateYa0 <= -1.0): + rotateY = -pi/2.0 + else: + rotateY = asin(rotateYa0) + + return rotateY + +def quat2Roll (qw, qx, qy, qz): + ''' + Translates from Quaternion to Roll. + + @param qw,qx,qy,qz: Quaternion values + + @type qw,qx,qy,qz: float + + @return Roll value translated from Quaternion + + ''' + rotateXa0=2.0*(qy*qz + qw*qx) + rotateXa1=qw*qw - qx*qx - qy*qy + qz*qz + rotateX=0.0 + + if(rotateXa0 != 0.0 and rotateXa1 != 0.0): + rotateX=atan2(rotateXa0, rotateXa1) + return rotateX + + +def odometry2Pose3D(odom): + ''' + Translates from ROS Odometry to JderobotTypes Pose3d. + + @param odom: ROS Odometry to translate + + @type odom: Odometry + + @return a Pose3d translated from odom + + ''' + pose = Pose3d() + ori = odom.pose.pose.orientation + + pose.x = odom.pose.pose.position.x + pose.y = odom.pose.pose.position.y + pose.z = odom.pose.pose.position.z + #pose.h = odom.pose.pose.position.h + pose.yaw = quat2Yaw(ori.w, ori.x, ori.y, ori.z) + pose.pitch = quat2Pitch(ori.w, ori.x, ori.y, ori.z) + pose.roll = quat2Roll(ori.w, ori.x, ori.y, ori.z) + pose.q = [ori.w, ori.x, ori.y, ori.z] + pose.timeStamp = odom.header.stamp.secs + (odom.header.stamp.nsecs *1e-9) + + return pose + +class Pose3d (): + + def __init__(self): + + self.x = 0 # X coord [meters] + self.y = 0 # Y coord [meters] + self.z = 0 # Z coord [meters] + self.h = 1 # H param + self.yaw = 0 #Yaw angle[rads] + self.pitch = 0 # Pitch angle[rads] + self.roll = 0 # Roll angle[rads] + self.q = [0,0,0,0] # Quaternion + self.timeStamp = 0 # Time stamp [s] + + + def __str__(self): + s = "Pose3D: {\n x: " + str(self.x) + "\n Y: " + str(self.y) + s = s + "\n Z: " + str(self.z) + "\n H: " + str(self.h) + s = s + "\n Yaw: " + str(self.yaw) + "\n Pitch: " + str(self.pitch) + "\n Roll: " + str(self.roll) + s = s + "\n quaternion: " + str(self.q) + "\n timeStamp: " + str(self.timeStamp) + "\n}" + return s + + +class ListenerPose3d: + ''' + ROS Pose3D Subscriber. Pose3D Client to Receive pose3d from ROS nodes. + ''' + def __init__(self, topic): + ''' + ListenerPose3d Constructor. + + @param topic: ROS topic to subscribe + + @type topic: String + + ''' + self.topic = topic + self.data = Pose3d() + self.sub = None + self.lock = threading.Lock() + self.start() + + def __callback (self, odom): + ''' + Callback function to receive and save Pose3d. + + @param odom: ROS Odometry received + + @type odom: Odometry + + ''' + pose = odometry2Pose3D(odom) + + self.lock.acquire() + self.data = pose + self.lock.release() + + def stop(self): + ''' + Stops (Unregisters) the client. + + ''' + self.sub.unregister() + + def start (self): + ''' + Starts (Subscribes) the client. + + ''' + self.sub = rospy.Subscriber(self.topic, Odometry, self.__callback) + + def getPose3d(self): + ''' + Returns last Pose3d. + + @return last JdeRobotTypes Pose3d saved + + ''' + self.lock.acquire() + pose = self.data + self.lock.release() + + return pose + diff --git a/exercises/static/exercises/follow_road/web-template/interfaces/threadPublisher.py b/exercises/static/exercises/follow_road/web-template/interfaces/threadPublisher.py new file mode 100644 index 000000000..69aa0ad48 --- /dev/null +++ b/exercises/static/exercises/follow_road/web-template/interfaces/threadPublisher.py @@ -0,0 +1,46 @@ +# +# Copyright (C) 1997-2016 JDE Developers Team +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. +# Authors : +# Alberto Martin Florido +# Aitor Martinez Fernandez +# +import threading +import time +from datetime import datetime + +time_cycle = 80 + + +class ThreadPublisher(threading.Thread): + + def __init__(self, pub, kill_event): + self.pub = pub + self.kill_event = kill_event + threading.Thread.__init__(self, args=kill_event) + + def run(self): + while (not self.kill_event.is_set()): + start_time = datetime.now() + + self.pub.publish() + + finish_Time = datetime.now() + + dt = finish_Time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + #print (ms) + if (ms < time_cycle): + time.sleep((time_cycle - ms) / 1000.0) \ No newline at end of file diff --git a/exercises/static/exercises/follow_road/web-template/launch/gazebo.launch b/exercises/static/exercises/follow_road/web-template/launch/gazebo.launch new file mode 100644 index 000000000..0f72c6a7f --- /dev/null +++ b/exercises/static/exercises/follow_road/web-template/launch/gazebo.launch @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/exercises/static/exercises/follow_road/web-template/launch/launch.py b/exercises/static/exercises/follow_road/web-template/launch/launch.py new file mode 100644 index 000000000..796a5f751 --- /dev/null +++ b/exercises/static/exercises/follow_road/web-template/launch/launch.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python3 + +import stat +import rospy +from os import lstat +from subprocess import Popen, PIPE + + +DRI_PATH = "/dev/dri/card0" +EXERCISE = "follow_road" +TIMEOUT = 30 +MAX_ATTEMPT = 2 + + +# Check if acceleration can be enabled +def check_device(device_path): + try: + return stat.S_ISCHR(lstat(device_path)[stat.ST_MODE]) + except: + return False + + +# Spawn new process +def spawn_process(args, insert_vglrun=False): + if insert_vglrun: + args.insert(0, "vglrun") + process = Popen(args, stdout=PIPE, bufsize=1, universal_newlines=True) + return process + + +class Test(): + def gazebo(self): + rospy.logwarn("[GAZEBO] Launching") + try: + rospy.wait_for_service("/gazebo/get_model_properties", TIMEOUT) + return True + except rospy.ROSException: + return False + + def px4(self): + rospy.logwarn("[PX4-SITL] Launching") + start_time = rospy.get_time() + args = ["./PX4-Autopilot/build/px4_sitl_default/bin/px4-commander","--instance", "0", "check"] + while rospy.get_time() - start_time < TIMEOUT: + process = spawn_process(args, insert_vglrun=False) + with process.stdout: + for line in iter(process.stdout.readline, ''): + if ("Prearm check: OK" in line): + return True + rospy.sleep(2) + return False + + def mavros(self, ns=""): + rospy.logwarn("[MAVROS] Launching") + try: + rospy.wait_for_service(ns + "/mavros/cmd/arming", TIMEOUT) + return True + except rospy.ROSException: + return False + + +class Launch(): + def __init__(self): + self.test = Test() + self.acceleration_enabled = check_device(DRI_PATH) + + # Start roscore + args = ["/opt/ros/noetic/bin/roscore"] + spawn_process(args, insert_vglrun=False) + + rospy.init_node("launch", anonymous=True) + + def start(self): + ######## LAUNCH GAZEBO ######## + args = ["/opt/ros/noetic/bin/roslaunch", + "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/gazebo.launch", + "--wait", + "--log" + ] + + attempt = 1 + while True: + spawn_process(args, insert_vglrun=self.acceleration_enabled) + if self.test.gazebo() == True: + break + if attempt == MAX_ATTEMPT: + rospy.logerr("[GAZEBO] Launch Failed") + return + attempt = attempt + 1 + + + ######## LAUNCH PX4 ######## + args = ["/opt/ros/noetic/bin/roslaunch", + "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/px4.launch", + "--log" + ] + + attempt = 1 + while True: + spawn_process(args, insert_vglrun=self.acceleration_enabled) + if self.test.px4() == True: + break + if attempt == MAX_ATTEMPT: + rospy.logerr("[PX4] Launch Failed") + return + attempt = attempt + 1 + + + ######## LAUNCH MAVROS ######## + args = ["/opt/ros/noetic/bin/roslaunch", + "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/mavros.launch", + "--log" + ] + + attempt = 1 + while True: + spawn_process(args, insert_vglrun=self.acceleration_enabled) + if self.test.mavros() == True: + break + if attempt == MAX_ATTEMPT: + rospy.logerr("[MAVROS] Launch Failed") + return + attempt = attempt + 1 + + +if __name__ == "__main__": + launch = Launch() + launch.start() + + with open("/drones_launch.log", "w") as f: + f.write("success") diff --git a/exercises/static/exercises/follow_road/web-template/launch/mavros.launch b/exercises/static/exercises/follow_road/web-template/launch/mavros.launch new file mode 100644 index 000000000..b899c0ec1 --- /dev/null +++ b/exercises/static/exercises/follow_road/web-template/launch/mavros.launch @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/exercises/static/exercises/follow_road/web-template/launch/px4.launch b/exercises/static/exercises/follow_road/web-template/launch/px4.launch new file mode 100644 index 000000000..2fa0fedc9 --- /dev/null +++ b/exercises/static/exercises/follow_road/web-template/launch/px4.launch @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 76218d63058cb3990159f2c9a6cdbf35601b5d5b Mon Sep 17 00:00:00 2001 From: Pedro Arias Date: Sun, 2 Oct 2022 13:33:14 +0200 Subject: [PATCH 40/42] rename drone exercises as exercise_rotors --- .../README.md | 0 .../autonomous_mouse.launch | 0 .../autonomous_mouse.py | 0 .../drone_cat_mouse.launch | 0 .../drone_cat_mouse.world | 0 .../drone_cat_mouse.yaml | 0 .../my_solution.py | 0 .../teleoperated_mouse.py | 0 .../web-template/RADI-launch | 0 .../web-template/README.md | 0 .../web-template/brain.py | 0 .../web-template/brain_guest.py | 0 .../web-template/code/academy.py | 0 .../web-template/console.py | 0 .../web-template/drone_cat_mouse.world | 0 .../web-template/drone_cat_mouse.yaml | 0 .../web-template/exercise.py | 0 .../web-template/exercise_guest.py | 0 .../web-template/gui.py | 0 .../web-template/gui_guest.py | 0 .../web-template/hal.py | 0 .../web-template/hal_guest.py | 0 .../web-template/interfaces/__init__.py | 0 .../web-template/interfaces/autonomous_mouse.py | 0 .../web-template/interfaces/camera.py | 0 .../web-template/interfaces/motors.py | 0 .../web-template/interfaces/pose3d.py | 0 .../web-template/interfaces/threadPublisher.py | 0 .../web-template/interfaces/threadStoppable.py | 0 .../web-template/launch/drone_cat_mouse.launch | 0 .../web-template/shared/__init__.py | 0 .../web-template/shared/image.py | 0 .../web-template/shared/mouse.py | 0 .../web-template/shared/structure_img.py | 0 .../web-template/shared/value.py | 0 .../web-template/user_functions.py | 0 .../web-template/user_functions_guest.py | 0 .../README.md | 0 .../drone_gymkhana.launch | 0 .../drone_gymkhana.world | 0 .../my_solution.py | 0 .../web-template/RADI-launch | 0 .../web-template/README.md | 0 .../web-template/brain.py | 0 .../web-template/code/academy.py | 0 .../web-template/console.py | 0 .../web-template/drone_gymkhana.world | 0 .../web-template/exercise.py | 0 .../web-template/gui.py | 0 .../web-template/hal.py | 0 .../web-template/interfaces/__init__.py | 0 .../web-template/interfaces/camera.py | 0 .../web-template/interfaces/motors.py | 0 .../web-template/interfaces/pose3d.py | 0 .../web-template/interfaces/threadPublisher.py | 0 .../web-template/interfaces/threadStoppable.py | 0 .../web-template/launch/drone_gymkhana.launch | 0 .../web-template/shared/__init__.py | 0 .../web-template/shared/image.py | 0 .../web-template/shared/structure_img.py | 0 .../web-template/shared/value.py | 0 .../web-template/user_functions.py | 0 .../{drone_hangar => drone_hangar_rotors}/README.md | 0 .../drone_hangar.launch | 0 .../drone_hangar.world | 0 .../my_solution.py | 0 .../web-template/RADI-launch | 0 .../web-template/README.md | 0 .../web-template/brain.py | 0 .../web-template/code/academy.py | 0 .../web-template/console.py | 0 .../web-template/drone_hangar.world | 0 .../web-template/exercise.py | 0 .../web-template/gui.py | 0 .../web-template/hal.py | 0 .../web-template/interfaces/__init__.py | 0 .../web-template/interfaces/camera.py | 0 .../web-template/interfaces/motors.py | 0 .../web-template/interfaces/pose3d.py | 0 .../web-template/interfaces/threadPublisher.py | 0 .../web-template/interfaces/threadStoppable.py | 0 .../web-template/launch/drone_hangar.launch | 0 .../web-template/shared/__init__.py | 0 .../web-template/shared/image.py | 0 .../web-template/shared/structure_img.py | 0 .../web-template/shared/value.py | 0 .../web-template/user_functions.py | 0 .../{follow_road_2 => follow_road_rotors}/README.md | 0 .../follow_road.launch | 0 .../follow_road.world | 0 .../my_solution.py | 0 .../web-template/RADI-launch | 0 .../web-template/README.md | 0 .../web-template/brain.py | 0 .../web-template/code/academy.py | 0 .../web-template/console.py | 0 .../web-template/exercise.py | 0 .../web-template/follow_road.world | 0 .../web-template/gui.py | 0 .../web-template/hal.py | 0 .../web-template/interfaces/__init__.py | 0 .../web-template/interfaces/camera.py | 0 .../web-template/interfaces/motors.py | 0 .../web-template/interfaces/pose3d.py | 0 .../web-template/interfaces/threadPublisher.py | 0 .../web-template/launch/follow_road.launch | 0 .../web-template/shared/__init__.py | 0 .../web-template/shared/image.py | 0 .../web-template/shared/structure_img.py | 0 .../web-template/shared/value.py | 0 .../web-template/user_functions.py | 0 .../README.md | 0 .../follow_turtlebot.launch | 0 .../follow_turtlebot.world | 0 .../my_solution.py | 0 .../web-template/RADI-launch | 0 .../web-template/README.md | 0 .../web-template/brain.py | 0 .../web-template/code/academy.py | 0 .../web-template/console.py | 0 .../web-template/exercise.py | 0 .../web-template/follow_turtlebot.world | 0 .../web-template/gui.py | 0 .../web-template/hal.py | 0 .../web-template/interfaces/__init__.py | 0 .../web-template/interfaces/camera.py | 0 .../web-template/interfaces/motors.py | 0 .../web-template/interfaces/pose3d.py | 0 .../web-template/interfaces/threadPublisher.py | 0 .../web-template/interfaces/threadStoppable.py | 0 .../web-template/launch/follow_turtlebot.launch | 0 .../web-template/shared/__init__.py | 0 .../web-template/shared/image.py | 0 .../web-template/shared/structure_img.py | 0 .../web-template/shared/turtlebot.py | 0 .../web-template/shared/value.py | 0 .../web-template/user_functions.py | 0 .../README.md | 0 .../arrows/down.png | Bin .../arrows/left.png | Bin .../arrows/right.png | Bin .../arrows/up.png | Bin .../labyrinth.world | 0 .../labyrinth_escape.launch | 0 .../my_solution.py | 0 .../web-template/RADI-launch | 0 .../web-template/README.md | 0 .../web-template/brain.py | 0 .../web-template/code/academy.py | 0 .../web-template/console.py | 0 .../web-template/exercise.py | 0 .../web-template/gui.py | 0 .../web-template/hal.py | 0 .../web-template/interfaces/__init__.py | 0 .../web-template/interfaces/camera.py | 0 .../web-template/interfaces/motors.py | 0 .../web-template/interfaces/pose3d.py | 0 .../web-template/interfaces/threadPublisher.py | 0 .../web-template/interfaces/threadStoppable.py | 0 .../web-template/labyrinth_escape.world | 0 .../web-template/launch/labyrinth_escape.launch | 0 .../web-template/shared/__init__.py | 0 .../web-template/shared/image.py | 0 .../web-template/shared/structure_img.py | 0 .../web-template/shared/value.py | 0 .../web-template/user_functions.py | 0 .../README.md | 0 .../magnet.py | 0 .../my_solution.py | 0 .../package_delivery.launch | 0 .../package_delivery.world | 0 .../package_delivery.yaml | 0 .../web-template/README.md | 0 .../web-template/brain.py | 0 .../web-template/code/academy.py | 0 .../web-template/console.py | 0 .../web-template/exercise.py | 0 .../web-template/gui.py | 0 .../web-template/hal.py | 0 .../web-template/interfaces/__init__.py | 0 .../web-template/interfaces/camera.py | 0 .../web-template/interfaces/motors.py | 0 .../web-template/interfaces/pose3d.py | 0 .../web-template/interfaces/threadPublisher.py | 0 .../web-template/launch/package_delivery.launch | 0 .../web-template/package_delivery.world | 0 .../web-template/shared/__init__.py | 0 .../web-template/shared/image.py | 0 .../web-template/shared/magnet.py | 0 .../web-template/shared/structure_img.py | 0 .../web-template/shared/value.py | 0 .../web-template/user_functions.py | 0 .../Beacon.py | 0 .../README.md | 0 .../my_solution.py | 0 .../position_control.launch | 0 .../position_control.world | 0 .../web-template/RADI-launch | 0 .../web-template/README.md | 0 .../web-template/brain.py | 0 .../web-template/code/academy.py | 0 .../web-template/console.py | 0 .../web-template/exercise.py | 0 .../web-template/gui.py | 0 .../web-template/hal.py | 0 .../web-template/interfaces/__init__.py | 0 .../web-template/interfaces/camera.py | 0 .../web-template/interfaces/motors.py | 0 .../web-template/interfaces/pose3d.py | 0 .../web-template/interfaces/threadPublisher.py | 0 .../web-template/interfaces/threadStoppable.py | 0 .../web-template/launch/position_control.launch | 0 .../web-template/position_control.world | 0 .../web-template/shared/Beacon.py | 0 .../web-template/shared/__init__.py | 0 .../web-template/shared/image.py | 0 .../web-template/shared/structure_img.py | 0 .../web-template/shared/value.py | 0 .../web-template/user_functions.py | 0 .../README.md | 0 .../my_solution.py | 0 .../power_tower_inspection.launch | 0 .../power_tower_inspection.world | 0 .../web-template/README.md | 0 .../web-template/brain.py | 0 .../web-template/code/academy.py | 0 .../web-template/console.py | 0 .../web-template/exercise.py | 0 .../web-template/gui.py | 0 .../web-template/hal.py | 0 .../web-template/interfaces/__init__.py | 0 .../web-template/interfaces/camera.py | 0 .../web-template/interfaces/motors.py | 0 .../web-template/interfaces/pose3d.py | 0 .../web-template/interfaces/threadPublisher.py | 0 .../launch/power_tower_inspection.launch | 0 .../web-template/power_tower_inspection.world | 0 .../web-template/shared/__init__.py | 0 .../web-template/shared/image.py | 0 .../web-template/shared/structure_img.py | 0 .../web-template/shared/value.py | 0 .../web-template/user_functions.py | 0 .../README.md | 0 .../haarcascade_frontalface_default.xml | 0 .../my_solution.py | 0 .../rescue_people.launch | 0 .../rescue_people.world | 0 .../web-template/RADI-launch | 0 .../web-template/README.md | 0 .../web-template/brain.py | 0 .../web-template/code/academy.py | 0 .../web-template/console.py | 0 .../web-template/exercise.py | 0 .../web-template/gui.py | 0 .../web-template/hal.py | 0 .../web-template/interfaces/__init__.py | 0 .../web-template/interfaces/camera.py | 0 .../web-template/interfaces/motors.py | 0 .../web-template/interfaces/pose3d.py | 0 .../web-template/interfaces/threadPublisher.py | 0 .../web-template/interfaces/threadStoppable.py | 0 .../web-template/launch/rescue_people.launch | 0 .../web-template/launch/rescue_people.sh | 0 .../web-template/rescue_people.world | 0 .../web-template/shared/__init__.py | 0 .../web-template/shared/image.py | 0 .../web-template/shared/light.py | 0 .../web-template/shared/structure_img.py | 0 .../web-template/shared/value.py | 0 .../web-template/user_functions.py | 0 .../README.md | 0 .../my_solution.py | 0 .../visual_lander.launch | 0 .../visual_lander.world | 0 .../web-template/RADI-launch | 0 .../web-template/README.md | 0 .../web-template/brain.py | 0 .../web-template/code/academy.py | 0 .../web-template/console.py | 0 .../web-template/exercise.py | 0 .../web-template/gui.py | 0 .../web-template/hal.py | 0 .../web-template/interfaces/__init__.py | 0 .../web-template/interfaces/camera.py | 0 .../web-template/interfaces/motors.py | 0 .../web-template/interfaces/pose3d.py | 0 .../web-template/interfaces/threadPublisher.py | 0 .../web-template/interfaces/threadStoppable.py | 0 .../web-template/launch/visual_lander.launch | 0 .../web-template/shared/__init__.py | 0 .../web-template/shared/car.py | 0 .../web-template/shared/image.py | 0 .../web-template/shared/structure_img.py | 0 .../web-template/shared/value.py | 0 .../web-template/user_functions.py | 0 .../web-template/visual_lander.world | 0 296 files changed, 0 insertions(+), 0 deletions(-) rename exercises/static/exercises/{drone_cat_mouse => drone_cat_mouse_rotors}/README.md (100%) rename exercises/static/exercises/{drone_cat_mouse => drone_cat_mouse_rotors}/autonomous_mouse.launch (100%) rename exercises/static/exercises/{drone_cat_mouse => drone_cat_mouse_rotors}/autonomous_mouse.py (100%) rename exercises/static/exercises/{drone_cat_mouse => drone_cat_mouse_rotors}/drone_cat_mouse.launch (100%) rename exercises/static/exercises/{drone_cat_mouse => drone_cat_mouse_rotors}/drone_cat_mouse.world (100%) rename exercises/static/exercises/{drone_cat_mouse => drone_cat_mouse_rotors}/drone_cat_mouse.yaml (100%) rename exercises/static/exercises/{drone_cat_mouse => drone_cat_mouse_rotors}/my_solution.py (100%) rename exercises/static/exercises/{drone_cat_mouse => drone_cat_mouse_rotors}/teleoperated_mouse.py (100%) rename exercises/static/exercises/{drone_cat_mouse => drone_cat_mouse_rotors}/web-template/RADI-launch (100%) rename exercises/static/exercises/{drone_cat_mouse => drone_cat_mouse_rotors}/web-template/README.md (100%) rename exercises/static/exercises/{drone_cat_mouse => drone_cat_mouse_rotors}/web-template/brain.py (100%) rename exercises/static/exercises/{drone_cat_mouse => drone_cat_mouse_rotors}/web-template/brain_guest.py (100%) rename exercises/static/exercises/{drone_cat_mouse => drone_cat_mouse_rotors}/web-template/code/academy.py (100%) rename exercises/static/exercises/{drone_cat_mouse => drone_cat_mouse_rotors}/web-template/console.py (100%) rename exercises/static/exercises/{drone_cat_mouse => drone_cat_mouse_rotors}/web-template/drone_cat_mouse.world (100%) rename exercises/static/exercises/{drone_cat_mouse => drone_cat_mouse_rotors}/web-template/drone_cat_mouse.yaml (100%) rename exercises/static/exercises/{drone_cat_mouse => drone_cat_mouse_rotors}/web-template/exercise.py (100%) rename exercises/static/exercises/{drone_cat_mouse => drone_cat_mouse_rotors}/web-template/exercise_guest.py (100%) rename exercises/static/exercises/{drone_cat_mouse => drone_cat_mouse_rotors}/web-template/gui.py (100%) rename exercises/static/exercises/{drone_cat_mouse => drone_cat_mouse_rotors}/web-template/gui_guest.py (100%) rename exercises/static/exercises/{drone_cat_mouse => drone_cat_mouse_rotors}/web-template/hal.py (100%) rename exercises/static/exercises/{drone_cat_mouse => drone_cat_mouse_rotors}/web-template/hal_guest.py (100%) rename exercises/static/exercises/{drone_cat_mouse => drone_cat_mouse_rotors}/web-template/interfaces/__init__.py (100%) rename exercises/static/exercises/{drone_cat_mouse => drone_cat_mouse_rotors}/web-template/interfaces/autonomous_mouse.py (100%) rename exercises/static/exercises/{drone_cat_mouse => drone_cat_mouse_rotors}/web-template/interfaces/camera.py (100%) rename exercises/static/exercises/{drone_cat_mouse => drone_cat_mouse_rotors}/web-template/interfaces/motors.py (100%) rename exercises/static/exercises/{drone_cat_mouse => drone_cat_mouse_rotors}/web-template/interfaces/pose3d.py (100%) rename exercises/static/exercises/{drone_cat_mouse => drone_cat_mouse_rotors}/web-template/interfaces/threadPublisher.py (100%) rename exercises/static/exercises/{drone_cat_mouse => drone_cat_mouse_rotors}/web-template/interfaces/threadStoppable.py (100%) rename exercises/static/exercises/{drone_cat_mouse => drone_cat_mouse_rotors}/web-template/launch/drone_cat_mouse.launch (100%) rename exercises/static/exercises/{drone_cat_mouse => drone_cat_mouse_rotors}/web-template/shared/__init__.py (100%) rename exercises/static/exercises/{drone_cat_mouse => drone_cat_mouse_rotors}/web-template/shared/image.py (100%) rename exercises/static/exercises/{drone_cat_mouse => drone_cat_mouse_rotors}/web-template/shared/mouse.py (100%) rename exercises/static/exercises/{drone_cat_mouse => drone_cat_mouse_rotors}/web-template/shared/structure_img.py (100%) rename exercises/static/exercises/{drone_cat_mouse => drone_cat_mouse_rotors}/web-template/shared/value.py (100%) rename exercises/static/exercises/{drone_cat_mouse => drone_cat_mouse_rotors}/web-template/user_functions.py (100%) rename exercises/static/exercises/{drone_cat_mouse => drone_cat_mouse_rotors}/web-template/user_functions_guest.py (100%) rename exercises/static/exercises/{drone_gymkhana => drone_gymkhana_rotors}/README.md (100%) rename exercises/static/exercises/{drone_gymkhana => drone_gymkhana_rotors}/drone_gymkhana.launch (100%) rename exercises/static/exercises/{drone_gymkhana => drone_gymkhana_rotors}/drone_gymkhana.world (100%) rename exercises/static/exercises/{drone_gymkhana => drone_gymkhana_rotors}/my_solution.py (100%) rename exercises/static/exercises/{drone_gymkhana => drone_gymkhana_rotors}/web-template/RADI-launch (100%) rename exercises/static/exercises/{drone_gymkhana => drone_gymkhana_rotors}/web-template/README.md (100%) rename exercises/static/exercises/{drone_gymkhana => drone_gymkhana_rotors}/web-template/brain.py (100%) rename exercises/static/exercises/{drone_gymkhana => drone_gymkhana_rotors}/web-template/code/academy.py (100%) rename exercises/static/exercises/{drone_gymkhana => drone_gymkhana_rotors}/web-template/console.py (100%) rename exercises/static/exercises/{drone_gymkhana => drone_gymkhana_rotors}/web-template/drone_gymkhana.world (100%) rename exercises/static/exercises/{drone_gymkhana => drone_gymkhana_rotors}/web-template/exercise.py (100%) rename exercises/static/exercises/{drone_gymkhana => drone_gymkhana_rotors}/web-template/gui.py (100%) rename exercises/static/exercises/{drone_gymkhana => drone_gymkhana_rotors}/web-template/hal.py (100%) rename exercises/static/exercises/{drone_gymkhana => drone_gymkhana_rotors}/web-template/interfaces/__init__.py (100%) rename exercises/static/exercises/{drone_gymkhana => drone_gymkhana_rotors}/web-template/interfaces/camera.py (100%) rename exercises/static/exercises/{drone_gymkhana => drone_gymkhana_rotors}/web-template/interfaces/motors.py (100%) rename exercises/static/exercises/{drone_gymkhana => drone_gymkhana_rotors}/web-template/interfaces/pose3d.py (100%) rename exercises/static/exercises/{drone_gymkhana => drone_gymkhana_rotors}/web-template/interfaces/threadPublisher.py (100%) rename exercises/static/exercises/{drone_gymkhana => drone_gymkhana_rotors}/web-template/interfaces/threadStoppable.py (100%) rename exercises/static/exercises/{drone_gymkhana => drone_gymkhana_rotors}/web-template/launch/drone_gymkhana.launch (100%) rename exercises/static/exercises/{drone_gymkhana => drone_gymkhana_rotors}/web-template/shared/__init__.py (100%) rename exercises/static/exercises/{drone_gymkhana => drone_gymkhana_rotors}/web-template/shared/image.py (100%) rename exercises/static/exercises/{drone_gymkhana => drone_gymkhana_rotors}/web-template/shared/structure_img.py (100%) rename exercises/static/exercises/{drone_gymkhana => drone_gymkhana_rotors}/web-template/shared/value.py (100%) rename exercises/static/exercises/{drone_gymkhana => drone_gymkhana_rotors}/web-template/user_functions.py (100%) rename exercises/static/exercises/{drone_hangar => drone_hangar_rotors}/README.md (100%) rename exercises/static/exercises/{drone_hangar => drone_hangar_rotors}/drone_hangar.launch (100%) rename exercises/static/exercises/{drone_hangar => drone_hangar_rotors}/drone_hangar.world (100%) rename exercises/static/exercises/{drone_hangar => drone_hangar_rotors}/my_solution.py (100%) rename exercises/static/exercises/{drone_hangar => drone_hangar_rotors}/web-template/RADI-launch (100%) rename exercises/static/exercises/{drone_hangar => drone_hangar_rotors}/web-template/README.md (100%) rename exercises/static/exercises/{drone_hangar => drone_hangar_rotors}/web-template/brain.py (100%) rename exercises/static/exercises/{drone_hangar => drone_hangar_rotors}/web-template/code/academy.py (100%) rename exercises/static/exercises/{drone_hangar => drone_hangar_rotors}/web-template/console.py (100%) rename exercises/static/exercises/{drone_hangar => drone_hangar_rotors}/web-template/drone_hangar.world (100%) rename exercises/static/exercises/{drone_hangar => drone_hangar_rotors}/web-template/exercise.py (100%) rename exercises/static/exercises/{drone_hangar => drone_hangar_rotors}/web-template/gui.py (100%) rename exercises/static/exercises/{drone_hangar => drone_hangar_rotors}/web-template/hal.py (100%) rename exercises/static/exercises/{drone_hangar => drone_hangar_rotors}/web-template/interfaces/__init__.py (100%) rename exercises/static/exercises/{drone_hangar => drone_hangar_rotors}/web-template/interfaces/camera.py (100%) rename exercises/static/exercises/{drone_hangar => drone_hangar_rotors}/web-template/interfaces/motors.py (100%) rename exercises/static/exercises/{drone_hangar => drone_hangar_rotors}/web-template/interfaces/pose3d.py (100%) rename exercises/static/exercises/{drone_hangar => drone_hangar_rotors}/web-template/interfaces/threadPublisher.py (100%) rename exercises/static/exercises/{drone_hangar => drone_hangar_rotors}/web-template/interfaces/threadStoppable.py (100%) rename exercises/static/exercises/{drone_hangar => drone_hangar_rotors}/web-template/launch/drone_hangar.launch (100%) rename exercises/static/exercises/{drone_hangar => drone_hangar_rotors}/web-template/shared/__init__.py (100%) rename exercises/static/exercises/{drone_hangar => drone_hangar_rotors}/web-template/shared/image.py (100%) rename exercises/static/exercises/{drone_hangar => drone_hangar_rotors}/web-template/shared/structure_img.py (100%) rename exercises/static/exercises/{drone_hangar => drone_hangar_rotors}/web-template/shared/value.py (100%) rename exercises/static/exercises/{drone_hangar => drone_hangar_rotors}/web-template/user_functions.py (100%) rename exercises/static/exercises/{follow_road_2 => follow_road_rotors}/README.md (100%) rename exercises/static/exercises/{follow_road_2 => follow_road_rotors}/follow_road.launch (100%) rename exercises/static/exercises/{follow_road_2 => follow_road_rotors}/follow_road.world (100%) rename exercises/static/exercises/{follow_road_2 => follow_road_rotors}/my_solution.py (100%) rename exercises/static/exercises/{follow_road_2 => follow_road_rotors}/web-template/RADI-launch (100%) rename exercises/static/exercises/{follow_road_2 => follow_road_rotors}/web-template/README.md (100%) rename exercises/static/exercises/{follow_road_2 => follow_road_rotors}/web-template/brain.py (100%) rename exercises/static/exercises/{follow_road_2 => follow_road_rotors}/web-template/code/academy.py (100%) rename exercises/static/exercises/{follow_road_2 => follow_road_rotors}/web-template/console.py (100%) rename exercises/static/exercises/{follow_road_2 => follow_road_rotors}/web-template/exercise.py (100%) rename exercises/static/exercises/{follow_road_2 => follow_road_rotors}/web-template/follow_road.world (100%) rename exercises/static/exercises/{follow_road_2 => follow_road_rotors}/web-template/gui.py (100%) rename exercises/static/exercises/{follow_road_2 => follow_road_rotors}/web-template/hal.py (100%) rename exercises/static/exercises/{follow_road_2 => follow_road_rotors}/web-template/interfaces/__init__.py (100%) rename exercises/static/exercises/{follow_road_2 => follow_road_rotors}/web-template/interfaces/camera.py (100%) rename exercises/static/exercises/{follow_road_2 => follow_road_rotors}/web-template/interfaces/motors.py (100%) rename exercises/static/exercises/{follow_road_2 => follow_road_rotors}/web-template/interfaces/pose3d.py (100%) rename exercises/static/exercises/{follow_road_2 => follow_road_rotors}/web-template/interfaces/threadPublisher.py (100%) rename exercises/static/exercises/{follow_road_2 => follow_road_rotors}/web-template/launch/follow_road.launch (100%) rename exercises/static/exercises/{follow_road_2 => follow_road_rotors}/web-template/shared/__init__.py (100%) rename exercises/static/exercises/{follow_road_2 => follow_road_rotors}/web-template/shared/image.py (100%) rename exercises/static/exercises/{follow_road_2 => follow_road_rotors}/web-template/shared/structure_img.py (100%) rename exercises/static/exercises/{follow_road_2 => follow_road_rotors}/web-template/shared/value.py (100%) rename exercises/static/exercises/{follow_road_2 => follow_road_rotors}/web-template/user_functions.py (100%) rename exercises/static/exercises/{follow_turtlebot => follow_turtlebot_rotors}/README.md (100%) rename exercises/static/exercises/{follow_turtlebot => follow_turtlebot_rotors}/follow_turtlebot.launch (100%) rename exercises/static/exercises/{follow_turtlebot => follow_turtlebot_rotors}/follow_turtlebot.world (100%) rename exercises/static/exercises/{follow_turtlebot => follow_turtlebot_rotors}/my_solution.py (100%) rename exercises/static/exercises/{follow_turtlebot => follow_turtlebot_rotors}/web-template/RADI-launch (100%) rename exercises/static/exercises/{follow_turtlebot => follow_turtlebot_rotors}/web-template/README.md (100%) rename exercises/static/exercises/{follow_turtlebot => follow_turtlebot_rotors}/web-template/brain.py (100%) rename exercises/static/exercises/{follow_turtlebot => follow_turtlebot_rotors}/web-template/code/academy.py (100%) rename exercises/static/exercises/{follow_turtlebot => follow_turtlebot_rotors}/web-template/console.py (100%) rename exercises/static/exercises/{follow_turtlebot => follow_turtlebot_rotors}/web-template/exercise.py (100%) rename exercises/static/exercises/{follow_turtlebot => follow_turtlebot_rotors}/web-template/follow_turtlebot.world (100%) rename exercises/static/exercises/{follow_turtlebot => follow_turtlebot_rotors}/web-template/gui.py (100%) rename exercises/static/exercises/{follow_turtlebot => follow_turtlebot_rotors}/web-template/hal.py (100%) rename exercises/static/exercises/{follow_turtlebot => follow_turtlebot_rotors}/web-template/interfaces/__init__.py (100%) rename exercises/static/exercises/{follow_turtlebot => follow_turtlebot_rotors}/web-template/interfaces/camera.py (100%) rename exercises/static/exercises/{follow_turtlebot => follow_turtlebot_rotors}/web-template/interfaces/motors.py (100%) rename exercises/static/exercises/{follow_turtlebot => follow_turtlebot_rotors}/web-template/interfaces/pose3d.py (100%) rename exercises/static/exercises/{follow_turtlebot => follow_turtlebot_rotors}/web-template/interfaces/threadPublisher.py (100%) rename exercises/static/exercises/{follow_turtlebot => follow_turtlebot_rotors}/web-template/interfaces/threadStoppable.py (100%) rename exercises/static/exercises/{follow_turtlebot => follow_turtlebot_rotors}/web-template/launch/follow_turtlebot.launch (100%) rename exercises/static/exercises/{follow_turtlebot => follow_turtlebot_rotors}/web-template/shared/__init__.py (100%) rename exercises/static/exercises/{follow_turtlebot => follow_turtlebot_rotors}/web-template/shared/image.py (100%) rename exercises/static/exercises/{follow_turtlebot => follow_turtlebot_rotors}/web-template/shared/structure_img.py (100%) rename exercises/static/exercises/{follow_turtlebot => follow_turtlebot_rotors}/web-template/shared/turtlebot.py (100%) rename exercises/static/exercises/{follow_turtlebot => follow_turtlebot_rotors}/web-template/shared/value.py (100%) rename exercises/static/exercises/{follow_turtlebot => follow_turtlebot_rotors}/web-template/user_functions.py (100%) rename exercises/static/exercises/{labyrinth_escape => labyrinth_escape_rotors}/README.md (100%) rename exercises/static/exercises/{labyrinth_escape => labyrinth_escape_rotors}/arrows/down.png (100%) rename exercises/static/exercises/{labyrinth_escape => labyrinth_escape_rotors}/arrows/left.png (100%) rename exercises/static/exercises/{labyrinth_escape => labyrinth_escape_rotors}/arrows/right.png (100%) rename exercises/static/exercises/{labyrinth_escape => labyrinth_escape_rotors}/arrows/up.png (100%) rename exercises/static/exercises/{labyrinth_escape => labyrinth_escape_rotors}/labyrinth.world (100%) rename exercises/static/exercises/{labyrinth_escape => labyrinth_escape_rotors}/labyrinth_escape.launch (100%) rename exercises/static/exercises/{labyrinth_escape => labyrinth_escape_rotors}/my_solution.py (100%) rename exercises/static/exercises/{labyrinth_escape => labyrinth_escape_rotors}/web-template/RADI-launch (100%) rename exercises/static/exercises/{labyrinth_escape => labyrinth_escape_rotors}/web-template/README.md (100%) rename exercises/static/exercises/{labyrinth_escape => labyrinth_escape_rotors}/web-template/brain.py (100%) rename exercises/static/exercises/{labyrinth_escape => labyrinth_escape_rotors}/web-template/code/academy.py (100%) rename exercises/static/exercises/{labyrinth_escape => labyrinth_escape_rotors}/web-template/console.py (100%) rename exercises/static/exercises/{labyrinth_escape => labyrinth_escape_rotors}/web-template/exercise.py (100%) rename exercises/static/exercises/{labyrinth_escape => labyrinth_escape_rotors}/web-template/gui.py (100%) rename exercises/static/exercises/{labyrinth_escape => labyrinth_escape_rotors}/web-template/hal.py (100%) rename exercises/static/exercises/{labyrinth_escape => labyrinth_escape_rotors}/web-template/interfaces/__init__.py (100%) rename exercises/static/exercises/{labyrinth_escape => labyrinth_escape_rotors}/web-template/interfaces/camera.py (100%) rename exercises/static/exercises/{labyrinth_escape => labyrinth_escape_rotors}/web-template/interfaces/motors.py (100%) rename exercises/static/exercises/{labyrinth_escape => labyrinth_escape_rotors}/web-template/interfaces/pose3d.py (100%) rename exercises/static/exercises/{labyrinth_escape => labyrinth_escape_rotors}/web-template/interfaces/threadPublisher.py (100%) rename exercises/static/exercises/{labyrinth_escape => labyrinth_escape_rotors}/web-template/interfaces/threadStoppable.py (100%) rename exercises/static/exercises/{labyrinth_escape => labyrinth_escape_rotors}/web-template/labyrinth_escape.world (100%) rename exercises/static/exercises/{labyrinth_escape => labyrinth_escape_rotors}/web-template/launch/labyrinth_escape.launch (100%) rename exercises/static/exercises/{labyrinth_escape => labyrinth_escape_rotors}/web-template/shared/__init__.py (100%) rename exercises/static/exercises/{labyrinth_escape => labyrinth_escape_rotors}/web-template/shared/image.py (100%) rename exercises/static/exercises/{labyrinth_escape => labyrinth_escape_rotors}/web-template/shared/structure_img.py (100%) rename exercises/static/exercises/{labyrinth_escape => labyrinth_escape_rotors}/web-template/shared/value.py (100%) rename exercises/static/exercises/{labyrinth_escape => labyrinth_escape_rotors}/web-template/user_functions.py (100%) rename exercises/static/exercises/{package_delivery => package_delivery_rotors}/README.md (100%) rename exercises/static/exercises/{package_delivery => package_delivery_rotors}/magnet.py (100%) rename exercises/static/exercises/{package_delivery => package_delivery_rotors}/my_solution.py (100%) rename exercises/static/exercises/{package_delivery => package_delivery_rotors}/package_delivery.launch (100%) rename exercises/static/exercises/{package_delivery => package_delivery_rotors}/package_delivery.world (100%) rename exercises/static/exercises/{package_delivery => package_delivery_rotors}/package_delivery.yaml (100%) rename exercises/static/exercises/{package_delivery => package_delivery_rotors}/web-template/README.md (100%) rename exercises/static/exercises/{package_delivery => package_delivery_rotors}/web-template/brain.py (100%) rename exercises/static/exercises/{package_delivery => package_delivery_rotors}/web-template/code/academy.py (100%) rename exercises/static/exercises/{package_delivery => package_delivery_rotors}/web-template/console.py (100%) rename exercises/static/exercises/{package_delivery => package_delivery_rotors}/web-template/exercise.py (100%) rename exercises/static/exercises/{package_delivery => package_delivery_rotors}/web-template/gui.py (100%) rename exercises/static/exercises/{package_delivery => package_delivery_rotors}/web-template/hal.py (100%) rename exercises/static/exercises/{package_delivery => package_delivery_rotors}/web-template/interfaces/__init__.py (100%) rename exercises/static/exercises/{package_delivery => package_delivery_rotors}/web-template/interfaces/camera.py (100%) rename exercises/static/exercises/{package_delivery => package_delivery_rotors}/web-template/interfaces/motors.py (100%) rename exercises/static/exercises/{package_delivery => package_delivery_rotors}/web-template/interfaces/pose3d.py (100%) rename exercises/static/exercises/{package_delivery => package_delivery_rotors}/web-template/interfaces/threadPublisher.py (100%) rename exercises/static/exercises/{package_delivery => package_delivery_rotors}/web-template/launch/package_delivery.launch (100%) rename exercises/static/exercises/{package_delivery => package_delivery_rotors}/web-template/package_delivery.world (100%) rename exercises/static/exercises/{package_delivery => package_delivery_rotors}/web-template/shared/__init__.py (100%) rename exercises/static/exercises/{package_delivery => package_delivery_rotors}/web-template/shared/image.py (100%) rename exercises/static/exercises/{package_delivery => package_delivery_rotors}/web-template/shared/magnet.py (100%) rename exercises/static/exercises/{package_delivery => package_delivery_rotors}/web-template/shared/structure_img.py (100%) rename exercises/static/exercises/{package_delivery => package_delivery_rotors}/web-template/shared/value.py (100%) rename exercises/static/exercises/{package_delivery => package_delivery_rotors}/web-template/user_functions.py (100%) rename exercises/static/exercises/{position_control => position_control_rotors}/Beacon.py (100%) rename exercises/static/exercises/{position_control => position_control_rotors}/README.md (100%) rename exercises/static/exercises/{position_control => position_control_rotors}/my_solution.py (100%) rename exercises/static/exercises/{position_control => position_control_rotors}/position_control.launch (100%) rename exercises/static/exercises/{position_control => position_control_rotors}/position_control.world (100%) rename exercises/static/exercises/{position_control => position_control_rotors}/web-template/RADI-launch (100%) rename exercises/static/exercises/{position_control => position_control_rotors}/web-template/README.md (100%) rename exercises/static/exercises/{position_control => position_control_rotors}/web-template/brain.py (100%) rename exercises/static/exercises/{position_control => position_control_rotors}/web-template/code/academy.py (100%) rename exercises/static/exercises/{position_control => position_control_rotors}/web-template/console.py (100%) rename exercises/static/exercises/{position_control => position_control_rotors}/web-template/exercise.py (100%) rename exercises/static/exercises/{position_control => position_control_rotors}/web-template/gui.py (100%) rename exercises/static/exercises/{position_control => position_control_rotors}/web-template/hal.py (100%) rename exercises/static/exercises/{position_control => position_control_rotors}/web-template/interfaces/__init__.py (100%) rename exercises/static/exercises/{position_control => position_control_rotors}/web-template/interfaces/camera.py (100%) rename exercises/static/exercises/{position_control => position_control_rotors}/web-template/interfaces/motors.py (100%) rename exercises/static/exercises/{position_control => position_control_rotors}/web-template/interfaces/pose3d.py (100%) rename exercises/static/exercises/{position_control => position_control_rotors}/web-template/interfaces/threadPublisher.py (100%) rename exercises/static/exercises/{position_control => position_control_rotors}/web-template/interfaces/threadStoppable.py (100%) rename exercises/static/exercises/{position_control => position_control_rotors}/web-template/launch/position_control.launch (100%) rename exercises/static/exercises/{position_control => position_control_rotors}/web-template/position_control.world (100%) rename exercises/static/exercises/{position_control => position_control_rotors}/web-template/shared/Beacon.py (100%) rename exercises/static/exercises/{position_control => position_control_rotors}/web-template/shared/__init__.py (100%) rename exercises/static/exercises/{position_control => position_control_rotors}/web-template/shared/image.py (100%) rename exercises/static/exercises/{position_control => position_control_rotors}/web-template/shared/structure_img.py (100%) rename exercises/static/exercises/{position_control => position_control_rotors}/web-template/shared/value.py (100%) rename exercises/static/exercises/{position_control => position_control_rotors}/web-template/user_functions.py (100%) rename exercises/static/exercises/{power_tower_inspection => power_tower_inspection_rotors}/README.md (100%) rename exercises/static/exercises/{power_tower_inspection => power_tower_inspection_rotors}/my_solution.py (100%) rename exercises/static/exercises/{power_tower_inspection => power_tower_inspection_rotors}/power_tower_inspection.launch (100%) rename exercises/static/exercises/{power_tower_inspection => power_tower_inspection_rotors}/power_tower_inspection.world (100%) rename exercises/static/exercises/{power_tower_inspection => power_tower_inspection_rotors}/web-template/README.md (100%) rename exercises/static/exercises/{power_tower_inspection => power_tower_inspection_rotors}/web-template/brain.py (100%) rename exercises/static/exercises/{power_tower_inspection => power_tower_inspection_rotors}/web-template/code/academy.py (100%) rename exercises/static/exercises/{power_tower_inspection => power_tower_inspection_rotors}/web-template/console.py (100%) rename exercises/static/exercises/{power_tower_inspection => power_tower_inspection_rotors}/web-template/exercise.py (100%) rename exercises/static/exercises/{power_tower_inspection => power_tower_inspection_rotors}/web-template/gui.py (100%) rename exercises/static/exercises/{power_tower_inspection => power_tower_inspection_rotors}/web-template/hal.py (100%) rename exercises/static/exercises/{power_tower_inspection => power_tower_inspection_rotors}/web-template/interfaces/__init__.py (100%) rename exercises/static/exercises/{power_tower_inspection => power_tower_inspection_rotors}/web-template/interfaces/camera.py (100%) rename exercises/static/exercises/{power_tower_inspection => power_tower_inspection_rotors}/web-template/interfaces/motors.py (100%) rename exercises/static/exercises/{power_tower_inspection => power_tower_inspection_rotors}/web-template/interfaces/pose3d.py (100%) rename exercises/static/exercises/{power_tower_inspection => power_tower_inspection_rotors}/web-template/interfaces/threadPublisher.py (100%) rename exercises/static/exercises/{power_tower_inspection => power_tower_inspection_rotors}/web-template/launch/power_tower_inspection.launch (100%) rename exercises/static/exercises/{power_tower_inspection => power_tower_inspection_rotors}/web-template/power_tower_inspection.world (100%) rename exercises/static/exercises/{power_tower_inspection => power_tower_inspection_rotors}/web-template/shared/__init__.py (100%) rename exercises/static/exercises/{power_tower_inspection => power_tower_inspection_rotors}/web-template/shared/image.py (100%) rename exercises/static/exercises/{power_tower_inspection => power_tower_inspection_rotors}/web-template/shared/structure_img.py (100%) rename exercises/static/exercises/{power_tower_inspection => power_tower_inspection_rotors}/web-template/shared/value.py (100%) rename exercises/static/exercises/{power_tower_inspection => power_tower_inspection_rotors}/web-template/user_functions.py (100%) rename exercises/static/exercises/{rescue_people => rescue_people_rotors}/README.md (100%) rename exercises/static/exercises/{rescue_people => rescue_people_rotors}/haarcascade_frontalface_default.xml (100%) rename exercises/static/exercises/{rescue_people => rescue_people_rotors}/my_solution.py (100%) rename exercises/static/exercises/{rescue_people => rescue_people_rotors}/rescue_people.launch (100%) rename exercises/static/exercises/{rescue_people => rescue_people_rotors}/rescue_people.world (100%) rename exercises/static/exercises/{rescue_people => rescue_people_rotors}/web-template/RADI-launch (100%) rename exercises/static/exercises/{rescue_people => rescue_people_rotors}/web-template/README.md (100%) rename exercises/static/exercises/{rescue_people => rescue_people_rotors}/web-template/brain.py (100%) rename exercises/static/exercises/{rescue_people => rescue_people_rotors}/web-template/code/academy.py (100%) rename exercises/static/exercises/{rescue_people => rescue_people_rotors}/web-template/console.py (100%) rename exercises/static/exercises/{rescue_people => rescue_people_rotors}/web-template/exercise.py (100%) rename exercises/static/exercises/{rescue_people => rescue_people_rotors}/web-template/gui.py (100%) rename exercises/static/exercises/{rescue_people => rescue_people_rotors}/web-template/hal.py (100%) rename exercises/static/exercises/{rescue_people => rescue_people_rotors}/web-template/interfaces/__init__.py (100%) rename exercises/static/exercises/{rescue_people => rescue_people_rotors}/web-template/interfaces/camera.py (100%) rename exercises/static/exercises/{rescue_people => rescue_people_rotors}/web-template/interfaces/motors.py (100%) rename exercises/static/exercises/{rescue_people => rescue_people_rotors}/web-template/interfaces/pose3d.py (100%) rename exercises/static/exercises/{rescue_people => rescue_people_rotors}/web-template/interfaces/threadPublisher.py (100%) rename exercises/static/exercises/{rescue_people => rescue_people_rotors}/web-template/interfaces/threadStoppable.py (100%) rename exercises/static/exercises/{rescue_people => rescue_people_rotors}/web-template/launch/rescue_people.launch (100%) rename exercises/static/exercises/{rescue_people => rescue_people_rotors}/web-template/launch/rescue_people.sh (100%) rename exercises/static/exercises/{rescue_people => rescue_people_rotors}/web-template/rescue_people.world (100%) rename exercises/static/exercises/{rescue_people => rescue_people_rotors}/web-template/shared/__init__.py (100%) rename exercises/static/exercises/{rescue_people => rescue_people_rotors}/web-template/shared/image.py (100%) rename exercises/static/exercises/{rescue_people => rescue_people_rotors}/web-template/shared/light.py (100%) rename exercises/static/exercises/{rescue_people => rescue_people_rotors}/web-template/shared/structure_img.py (100%) rename exercises/static/exercises/{rescue_people => rescue_people_rotors}/web-template/shared/value.py (100%) rename exercises/static/exercises/{rescue_people => rescue_people_rotors}/web-template/user_functions.py (100%) rename exercises/static/exercises/{visual_lander => visual_lander_rotors}/README.md (100%) rename exercises/static/exercises/{visual_lander => visual_lander_rotors}/my_solution.py (100%) rename exercises/static/exercises/{visual_lander => visual_lander_rotors}/visual_lander.launch (100%) rename exercises/static/exercises/{visual_lander => visual_lander_rotors}/visual_lander.world (100%) rename exercises/static/exercises/{visual_lander => visual_lander_rotors}/web-template/RADI-launch (100%) rename exercises/static/exercises/{visual_lander => visual_lander_rotors}/web-template/README.md (100%) rename exercises/static/exercises/{visual_lander => visual_lander_rotors}/web-template/brain.py (100%) rename exercises/static/exercises/{visual_lander => visual_lander_rotors}/web-template/code/academy.py (100%) rename exercises/static/exercises/{visual_lander => visual_lander_rotors}/web-template/console.py (100%) rename exercises/static/exercises/{visual_lander => visual_lander_rotors}/web-template/exercise.py (100%) rename exercises/static/exercises/{visual_lander => visual_lander_rotors}/web-template/gui.py (100%) rename exercises/static/exercises/{visual_lander => visual_lander_rotors}/web-template/hal.py (100%) rename exercises/static/exercises/{visual_lander => visual_lander_rotors}/web-template/interfaces/__init__.py (100%) rename exercises/static/exercises/{visual_lander => visual_lander_rotors}/web-template/interfaces/camera.py (100%) rename exercises/static/exercises/{visual_lander => visual_lander_rotors}/web-template/interfaces/motors.py (100%) rename exercises/static/exercises/{visual_lander => visual_lander_rotors}/web-template/interfaces/pose3d.py (100%) rename exercises/static/exercises/{visual_lander => visual_lander_rotors}/web-template/interfaces/threadPublisher.py (100%) rename exercises/static/exercises/{visual_lander => visual_lander_rotors}/web-template/interfaces/threadStoppable.py (100%) rename exercises/static/exercises/{visual_lander => visual_lander_rotors}/web-template/launch/visual_lander.launch (100%) rename exercises/static/exercises/{visual_lander => visual_lander_rotors}/web-template/shared/__init__.py (100%) rename exercises/static/exercises/{visual_lander => visual_lander_rotors}/web-template/shared/car.py (100%) rename exercises/static/exercises/{visual_lander => visual_lander_rotors}/web-template/shared/image.py (100%) rename exercises/static/exercises/{visual_lander => visual_lander_rotors}/web-template/shared/structure_img.py (100%) rename exercises/static/exercises/{visual_lander => visual_lander_rotors}/web-template/shared/value.py (100%) rename exercises/static/exercises/{visual_lander => visual_lander_rotors}/web-template/user_functions.py (100%) rename exercises/static/exercises/{visual_lander => visual_lander_rotors}/web-template/visual_lander.world (100%) diff --git a/exercises/static/exercises/drone_cat_mouse/README.md b/exercises/static/exercises/drone_cat_mouse_rotors/README.md similarity index 100% rename from exercises/static/exercises/drone_cat_mouse/README.md rename to exercises/static/exercises/drone_cat_mouse_rotors/README.md diff --git a/exercises/static/exercises/drone_cat_mouse/autonomous_mouse.launch b/exercises/static/exercises/drone_cat_mouse_rotors/autonomous_mouse.launch similarity index 100% rename from exercises/static/exercises/drone_cat_mouse/autonomous_mouse.launch rename to exercises/static/exercises/drone_cat_mouse_rotors/autonomous_mouse.launch diff --git a/exercises/static/exercises/drone_cat_mouse/autonomous_mouse.py b/exercises/static/exercises/drone_cat_mouse_rotors/autonomous_mouse.py similarity index 100% rename from exercises/static/exercises/drone_cat_mouse/autonomous_mouse.py rename to exercises/static/exercises/drone_cat_mouse_rotors/autonomous_mouse.py diff --git a/exercises/static/exercises/drone_cat_mouse/drone_cat_mouse.launch b/exercises/static/exercises/drone_cat_mouse_rotors/drone_cat_mouse.launch similarity index 100% rename from exercises/static/exercises/drone_cat_mouse/drone_cat_mouse.launch rename to exercises/static/exercises/drone_cat_mouse_rotors/drone_cat_mouse.launch diff --git a/exercises/static/exercises/drone_cat_mouse/drone_cat_mouse.world b/exercises/static/exercises/drone_cat_mouse_rotors/drone_cat_mouse.world similarity index 100% rename from exercises/static/exercises/drone_cat_mouse/drone_cat_mouse.world rename to exercises/static/exercises/drone_cat_mouse_rotors/drone_cat_mouse.world diff --git a/exercises/static/exercises/drone_cat_mouse/drone_cat_mouse.yaml b/exercises/static/exercises/drone_cat_mouse_rotors/drone_cat_mouse.yaml similarity index 100% rename from exercises/static/exercises/drone_cat_mouse/drone_cat_mouse.yaml rename to exercises/static/exercises/drone_cat_mouse_rotors/drone_cat_mouse.yaml diff --git a/exercises/static/exercises/drone_cat_mouse/my_solution.py b/exercises/static/exercises/drone_cat_mouse_rotors/my_solution.py similarity index 100% rename from exercises/static/exercises/drone_cat_mouse/my_solution.py rename to exercises/static/exercises/drone_cat_mouse_rotors/my_solution.py diff --git a/exercises/static/exercises/drone_cat_mouse/teleoperated_mouse.py b/exercises/static/exercises/drone_cat_mouse_rotors/teleoperated_mouse.py similarity index 100% rename from exercises/static/exercises/drone_cat_mouse/teleoperated_mouse.py rename to exercises/static/exercises/drone_cat_mouse_rotors/teleoperated_mouse.py diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/RADI-launch b/exercises/static/exercises/drone_cat_mouse_rotors/web-template/RADI-launch similarity index 100% rename from exercises/static/exercises/drone_cat_mouse/web-template/RADI-launch rename to exercises/static/exercises/drone_cat_mouse_rotors/web-template/RADI-launch diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/README.md b/exercises/static/exercises/drone_cat_mouse_rotors/web-template/README.md similarity index 100% rename from exercises/static/exercises/drone_cat_mouse/web-template/README.md rename to exercises/static/exercises/drone_cat_mouse_rotors/web-template/README.md diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/brain.py b/exercises/static/exercises/drone_cat_mouse_rotors/web-template/brain.py similarity index 100% rename from exercises/static/exercises/drone_cat_mouse/web-template/brain.py rename to exercises/static/exercises/drone_cat_mouse_rotors/web-template/brain.py diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/brain_guest.py b/exercises/static/exercises/drone_cat_mouse_rotors/web-template/brain_guest.py similarity index 100% rename from exercises/static/exercises/drone_cat_mouse/web-template/brain_guest.py rename to exercises/static/exercises/drone_cat_mouse_rotors/web-template/brain_guest.py diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/code/academy.py b/exercises/static/exercises/drone_cat_mouse_rotors/web-template/code/academy.py similarity index 100% rename from exercises/static/exercises/drone_cat_mouse/web-template/code/academy.py rename to exercises/static/exercises/drone_cat_mouse_rotors/web-template/code/academy.py diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/console.py b/exercises/static/exercises/drone_cat_mouse_rotors/web-template/console.py similarity index 100% rename from exercises/static/exercises/drone_cat_mouse/web-template/console.py rename to exercises/static/exercises/drone_cat_mouse_rotors/web-template/console.py diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/drone_cat_mouse.world b/exercises/static/exercises/drone_cat_mouse_rotors/web-template/drone_cat_mouse.world similarity index 100% rename from exercises/static/exercises/drone_cat_mouse/web-template/drone_cat_mouse.world rename to exercises/static/exercises/drone_cat_mouse_rotors/web-template/drone_cat_mouse.world diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/drone_cat_mouse.yaml b/exercises/static/exercises/drone_cat_mouse_rotors/web-template/drone_cat_mouse.yaml similarity index 100% rename from exercises/static/exercises/drone_cat_mouse/web-template/drone_cat_mouse.yaml rename to exercises/static/exercises/drone_cat_mouse_rotors/web-template/drone_cat_mouse.yaml diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/exercise.py b/exercises/static/exercises/drone_cat_mouse_rotors/web-template/exercise.py similarity index 100% rename from exercises/static/exercises/drone_cat_mouse/web-template/exercise.py rename to exercises/static/exercises/drone_cat_mouse_rotors/web-template/exercise.py diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/exercise_guest.py b/exercises/static/exercises/drone_cat_mouse_rotors/web-template/exercise_guest.py similarity index 100% rename from exercises/static/exercises/drone_cat_mouse/web-template/exercise_guest.py rename to exercises/static/exercises/drone_cat_mouse_rotors/web-template/exercise_guest.py diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/gui.py b/exercises/static/exercises/drone_cat_mouse_rotors/web-template/gui.py similarity index 100% rename from exercises/static/exercises/drone_cat_mouse/web-template/gui.py rename to exercises/static/exercises/drone_cat_mouse_rotors/web-template/gui.py diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/gui_guest.py b/exercises/static/exercises/drone_cat_mouse_rotors/web-template/gui_guest.py similarity index 100% rename from exercises/static/exercises/drone_cat_mouse/web-template/gui_guest.py rename to exercises/static/exercises/drone_cat_mouse_rotors/web-template/gui_guest.py diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/hal.py b/exercises/static/exercises/drone_cat_mouse_rotors/web-template/hal.py similarity index 100% rename from exercises/static/exercises/drone_cat_mouse/web-template/hal.py rename to exercises/static/exercises/drone_cat_mouse_rotors/web-template/hal.py diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/hal_guest.py b/exercises/static/exercises/drone_cat_mouse_rotors/web-template/hal_guest.py similarity index 100% rename from exercises/static/exercises/drone_cat_mouse/web-template/hal_guest.py rename to exercises/static/exercises/drone_cat_mouse_rotors/web-template/hal_guest.py diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/interfaces/__init__.py b/exercises/static/exercises/drone_cat_mouse_rotors/web-template/interfaces/__init__.py similarity index 100% rename from exercises/static/exercises/drone_cat_mouse/web-template/interfaces/__init__.py rename to exercises/static/exercises/drone_cat_mouse_rotors/web-template/interfaces/__init__.py diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/interfaces/autonomous_mouse.py b/exercises/static/exercises/drone_cat_mouse_rotors/web-template/interfaces/autonomous_mouse.py similarity index 100% rename from exercises/static/exercises/drone_cat_mouse/web-template/interfaces/autonomous_mouse.py rename to exercises/static/exercises/drone_cat_mouse_rotors/web-template/interfaces/autonomous_mouse.py diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/interfaces/camera.py b/exercises/static/exercises/drone_cat_mouse_rotors/web-template/interfaces/camera.py similarity index 100% rename from exercises/static/exercises/drone_cat_mouse/web-template/interfaces/camera.py rename to exercises/static/exercises/drone_cat_mouse_rotors/web-template/interfaces/camera.py diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/interfaces/motors.py b/exercises/static/exercises/drone_cat_mouse_rotors/web-template/interfaces/motors.py similarity index 100% rename from exercises/static/exercises/drone_cat_mouse/web-template/interfaces/motors.py rename to exercises/static/exercises/drone_cat_mouse_rotors/web-template/interfaces/motors.py diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/interfaces/pose3d.py b/exercises/static/exercises/drone_cat_mouse_rotors/web-template/interfaces/pose3d.py similarity index 100% rename from exercises/static/exercises/drone_cat_mouse/web-template/interfaces/pose3d.py rename to exercises/static/exercises/drone_cat_mouse_rotors/web-template/interfaces/pose3d.py diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/interfaces/threadPublisher.py b/exercises/static/exercises/drone_cat_mouse_rotors/web-template/interfaces/threadPublisher.py similarity index 100% rename from exercises/static/exercises/drone_cat_mouse/web-template/interfaces/threadPublisher.py rename to exercises/static/exercises/drone_cat_mouse_rotors/web-template/interfaces/threadPublisher.py diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/interfaces/threadStoppable.py b/exercises/static/exercises/drone_cat_mouse_rotors/web-template/interfaces/threadStoppable.py similarity index 100% rename from exercises/static/exercises/drone_cat_mouse/web-template/interfaces/threadStoppable.py rename to exercises/static/exercises/drone_cat_mouse_rotors/web-template/interfaces/threadStoppable.py diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/launch/drone_cat_mouse.launch b/exercises/static/exercises/drone_cat_mouse_rotors/web-template/launch/drone_cat_mouse.launch similarity index 100% rename from exercises/static/exercises/drone_cat_mouse/web-template/launch/drone_cat_mouse.launch rename to exercises/static/exercises/drone_cat_mouse_rotors/web-template/launch/drone_cat_mouse.launch diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/shared/__init__.py b/exercises/static/exercises/drone_cat_mouse_rotors/web-template/shared/__init__.py similarity index 100% rename from exercises/static/exercises/drone_cat_mouse/web-template/shared/__init__.py rename to exercises/static/exercises/drone_cat_mouse_rotors/web-template/shared/__init__.py diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/shared/image.py b/exercises/static/exercises/drone_cat_mouse_rotors/web-template/shared/image.py similarity index 100% rename from exercises/static/exercises/drone_cat_mouse/web-template/shared/image.py rename to exercises/static/exercises/drone_cat_mouse_rotors/web-template/shared/image.py diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/shared/mouse.py b/exercises/static/exercises/drone_cat_mouse_rotors/web-template/shared/mouse.py similarity index 100% rename from exercises/static/exercises/drone_cat_mouse/web-template/shared/mouse.py rename to exercises/static/exercises/drone_cat_mouse_rotors/web-template/shared/mouse.py diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/shared/structure_img.py b/exercises/static/exercises/drone_cat_mouse_rotors/web-template/shared/structure_img.py similarity index 100% rename from exercises/static/exercises/drone_cat_mouse/web-template/shared/structure_img.py rename to exercises/static/exercises/drone_cat_mouse_rotors/web-template/shared/structure_img.py diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/shared/value.py b/exercises/static/exercises/drone_cat_mouse_rotors/web-template/shared/value.py similarity index 100% rename from exercises/static/exercises/drone_cat_mouse/web-template/shared/value.py rename to exercises/static/exercises/drone_cat_mouse_rotors/web-template/shared/value.py diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/user_functions.py b/exercises/static/exercises/drone_cat_mouse_rotors/web-template/user_functions.py similarity index 100% rename from exercises/static/exercises/drone_cat_mouse/web-template/user_functions.py rename to exercises/static/exercises/drone_cat_mouse_rotors/web-template/user_functions.py diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/user_functions_guest.py b/exercises/static/exercises/drone_cat_mouse_rotors/web-template/user_functions_guest.py similarity index 100% rename from exercises/static/exercises/drone_cat_mouse/web-template/user_functions_guest.py rename to exercises/static/exercises/drone_cat_mouse_rotors/web-template/user_functions_guest.py diff --git a/exercises/static/exercises/drone_gymkhana/README.md b/exercises/static/exercises/drone_gymkhana_rotors/README.md similarity index 100% rename from exercises/static/exercises/drone_gymkhana/README.md rename to exercises/static/exercises/drone_gymkhana_rotors/README.md diff --git a/exercises/static/exercises/drone_gymkhana/drone_gymkhana.launch b/exercises/static/exercises/drone_gymkhana_rotors/drone_gymkhana.launch similarity index 100% rename from exercises/static/exercises/drone_gymkhana/drone_gymkhana.launch rename to exercises/static/exercises/drone_gymkhana_rotors/drone_gymkhana.launch diff --git a/exercises/static/exercises/drone_gymkhana/drone_gymkhana.world b/exercises/static/exercises/drone_gymkhana_rotors/drone_gymkhana.world similarity index 100% rename from exercises/static/exercises/drone_gymkhana/drone_gymkhana.world rename to exercises/static/exercises/drone_gymkhana_rotors/drone_gymkhana.world diff --git a/exercises/static/exercises/drone_gymkhana/my_solution.py b/exercises/static/exercises/drone_gymkhana_rotors/my_solution.py similarity index 100% rename from exercises/static/exercises/drone_gymkhana/my_solution.py rename to exercises/static/exercises/drone_gymkhana_rotors/my_solution.py diff --git a/exercises/static/exercises/drone_gymkhana/web-template/RADI-launch b/exercises/static/exercises/drone_gymkhana_rotors/web-template/RADI-launch similarity index 100% rename from exercises/static/exercises/drone_gymkhana/web-template/RADI-launch rename to exercises/static/exercises/drone_gymkhana_rotors/web-template/RADI-launch diff --git a/exercises/static/exercises/drone_gymkhana/web-template/README.md b/exercises/static/exercises/drone_gymkhana_rotors/web-template/README.md similarity index 100% rename from exercises/static/exercises/drone_gymkhana/web-template/README.md rename to exercises/static/exercises/drone_gymkhana_rotors/web-template/README.md diff --git a/exercises/static/exercises/drone_gymkhana/web-template/brain.py b/exercises/static/exercises/drone_gymkhana_rotors/web-template/brain.py similarity index 100% rename from exercises/static/exercises/drone_gymkhana/web-template/brain.py rename to exercises/static/exercises/drone_gymkhana_rotors/web-template/brain.py diff --git a/exercises/static/exercises/drone_gymkhana/web-template/code/academy.py b/exercises/static/exercises/drone_gymkhana_rotors/web-template/code/academy.py similarity index 100% rename from exercises/static/exercises/drone_gymkhana/web-template/code/academy.py rename to exercises/static/exercises/drone_gymkhana_rotors/web-template/code/academy.py diff --git a/exercises/static/exercises/drone_gymkhana/web-template/console.py b/exercises/static/exercises/drone_gymkhana_rotors/web-template/console.py similarity index 100% rename from exercises/static/exercises/drone_gymkhana/web-template/console.py rename to exercises/static/exercises/drone_gymkhana_rotors/web-template/console.py diff --git a/exercises/static/exercises/drone_gymkhana/web-template/drone_gymkhana.world b/exercises/static/exercises/drone_gymkhana_rotors/web-template/drone_gymkhana.world similarity index 100% rename from exercises/static/exercises/drone_gymkhana/web-template/drone_gymkhana.world rename to exercises/static/exercises/drone_gymkhana_rotors/web-template/drone_gymkhana.world diff --git a/exercises/static/exercises/drone_gymkhana/web-template/exercise.py b/exercises/static/exercises/drone_gymkhana_rotors/web-template/exercise.py similarity index 100% rename from exercises/static/exercises/drone_gymkhana/web-template/exercise.py rename to exercises/static/exercises/drone_gymkhana_rotors/web-template/exercise.py diff --git a/exercises/static/exercises/drone_gymkhana/web-template/gui.py b/exercises/static/exercises/drone_gymkhana_rotors/web-template/gui.py similarity index 100% rename from exercises/static/exercises/drone_gymkhana/web-template/gui.py rename to exercises/static/exercises/drone_gymkhana_rotors/web-template/gui.py diff --git a/exercises/static/exercises/drone_gymkhana/web-template/hal.py b/exercises/static/exercises/drone_gymkhana_rotors/web-template/hal.py similarity index 100% rename from exercises/static/exercises/drone_gymkhana/web-template/hal.py rename to exercises/static/exercises/drone_gymkhana_rotors/web-template/hal.py diff --git a/exercises/static/exercises/drone_gymkhana/web-template/interfaces/__init__.py b/exercises/static/exercises/drone_gymkhana_rotors/web-template/interfaces/__init__.py similarity index 100% rename from exercises/static/exercises/drone_gymkhana/web-template/interfaces/__init__.py rename to exercises/static/exercises/drone_gymkhana_rotors/web-template/interfaces/__init__.py diff --git a/exercises/static/exercises/drone_gymkhana/web-template/interfaces/camera.py b/exercises/static/exercises/drone_gymkhana_rotors/web-template/interfaces/camera.py similarity index 100% rename from exercises/static/exercises/drone_gymkhana/web-template/interfaces/camera.py rename to exercises/static/exercises/drone_gymkhana_rotors/web-template/interfaces/camera.py diff --git a/exercises/static/exercises/drone_gymkhana/web-template/interfaces/motors.py b/exercises/static/exercises/drone_gymkhana_rotors/web-template/interfaces/motors.py similarity index 100% rename from exercises/static/exercises/drone_gymkhana/web-template/interfaces/motors.py rename to exercises/static/exercises/drone_gymkhana_rotors/web-template/interfaces/motors.py diff --git a/exercises/static/exercises/drone_gymkhana/web-template/interfaces/pose3d.py b/exercises/static/exercises/drone_gymkhana_rotors/web-template/interfaces/pose3d.py similarity index 100% rename from exercises/static/exercises/drone_gymkhana/web-template/interfaces/pose3d.py rename to exercises/static/exercises/drone_gymkhana_rotors/web-template/interfaces/pose3d.py diff --git a/exercises/static/exercises/drone_gymkhana/web-template/interfaces/threadPublisher.py b/exercises/static/exercises/drone_gymkhana_rotors/web-template/interfaces/threadPublisher.py similarity index 100% rename from exercises/static/exercises/drone_gymkhana/web-template/interfaces/threadPublisher.py rename to exercises/static/exercises/drone_gymkhana_rotors/web-template/interfaces/threadPublisher.py diff --git a/exercises/static/exercises/drone_gymkhana/web-template/interfaces/threadStoppable.py b/exercises/static/exercises/drone_gymkhana_rotors/web-template/interfaces/threadStoppable.py similarity index 100% rename from exercises/static/exercises/drone_gymkhana/web-template/interfaces/threadStoppable.py rename to exercises/static/exercises/drone_gymkhana_rotors/web-template/interfaces/threadStoppable.py diff --git a/exercises/static/exercises/drone_gymkhana/web-template/launch/drone_gymkhana.launch b/exercises/static/exercises/drone_gymkhana_rotors/web-template/launch/drone_gymkhana.launch similarity index 100% rename from exercises/static/exercises/drone_gymkhana/web-template/launch/drone_gymkhana.launch rename to exercises/static/exercises/drone_gymkhana_rotors/web-template/launch/drone_gymkhana.launch diff --git a/exercises/static/exercises/drone_gymkhana/web-template/shared/__init__.py b/exercises/static/exercises/drone_gymkhana_rotors/web-template/shared/__init__.py similarity index 100% rename from exercises/static/exercises/drone_gymkhana/web-template/shared/__init__.py rename to exercises/static/exercises/drone_gymkhana_rotors/web-template/shared/__init__.py diff --git a/exercises/static/exercises/drone_gymkhana/web-template/shared/image.py b/exercises/static/exercises/drone_gymkhana_rotors/web-template/shared/image.py similarity index 100% rename from exercises/static/exercises/drone_gymkhana/web-template/shared/image.py rename to exercises/static/exercises/drone_gymkhana_rotors/web-template/shared/image.py diff --git a/exercises/static/exercises/drone_gymkhana/web-template/shared/structure_img.py b/exercises/static/exercises/drone_gymkhana_rotors/web-template/shared/structure_img.py similarity index 100% rename from exercises/static/exercises/drone_gymkhana/web-template/shared/structure_img.py rename to exercises/static/exercises/drone_gymkhana_rotors/web-template/shared/structure_img.py diff --git a/exercises/static/exercises/drone_gymkhana/web-template/shared/value.py b/exercises/static/exercises/drone_gymkhana_rotors/web-template/shared/value.py similarity index 100% rename from exercises/static/exercises/drone_gymkhana/web-template/shared/value.py rename to exercises/static/exercises/drone_gymkhana_rotors/web-template/shared/value.py diff --git a/exercises/static/exercises/drone_gymkhana/web-template/user_functions.py b/exercises/static/exercises/drone_gymkhana_rotors/web-template/user_functions.py similarity index 100% rename from exercises/static/exercises/drone_gymkhana/web-template/user_functions.py rename to exercises/static/exercises/drone_gymkhana_rotors/web-template/user_functions.py diff --git a/exercises/static/exercises/drone_hangar/README.md b/exercises/static/exercises/drone_hangar_rotors/README.md similarity index 100% rename from exercises/static/exercises/drone_hangar/README.md rename to exercises/static/exercises/drone_hangar_rotors/README.md diff --git a/exercises/static/exercises/drone_hangar/drone_hangar.launch b/exercises/static/exercises/drone_hangar_rotors/drone_hangar.launch similarity index 100% rename from exercises/static/exercises/drone_hangar/drone_hangar.launch rename to exercises/static/exercises/drone_hangar_rotors/drone_hangar.launch diff --git a/exercises/static/exercises/drone_hangar/drone_hangar.world b/exercises/static/exercises/drone_hangar_rotors/drone_hangar.world similarity index 100% rename from exercises/static/exercises/drone_hangar/drone_hangar.world rename to exercises/static/exercises/drone_hangar_rotors/drone_hangar.world diff --git a/exercises/static/exercises/drone_hangar/my_solution.py b/exercises/static/exercises/drone_hangar_rotors/my_solution.py similarity index 100% rename from exercises/static/exercises/drone_hangar/my_solution.py rename to exercises/static/exercises/drone_hangar_rotors/my_solution.py diff --git a/exercises/static/exercises/drone_hangar/web-template/RADI-launch b/exercises/static/exercises/drone_hangar_rotors/web-template/RADI-launch similarity index 100% rename from exercises/static/exercises/drone_hangar/web-template/RADI-launch rename to exercises/static/exercises/drone_hangar_rotors/web-template/RADI-launch diff --git a/exercises/static/exercises/drone_hangar/web-template/README.md b/exercises/static/exercises/drone_hangar_rotors/web-template/README.md similarity index 100% rename from exercises/static/exercises/drone_hangar/web-template/README.md rename to exercises/static/exercises/drone_hangar_rotors/web-template/README.md diff --git a/exercises/static/exercises/drone_hangar/web-template/brain.py b/exercises/static/exercises/drone_hangar_rotors/web-template/brain.py similarity index 100% rename from exercises/static/exercises/drone_hangar/web-template/brain.py rename to exercises/static/exercises/drone_hangar_rotors/web-template/brain.py diff --git a/exercises/static/exercises/drone_hangar/web-template/code/academy.py b/exercises/static/exercises/drone_hangar_rotors/web-template/code/academy.py similarity index 100% rename from exercises/static/exercises/drone_hangar/web-template/code/academy.py rename to exercises/static/exercises/drone_hangar_rotors/web-template/code/academy.py diff --git a/exercises/static/exercises/drone_hangar/web-template/console.py b/exercises/static/exercises/drone_hangar_rotors/web-template/console.py similarity index 100% rename from exercises/static/exercises/drone_hangar/web-template/console.py rename to exercises/static/exercises/drone_hangar_rotors/web-template/console.py diff --git a/exercises/static/exercises/drone_hangar/web-template/drone_hangar.world b/exercises/static/exercises/drone_hangar_rotors/web-template/drone_hangar.world similarity index 100% rename from exercises/static/exercises/drone_hangar/web-template/drone_hangar.world rename to exercises/static/exercises/drone_hangar_rotors/web-template/drone_hangar.world diff --git a/exercises/static/exercises/drone_hangar/web-template/exercise.py b/exercises/static/exercises/drone_hangar_rotors/web-template/exercise.py similarity index 100% rename from exercises/static/exercises/drone_hangar/web-template/exercise.py rename to exercises/static/exercises/drone_hangar_rotors/web-template/exercise.py diff --git a/exercises/static/exercises/drone_hangar/web-template/gui.py b/exercises/static/exercises/drone_hangar_rotors/web-template/gui.py similarity index 100% rename from exercises/static/exercises/drone_hangar/web-template/gui.py rename to exercises/static/exercises/drone_hangar_rotors/web-template/gui.py diff --git a/exercises/static/exercises/drone_hangar/web-template/hal.py b/exercises/static/exercises/drone_hangar_rotors/web-template/hal.py similarity index 100% rename from exercises/static/exercises/drone_hangar/web-template/hal.py rename to exercises/static/exercises/drone_hangar_rotors/web-template/hal.py diff --git a/exercises/static/exercises/drone_hangar/web-template/interfaces/__init__.py b/exercises/static/exercises/drone_hangar_rotors/web-template/interfaces/__init__.py similarity index 100% rename from exercises/static/exercises/drone_hangar/web-template/interfaces/__init__.py rename to exercises/static/exercises/drone_hangar_rotors/web-template/interfaces/__init__.py diff --git a/exercises/static/exercises/drone_hangar/web-template/interfaces/camera.py b/exercises/static/exercises/drone_hangar_rotors/web-template/interfaces/camera.py similarity index 100% rename from exercises/static/exercises/drone_hangar/web-template/interfaces/camera.py rename to exercises/static/exercises/drone_hangar_rotors/web-template/interfaces/camera.py diff --git a/exercises/static/exercises/drone_hangar/web-template/interfaces/motors.py b/exercises/static/exercises/drone_hangar_rotors/web-template/interfaces/motors.py similarity index 100% rename from exercises/static/exercises/drone_hangar/web-template/interfaces/motors.py rename to exercises/static/exercises/drone_hangar_rotors/web-template/interfaces/motors.py diff --git a/exercises/static/exercises/drone_hangar/web-template/interfaces/pose3d.py b/exercises/static/exercises/drone_hangar_rotors/web-template/interfaces/pose3d.py similarity index 100% rename from exercises/static/exercises/drone_hangar/web-template/interfaces/pose3d.py rename to exercises/static/exercises/drone_hangar_rotors/web-template/interfaces/pose3d.py diff --git a/exercises/static/exercises/drone_hangar/web-template/interfaces/threadPublisher.py b/exercises/static/exercises/drone_hangar_rotors/web-template/interfaces/threadPublisher.py similarity index 100% rename from exercises/static/exercises/drone_hangar/web-template/interfaces/threadPublisher.py rename to exercises/static/exercises/drone_hangar_rotors/web-template/interfaces/threadPublisher.py diff --git a/exercises/static/exercises/drone_hangar/web-template/interfaces/threadStoppable.py b/exercises/static/exercises/drone_hangar_rotors/web-template/interfaces/threadStoppable.py similarity index 100% rename from exercises/static/exercises/drone_hangar/web-template/interfaces/threadStoppable.py rename to exercises/static/exercises/drone_hangar_rotors/web-template/interfaces/threadStoppable.py diff --git a/exercises/static/exercises/drone_hangar/web-template/launch/drone_hangar.launch b/exercises/static/exercises/drone_hangar_rotors/web-template/launch/drone_hangar.launch similarity index 100% rename from exercises/static/exercises/drone_hangar/web-template/launch/drone_hangar.launch rename to exercises/static/exercises/drone_hangar_rotors/web-template/launch/drone_hangar.launch diff --git a/exercises/static/exercises/drone_hangar/web-template/shared/__init__.py b/exercises/static/exercises/drone_hangar_rotors/web-template/shared/__init__.py similarity index 100% rename from exercises/static/exercises/drone_hangar/web-template/shared/__init__.py rename to exercises/static/exercises/drone_hangar_rotors/web-template/shared/__init__.py diff --git a/exercises/static/exercises/drone_hangar/web-template/shared/image.py b/exercises/static/exercises/drone_hangar_rotors/web-template/shared/image.py similarity index 100% rename from exercises/static/exercises/drone_hangar/web-template/shared/image.py rename to exercises/static/exercises/drone_hangar_rotors/web-template/shared/image.py diff --git a/exercises/static/exercises/drone_hangar/web-template/shared/structure_img.py b/exercises/static/exercises/drone_hangar_rotors/web-template/shared/structure_img.py similarity index 100% rename from exercises/static/exercises/drone_hangar/web-template/shared/structure_img.py rename to exercises/static/exercises/drone_hangar_rotors/web-template/shared/structure_img.py diff --git a/exercises/static/exercises/drone_hangar/web-template/shared/value.py b/exercises/static/exercises/drone_hangar_rotors/web-template/shared/value.py similarity index 100% rename from exercises/static/exercises/drone_hangar/web-template/shared/value.py rename to exercises/static/exercises/drone_hangar_rotors/web-template/shared/value.py diff --git a/exercises/static/exercises/drone_hangar/web-template/user_functions.py b/exercises/static/exercises/drone_hangar_rotors/web-template/user_functions.py similarity index 100% rename from exercises/static/exercises/drone_hangar/web-template/user_functions.py rename to exercises/static/exercises/drone_hangar_rotors/web-template/user_functions.py diff --git a/exercises/static/exercises/follow_road_2/README.md b/exercises/static/exercises/follow_road_rotors/README.md similarity index 100% rename from exercises/static/exercises/follow_road_2/README.md rename to exercises/static/exercises/follow_road_rotors/README.md diff --git a/exercises/static/exercises/follow_road_2/follow_road.launch b/exercises/static/exercises/follow_road_rotors/follow_road.launch similarity index 100% rename from exercises/static/exercises/follow_road_2/follow_road.launch rename to exercises/static/exercises/follow_road_rotors/follow_road.launch diff --git a/exercises/static/exercises/follow_road_2/follow_road.world b/exercises/static/exercises/follow_road_rotors/follow_road.world similarity index 100% rename from exercises/static/exercises/follow_road_2/follow_road.world rename to exercises/static/exercises/follow_road_rotors/follow_road.world diff --git a/exercises/static/exercises/follow_road_2/my_solution.py b/exercises/static/exercises/follow_road_rotors/my_solution.py similarity index 100% rename from exercises/static/exercises/follow_road_2/my_solution.py rename to exercises/static/exercises/follow_road_rotors/my_solution.py diff --git a/exercises/static/exercises/follow_road_2/web-template/RADI-launch b/exercises/static/exercises/follow_road_rotors/web-template/RADI-launch similarity index 100% rename from exercises/static/exercises/follow_road_2/web-template/RADI-launch rename to exercises/static/exercises/follow_road_rotors/web-template/RADI-launch diff --git a/exercises/static/exercises/follow_road_2/web-template/README.md b/exercises/static/exercises/follow_road_rotors/web-template/README.md similarity index 100% rename from exercises/static/exercises/follow_road_2/web-template/README.md rename to exercises/static/exercises/follow_road_rotors/web-template/README.md diff --git a/exercises/static/exercises/follow_road_2/web-template/brain.py b/exercises/static/exercises/follow_road_rotors/web-template/brain.py similarity index 100% rename from exercises/static/exercises/follow_road_2/web-template/brain.py rename to exercises/static/exercises/follow_road_rotors/web-template/brain.py diff --git a/exercises/static/exercises/follow_road_2/web-template/code/academy.py b/exercises/static/exercises/follow_road_rotors/web-template/code/academy.py similarity index 100% rename from exercises/static/exercises/follow_road_2/web-template/code/academy.py rename to exercises/static/exercises/follow_road_rotors/web-template/code/academy.py diff --git a/exercises/static/exercises/follow_road_2/web-template/console.py b/exercises/static/exercises/follow_road_rotors/web-template/console.py similarity index 100% rename from exercises/static/exercises/follow_road_2/web-template/console.py rename to exercises/static/exercises/follow_road_rotors/web-template/console.py diff --git a/exercises/static/exercises/follow_road_2/web-template/exercise.py b/exercises/static/exercises/follow_road_rotors/web-template/exercise.py similarity index 100% rename from exercises/static/exercises/follow_road_2/web-template/exercise.py rename to exercises/static/exercises/follow_road_rotors/web-template/exercise.py diff --git a/exercises/static/exercises/follow_road_2/web-template/follow_road.world b/exercises/static/exercises/follow_road_rotors/web-template/follow_road.world similarity index 100% rename from exercises/static/exercises/follow_road_2/web-template/follow_road.world rename to exercises/static/exercises/follow_road_rotors/web-template/follow_road.world diff --git a/exercises/static/exercises/follow_road_2/web-template/gui.py b/exercises/static/exercises/follow_road_rotors/web-template/gui.py similarity index 100% rename from exercises/static/exercises/follow_road_2/web-template/gui.py rename to exercises/static/exercises/follow_road_rotors/web-template/gui.py diff --git a/exercises/static/exercises/follow_road_2/web-template/hal.py b/exercises/static/exercises/follow_road_rotors/web-template/hal.py similarity index 100% rename from exercises/static/exercises/follow_road_2/web-template/hal.py rename to exercises/static/exercises/follow_road_rotors/web-template/hal.py diff --git a/exercises/static/exercises/follow_road_2/web-template/interfaces/__init__.py b/exercises/static/exercises/follow_road_rotors/web-template/interfaces/__init__.py similarity index 100% rename from exercises/static/exercises/follow_road_2/web-template/interfaces/__init__.py rename to exercises/static/exercises/follow_road_rotors/web-template/interfaces/__init__.py diff --git a/exercises/static/exercises/follow_road_2/web-template/interfaces/camera.py b/exercises/static/exercises/follow_road_rotors/web-template/interfaces/camera.py similarity index 100% rename from exercises/static/exercises/follow_road_2/web-template/interfaces/camera.py rename to exercises/static/exercises/follow_road_rotors/web-template/interfaces/camera.py diff --git a/exercises/static/exercises/follow_road_2/web-template/interfaces/motors.py b/exercises/static/exercises/follow_road_rotors/web-template/interfaces/motors.py similarity index 100% rename from exercises/static/exercises/follow_road_2/web-template/interfaces/motors.py rename to exercises/static/exercises/follow_road_rotors/web-template/interfaces/motors.py diff --git a/exercises/static/exercises/follow_road_2/web-template/interfaces/pose3d.py b/exercises/static/exercises/follow_road_rotors/web-template/interfaces/pose3d.py similarity index 100% rename from exercises/static/exercises/follow_road_2/web-template/interfaces/pose3d.py rename to exercises/static/exercises/follow_road_rotors/web-template/interfaces/pose3d.py diff --git a/exercises/static/exercises/follow_road_2/web-template/interfaces/threadPublisher.py b/exercises/static/exercises/follow_road_rotors/web-template/interfaces/threadPublisher.py similarity index 100% rename from exercises/static/exercises/follow_road_2/web-template/interfaces/threadPublisher.py rename to exercises/static/exercises/follow_road_rotors/web-template/interfaces/threadPublisher.py diff --git a/exercises/static/exercises/follow_road_2/web-template/launch/follow_road.launch b/exercises/static/exercises/follow_road_rotors/web-template/launch/follow_road.launch similarity index 100% rename from exercises/static/exercises/follow_road_2/web-template/launch/follow_road.launch rename to exercises/static/exercises/follow_road_rotors/web-template/launch/follow_road.launch diff --git a/exercises/static/exercises/follow_road_2/web-template/shared/__init__.py b/exercises/static/exercises/follow_road_rotors/web-template/shared/__init__.py similarity index 100% rename from exercises/static/exercises/follow_road_2/web-template/shared/__init__.py rename to exercises/static/exercises/follow_road_rotors/web-template/shared/__init__.py diff --git a/exercises/static/exercises/follow_road_2/web-template/shared/image.py b/exercises/static/exercises/follow_road_rotors/web-template/shared/image.py similarity index 100% rename from exercises/static/exercises/follow_road_2/web-template/shared/image.py rename to exercises/static/exercises/follow_road_rotors/web-template/shared/image.py diff --git a/exercises/static/exercises/follow_road_2/web-template/shared/structure_img.py b/exercises/static/exercises/follow_road_rotors/web-template/shared/structure_img.py similarity index 100% rename from exercises/static/exercises/follow_road_2/web-template/shared/structure_img.py rename to exercises/static/exercises/follow_road_rotors/web-template/shared/structure_img.py diff --git a/exercises/static/exercises/follow_road_2/web-template/shared/value.py b/exercises/static/exercises/follow_road_rotors/web-template/shared/value.py similarity index 100% rename from exercises/static/exercises/follow_road_2/web-template/shared/value.py rename to exercises/static/exercises/follow_road_rotors/web-template/shared/value.py diff --git a/exercises/static/exercises/follow_road_2/web-template/user_functions.py b/exercises/static/exercises/follow_road_rotors/web-template/user_functions.py similarity index 100% rename from exercises/static/exercises/follow_road_2/web-template/user_functions.py rename to exercises/static/exercises/follow_road_rotors/web-template/user_functions.py diff --git a/exercises/static/exercises/follow_turtlebot/README.md b/exercises/static/exercises/follow_turtlebot_rotors/README.md similarity index 100% rename from exercises/static/exercises/follow_turtlebot/README.md rename to exercises/static/exercises/follow_turtlebot_rotors/README.md diff --git a/exercises/static/exercises/follow_turtlebot/follow_turtlebot.launch b/exercises/static/exercises/follow_turtlebot_rotors/follow_turtlebot.launch similarity index 100% rename from exercises/static/exercises/follow_turtlebot/follow_turtlebot.launch rename to exercises/static/exercises/follow_turtlebot_rotors/follow_turtlebot.launch diff --git a/exercises/static/exercises/follow_turtlebot/follow_turtlebot.world b/exercises/static/exercises/follow_turtlebot_rotors/follow_turtlebot.world similarity index 100% rename from exercises/static/exercises/follow_turtlebot/follow_turtlebot.world rename to exercises/static/exercises/follow_turtlebot_rotors/follow_turtlebot.world diff --git a/exercises/static/exercises/follow_turtlebot/my_solution.py b/exercises/static/exercises/follow_turtlebot_rotors/my_solution.py similarity index 100% rename from exercises/static/exercises/follow_turtlebot/my_solution.py rename to exercises/static/exercises/follow_turtlebot_rotors/my_solution.py diff --git a/exercises/static/exercises/follow_turtlebot/web-template/RADI-launch b/exercises/static/exercises/follow_turtlebot_rotors/web-template/RADI-launch similarity index 100% rename from exercises/static/exercises/follow_turtlebot/web-template/RADI-launch rename to exercises/static/exercises/follow_turtlebot_rotors/web-template/RADI-launch diff --git a/exercises/static/exercises/follow_turtlebot/web-template/README.md b/exercises/static/exercises/follow_turtlebot_rotors/web-template/README.md similarity index 100% rename from exercises/static/exercises/follow_turtlebot/web-template/README.md rename to exercises/static/exercises/follow_turtlebot_rotors/web-template/README.md diff --git a/exercises/static/exercises/follow_turtlebot/web-template/brain.py b/exercises/static/exercises/follow_turtlebot_rotors/web-template/brain.py similarity index 100% rename from exercises/static/exercises/follow_turtlebot/web-template/brain.py rename to exercises/static/exercises/follow_turtlebot_rotors/web-template/brain.py diff --git a/exercises/static/exercises/follow_turtlebot/web-template/code/academy.py b/exercises/static/exercises/follow_turtlebot_rotors/web-template/code/academy.py similarity index 100% rename from exercises/static/exercises/follow_turtlebot/web-template/code/academy.py rename to exercises/static/exercises/follow_turtlebot_rotors/web-template/code/academy.py diff --git a/exercises/static/exercises/follow_turtlebot/web-template/console.py b/exercises/static/exercises/follow_turtlebot_rotors/web-template/console.py similarity index 100% rename from exercises/static/exercises/follow_turtlebot/web-template/console.py rename to exercises/static/exercises/follow_turtlebot_rotors/web-template/console.py diff --git a/exercises/static/exercises/follow_turtlebot/web-template/exercise.py b/exercises/static/exercises/follow_turtlebot_rotors/web-template/exercise.py similarity index 100% rename from exercises/static/exercises/follow_turtlebot/web-template/exercise.py rename to exercises/static/exercises/follow_turtlebot_rotors/web-template/exercise.py diff --git a/exercises/static/exercises/follow_turtlebot/web-template/follow_turtlebot.world b/exercises/static/exercises/follow_turtlebot_rotors/web-template/follow_turtlebot.world similarity index 100% rename from exercises/static/exercises/follow_turtlebot/web-template/follow_turtlebot.world rename to exercises/static/exercises/follow_turtlebot_rotors/web-template/follow_turtlebot.world diff --git a/exercises/static/exercises/follow_turtlebot/web-template/gui.py b/exercises/static/exercises/follow_turtlebot_rotors/web-template/gui.py similarity index 100% rename from exercises/static/exercises/follow_turtlebot/web-template/gui.py rename to exercises/static/exercises/follow_turtlebot_rotors/web-template/gui.py diff --git a/exercises/static/exercises/follow_turtlebot/web-template/hal.py b/exercises/static/exercises/follow_turtlebot_rotors/web-template/hal.py similarity index 100% rename from exercises/static/exercises/follow_turtlebot/web-template/hal.py rename to exercises/static/exercises/follow_turtlebot_rotors/web-template/hal.py diff --git a/exercises/static/exercises/follow_turtlebot/web-template/interfaces/__init__.py b/exercises/static/exercises/follow_turtlebot_rotors/web-template/interfaces/__init__.py similarity index 100% rename from exercises/static/exercises/follow_turtlebot/web-template/interfaces/__init__.py rename to exercises/static/exercises/follow_turtlebot_rotors/web-template/interfaces/__init__.py diff --git a/exercises/static/exercises/follow_turtlebot/web-template/interfaces/camera.py b/exercises/static/exercises/follow_turtlebot_rotors/web-template/interfaces/camera.py similarity index 100% rename from exercises/static/exercises/follow_turtlebot/web-template/interfaces/camera.py rename to exercises/static/exercises/follow_turtlebot_rotors/web-template/interfaces/camera.py diff --git a/exercises/static/exercises/follow_turtlebot/web-template/interfaces/motors.py b/exercises/static/exercises/follow_turtlebot_rotors/web-template/interfaces/motors.py similarity index 100% rename from exercises/static/exercises/follow_turtlebot/web-template/interfaces/motors.py rename to exercises/static/exercises/follow_turtlebot_rotors/web-template/interfaces/motors.py diff --git a/exercises/static/exercises/follow_turtlebot/web-template/interfaces/pose3d.py b/exercises/static/exercises/follow_turtlebot_rotors/web-template/interfaces/pose3d.py similarity index 100% rename from exercises/static/exercises/follow_turtlebot/web-template/interfaces/pose3d.py rename to exercises/static/exercises/follow_turtlebot_rotors/web-template/interfaces/pose3d.py diff --git a/exercises/static/exercises/follow_turtlebot/web-template/interfaces/threadPublisher.py b/exercises/static/exercises/follow_turtlebot_rotors/web-template/interfaces/threadPublisher.py similarity index 100% rename from exercises/static/exercises/follow_turtlebot/web-template/interfaces/threadPublisher.py rename to exercises/static/exercises/follow_turtlebot_rotors/web-template/interfaces/threadPublisher.py diff --git a/exercises/static/exercises/follow_turtlebot/web-template/interfaces/threadStoppable.py b/exercises/static/exercises/follow_turtlebot_rotors/web-template/interfaces/threadStoppable.py similarity index 100% rename from exercises/static/exercises/follow_turtlebot/web-template/interfaces/threadStoppable.py rename to exercises/static/exercises/follow_turtlebot_rotors/web-template/interfaces/threadStoppable.py diff --git a/exercises/static/exercises/follow_turtlebot/web-template/launch/follow_turtlebot.launch b/exercises/static/exercises/follow_turtlebot_rotors/web-template/launch/follow_turtlebot.launch similarity index 100% rename from exercises/static/exercises/follow_turtlebot/web-template/launch/follow_turtlebot.launch rename to exercises/static/exercises/follow_turtlebot_rotors/web-template/launch/follow_turtlebot.launch diff --git a/exercises/static/exercises/follow_turtlebot/web-template/shared/__init__.py b/exercises/static/exercises/follow_turtlebot_rotors/web-template/shared/__init__.py similarity index 100% rename from exercises/static/exercises/follow_turtlebot/web-template/shared/__init__.py rename to exercises/static/exercises/follow_turtlebot_rotors/web-template/shared/__init__.py diff --git a/exercises/static/exercises/follow_turtlebot/web-template/shared/image.py b/exercises/static/exercises/follow_turtlebot_rotors/web-template/shared/image.py similarity index 100% rename from exercises/static/exercises/follow_turtlebot/web-template/shared/image.py rename to exercises/static/exercises/follow_turtlebot_rotors/web-template/shared/image.py diff --git a/exercises/static/exercises/follow_turtlebot/web-template/shared/structure_img.py b/exercises/static/exercises/follow_turtlebot_rotors/web-template/shared/structure_img.py similarity index 100% rename from exercises/static/exercises/follow_turtlebot/web-template/shared/structure_img.py rename to exercises/static/exercises/follow_turtlebot_rotors/web-template/shared/structure_img.py diff --git a/exercises/static/exercises/follow_turtlebot/web-template/shared/turtlebot.py b/exercises/static/exercises/follow_turtlebot_rotors/web-template/shared/turtlebot.py similarity index 100% rename from exercises/static/exercises/follow_turtlebot/web-template/shared/turtlebot.py rename to exercises/static/exercises/follow_turtlebot_rotors/web-template/shared/turtlebot.py diff --git a/exercises/static/exercises/follow_turtlebot/web-template/shared/value.py b/exercises/static/exercises/follow_turtlebot_rotors/web-template/shared/value.py similarity index 100% rename from exercises/static/exercises/follow_turtlebot/web-template/shared/value.py rename to exercises/static/exercises/follow_turtlebot_rotors/web-template/shared/value.py diff --git a/exercises/static/exercises/follow_turtlebot/web-template/user_functions.py b/exercises/static/exercises/follow_turtlebot_rotors/web-template/user_functions.py similarity index 100% rename from exercises/static/exercises/follow_turtlebot/web-template/user_functions.py rename to exercises/static/exercises/follow_turtlebot_rotors/web-template/user_functions.py diff --git a/exercises/static/exercises/labyrinth_escape/README.md b/exercises/static/exercises/labyrinth_escape_rotors/README.md similarity index 100% rename from exercises/static/exercises/labyrinth_escape/README.md rename to exercises/static/exercises/labyrinth_escape_rotors/README.md diff --git a/exercises/static/exercises/labyrinth_escape/arrows/down.png b/exercises/static/exercises/labyrinth_escape_rotors/arrows/down.png similarity index 100% rename from exercises/static/exercises/labyrinth_escape/arrows/down.png rename to exercises/static/exercises/labyrinth_escape_rotors/arrows/down.png diff --git a/exercises/static/exercises/labyrinth_escape/arrows/left.png b/exercises/static/exercises/labyrinth_escape_rotors/arrows/left.png similarity index 100% rename from exercises/static/exercises/labyrinth_escape/arrows/left.png rename to exercises/static/exercises/labyrinth_escape_rotors/arrows/left.png diff --git a/exercises/static/exercises/labyrinth_escape/arrows/right.png b/exercises/static/exercises/labyrinth_escape_rotors/arrows/right.png similarity index 100% rename from exercises/static/exercises/labyrinth_escape/arrows/right.png rename to exercises/static/exercises/labyrinth_escape_rotors/arrows/right.png diff --git a/exercises/static/exercises/labyrinth_escape/arrows/up.png b/exercises/static/exercises/labyrinth_escape_rotors/arrows/up.png similarity index 100% rename from exercises/static/exercises/labyrinth_escape/arrows/up.png rename to exercises/static/exercises/labyrinth_escape_rotors/arrows/up.png diff --git a/exercises/static/exercises/labyrinth_escape/labyrinth.world b/exercises/static/exercises/labyrinth_escape_rotors/labyrinth.world similarity index 100% rename from exercises/static/exercises/labyrinth_escape/labyrinth.world rename to exercises/static/exercises/labyrinth_escape_rotors/labyrinth.world diff --git a/exercises/static/exercises/labyrinth_escape/labyrinth_escape.launch b/exercises/static/exercises/labyrinth_escape_rotors/labyrinth_escape.launch similarity index 100% rename from exercises/static/exercises/labyrinth_escape/labyrinth_escape.launch rename to exercises/static/exercises/labyrinth_escape_rotors/labyrinth_escape.launch diff --git a/exercises/static/exercises/labyrinth_escape/my_solution.py b/exercises/static/exercises/labyrinth_escape_rotors/my_solution.py similarity index 100% rename from exercises/static/exercises/labyrinth_escape/my_solution.py rename to exercises/static/exercises/labyrinth_escape_rotors/my_solution.py diff --git a/exercises/static/exercises/labyrinth_escape/web-template/RADI-launch b/exercises/static/exercises/labyrinth_escape_rotors/web-template/RADI-launch similarity index 100% rename from exercises/static/exercises/labyrinth_escape/web-template/RADI-launch rename to exercises/static/exercises/labyrinth_escape_rotors/web-template/RADI-launch diff --git a/exercises/static/exercises/labyrinth_escape/web-template/README.md b/exercises/static/exercises/labyrinth_escape_rotors/web-template/README.md similarity index 100% rename from exercises/static/exercises/labyrinth_escape/web-template/README.md rename to exercises/static/exercises/labyrinth_escape_rotors/web-template/README.md diff --git a/exercises/static/exercises/labyrinth_escape/web-template/brain.py b/exercises/static/exercises/labyrinth_escape_rotors/web-template/brain.py similarity index 100% rename from exercises/static/exercises/labyrinth_escape/web-template/brain.py rename to exercises/static/exercises/labyrinth_escape_rotors/web-template/brain.py diff --git a/exercises/static/exercises/labyrinth_escape/web-template/code/academy.py b/exercises/static/exercises/labyrinth_escape_rotors/web-template/code/academy.py similarity index 100% rename from exercises/static/exercises/labyrinth_escape/web-template/code/academy.py rename to exercises/static/exercises/labyrinth_escape_rotors/web-template/code/academy.py diff --git a/exercises/static/exercises/labyrinth_escape/web-template/console.py b/exercises/static/exercises/labyrinth_escape_rotors/web-template/console.py similarity index 100% rename from exercises/static/exercises/labyrinth_escape/web-template/console.py rename to exercises/static/exercises/labyrinth_escape_rotors/web-template/console.py diff --git a/exercises/static/exercises/labyrinth_escape/web-template/exercise.py b/exercises/static/exercises/labyrinth_escape_rotors/web-template/exercise.py similarity index 100% rename from exercises/static/exercises/labyrinth_escape/web-template/exercise.py rename to exercises/static/exercises/labyrinth_escape_rotors/web-template/exercise.py diff --git a/exercises/static/exercises/labyrinth_escape/web-template/gui.py b/exercises/static/exercises/labyrinth_escape_rotors/web-template/gui.py similarity index 100% rename from exercises/static/exercises/labyrinth_escape/web-template/gui.py rename to exercises/static/exercises/labyrinth_escape_rotors/web-template/gui.py diff --git a/exercises/static/exercises/labyrinth_escape/web-template/hal.py b/exercises/static/exercises/labyrinth_escape_rotors/web-template/hal.py similarity index 100% rename from exercises/static/exercises/labyrinth_escape/web-template/hal.py rename to exercises/static/exercises/labyrinth_escape_rotors/web-template/hal.py diff --git a/exercises/static/exercises/labyrinth_escape/web-template/interfaces/__init__.py b/exercises/static/exercises/labyrinth_escape_rotors/web-template/interfaces/__init__.py similarity index 100% rename from exercises/static/exercises/labyrinth_escape/web-template/interfaces/__init__.py rename to exercises/static/exercises/labyrinth_escape_rotors/web-template/interfaces/__init__.py diff --git a/exercises/static/exercises/labyrinth_escape/web-template/interfaces/camera.py b/exercises/static/exercises/labyrinth_escape_rotors/web-template/interfaces/camera.py similarity index 100% rename from exercises/static/exercises/labyrinth_escape/web-template/interfaces/camera.py rename to exercises/static/exercises/labyrinth_escape_rotors/web-template/interfaces/camera.py diff --git a/exercises/static/exercises/labyrinth_escape/web-template/interfaces/motors.py b/exercises/static/exercises/labyrinth_escape_rotors/web-template/interfaces/motors.py similarity index 100% rename from exercises/static/exercises/labyrinth_escape/web-template/interfaces/motors.py rename to exercises/static/exercises/labyrinth_escape_rotors/web-template/interfaces/motors.py diff --git a/exercises/static/exercises/labyrinth_escape/web-template/interfaces/pose3d.py b/exercises/static/exercises/labyrinth_escape_rotors/web-template/interfaces/pose3d.py similarity index 100% rename from exercises/static/exercises/labyrinth_escape/web-template/interfaces/pose3d.py rename to exercises/static/exercises/labyrinth_escape_rotors/web-template/interfaces/pose3d.py diff --git a/exercises/static/exercises/labyrinth_escape/web-template/interfaces/threadPublisher.py b/exercises/static/exercises/labyrinth_escape_rotors/web-template/interfaces/threadPublisher.py similarity index 100% rename from exercises/static/exercises/labyrinth_escape/web-template/interfaces/threadPublisher.py rename to exercises/static/exercises/labyrinth_escape_rotors/web-template/interfaces/threadPublisher.py diff --git a/exercises/static/exercises/labyrinth_escape/web-template/interfaces/threadStoppable.py b/exercises/static/exercises/labyrinth_escape_rotors/web-template/interfaces/threadStoppable.py similarity index 100% rename from exercises/static/exercises/labyrinth_escape/web-template/interfaces/threadStoppable.py rename to exercises/static/exercises/labyrinth_escape_rotors/web-template/interfaces/threadStoppable.py diff --git a/exercises/static/exercises/labyrinth_escape/web-template/labyrinth_escape.world b/exercises/static/exercises/labyrinth_escape_rotors/web-template/labyrinth_escape.world similarity index 100% rename from exercises/static/exercises/labyrinth_escape/web-template/labyrinth_escape.world rename to exercises/static/exercises/labyrinth_escape_rotors/web-template/labyrinth_escape.world diff --git a/exercises/static/exercises/labyrinth_escape/web-template/launch/labyrinth_escape.launch b/exercises/static/exercises/labyrinth_escape_rotors/web-template/launch/labyrinth_escape.launch similarity index 100% rename from exercises/static/exercises/labyrinth_escape/web-template/launch/labyrinth_escape.launch rename to exercises/static/exercises/labyrinth_escape_rotors/web-template/launch/labyrinth_escape.launch diff --git a/exercises/static/exercises/labyrinth_escape/web-template/shared/__init__.py b/exercises/static/exercises/labyrinth_escape_rotors/web-template/shared/__init__.py similarity index 100% rename from exercises/static/exercises/labyrinth_escape/web-template/shared/__init__.py rename to exercises/static/exercises/labyrinth_escape_rotors/web-template/shared/__init__.py diff --git a/exercises/static/exercises/labyrinth_escape/web-template/shared/image.py b/exercises/static/exercises/labyrinth_escape_rotors/web-template/shared/image.py similarity index 100% rename from exercises/static/exercises/labyrinth_escape/web-template/shared/image.py rename to exercises/static/exercises/labyrinth_escape_rotors/web-template/shared/image.py diff --git a/exercises/static/exercises/labyrinth_escape/web-template/shared/structure_img.py b/exercises/static/exercises/labyrinth_escape_rotors/web-template/shared/structure_img.py similarity index 100% rename from exercises/static/exercises/labyrinth_escape/web-template/shared/structure_img.py rename to exercises/static/exercises/labyrinth_escape_rotors/web-template/shared/structure_img.py diff --git a/exercises/static/exercises/labyrinth_escape/web-template/shared/value.py b/exercises/static/exercises/labyrinth_escape_rotors/web-template/shared/value.py similarity index 100% rename from exercises/static/exercises/labyrinth_escape/web-template/shared/value.py rename to exercises/static/exercises/labyrinth_escape_rotors/web-template/shared/value.py diff --git a/exercises/static/exercises/labyrinth_escape/web-template/user_functions.py b/exercises/static/exercises/labyrinth_escape_rotors/web-template/user_functions.py similarity index 100% rename from exercises/static/exercises/labyrinth_escape/web-template/user_functions.py rename to exercises/static/exercises/labyrinth_escape_rotors/web-template/user_functions.py diff --git a/exercises/static/exercises/package_delivery/README.md b/exercises/static/exercises/package_delivery_rotors/README.md similarity index 100% rename from exercises/static/exercises/package_delivery/README.md rename to exercises/static/exercises/package_delivery_rotors/README.md diff --git a/exercises/static/exercises/package_delivery/magnet.py b/exercises/static/exercises/package_delivery_rotors/magnet.py similarity index 100% rename from exercises/static/exercises/package_delivery/magnet.py rename to exercises/static/exercises/package_delivery_rotors/magnet.py diff --git a/exercises/static/exercises/package_delivery/my_solution.py b/exercises/static/exercises/package_delivery_rotors/my_solution.py similarity index 100% rename from exercises/static/exercises/package_delivery/my_solution.py rename to exercises/static/exercises/package_delivery_rotors/my_solution.py diff --git a/exercises/static/exercises/package_delivery/package_delivery.launch b/exercises/static/exercises/package_delivery_rotors/package_delivery.launch similarity index 100% rename from exercises/static/exercises/package_delivery/package_delivery.launch rename to exercises/static/exercises/package_delivery_rotors/package_delivery.launch diff --git a/exercises/static/exercises/package_delivery/package_delivery.world b/exercises/static/exercises/package_delivery_rotors/package_delivery.world similarity index 100% rename from exercises/static/exercises/package_delivery/package_delivery.world rename to exercises/static/exercises/package_delivery_rotors/package_delivery.world diff --git a/exercises/static/exercises/package_delivery/package_delivery.yaml b/exercises/static/exercises/package_delivery_rotors/package_delivery.yaml similarity index 100% rename from exercises/static/exercises/package_delivery/package_delivery.yaml rename to exercises/static/exercises/package_delivery_rotors/package_delivery.yaml diff --git a/exercises/static/exercises/package_delivery/web-template/README.md b/exercises/static/exercises/package_delivery_rotors/web-template/README.md similarity index 100% rename from exercises/static/exercises/package_delivery/web-template/README.md rename to exercises/static/exercises/package_delivery_rotors/web-template/README.md diff --git a/exercises/static/exercises/package_delivery/web-template/brain.py b/exercises/static/exercises/package_delivery_rotors/web-template/brain.py similarity index 100% rename from exercises/static/exercises/package_delivery/web-template/brain.py rename to exercises/static/exercises/package_delivery_rotors/web-template/brain.py diff --git a/exercises/static/exercises/package_delivery/web-template/code/academy.py b/exercises/static/exercises/package_delivery_rotors/web-template/code/academy.py similarity index 100% rename from exercises/static/exercises/package_delivery/web-template/code/academy.py rename to exercises/static/exercises/package_delivery_rotors/web-template/code/academy.py diff --git a/exercises/static/exercises/package_delivery/web-template/console.py b/exercises/static/exercises/package_delivery_rotors/web-template/console.py similarity index 100% rename from exercises/static/exercises/package_delivery/web-template/console.py rename to exercises/static/exercises/package_delivery_rotors/web-template/console.py diff --git a/exercises/static/exercises/package_delivery/web-template/exercise.py b/exercises/static/exercises/package_delivery_rotors/web-template/exercise.py similarity index 100% rename from exercises/static/exercises/package_delivery/web-template/exercise.py rename to exercises/static/exercises/package_delivery_rotors/web-template/exercise.py diff --git a/exercises/static/exercises/package_delivery/web-template/gui.py b/exercises/static/exercises/package_delivery_rotors/web-template/gui.py similarity index 100% rename from exercises/static/exercises/package_delivery/web-template/gui.py rename to exercises/static/exercises/package_delivery_rotors/web-template/gui.py diff --git a/exercises/static/exercises/package_delivery/web-template/hal.py b/exercises/static/exercises/package_delivery_rotors/web-template/hal.py similarity index 100% rename from exercises/static/exercises/package_delivery/web-template/hal.py rename to exercises/static/exercises/package_delivery_rotors/web-template/hal.py diff --git a/exercises/static/exercises/package_delivery/web-template/interfaces/__init__.py b/exercises/static/exercises/package_delivery_rotors/web-template/interfaces/__init__.py similarity index 100% rename from exercises/static/exercises/package_delivery/web-template/interfaces/__init__.py rename to exercises/static/exercises/package_delivery_rotors/web-template/interfaces/__init__.py diff --git a/exercises/static/exercises/package_delivery/web-template/interfaces/camera.py b/exercises/static/exercises/package_delivery_rotors/web-template/interfaces/camera.py similarity index 100% rename from exercises/static/exercises/package_delivery/web-template/interfaces/camera.py rename to exercises/static/exercises/package_delivery_rotors/web-template/interfaces/camera.py diff --git a/exercises/static/exercises/package_delivery/web-template/interfaces/motors.py b/exercises/static/exercises/package_delivery_rotors/web-template/interfaces/motors.py similarity index 100% rename from exercises/static/exercises/package_delivery/web-template/interfaces/motors.py rename to exercises/static/exercises/package_delivery_rotors/web-template/interfaces/motors.py diff --git a/exercises/static/exercises/package_delivery/web-template/interfaces/pose3d.py b/exercises/static/exercises/package_delivery_rotors/web-template/interfaces/pose3d.py similarity index 100% rename from exercises/static/exercises/package_delivery/web-template/interfaces/pose3d.py rename to exercises/static/exercises/package_delivery_rotors/web-template/interfaces/pose3d.py diff --git a/exercises/static/exercises/package_delivery/web-template/interfaces/threadPublisher.py b/exercises/static/exercises/package_delivery_rotors/web-template/interfaces/threadPublisher.py similarity index 100% rename from exercises/static/exercises/package_delivery/web-template/interfaces/threadPublisher.py rename to exercises/static/exercises/package_delivery_rotors/web-template/interfaces/threadPublisher.py diff --git a/exercises/static/exercises/package_delivery/web-template/launch/package_delivery.launch b/exercises/static/exercises/package_delivery_rotors/web-template/launch/package_delivery.launch similarity index 100% rename from exercises/static/exercises/package_delivery/web-template/launch/package_delivery.launch rename to exercises/static/exercises/package_delivery_rotors/web-template/launch/package_delivery.launch diff --git a/exercises/static/exercises/package_delivery/web-template/package_delivery.world b/exercises/static/exercises/package_delivery_rotors/web-template/package_delivery.world similarity index 100% rename from exercises/static/exercises/package_delivery/web-template/package_delivery.world rename to exercises/static/exercises/package_delivery_rotors/web-template/package_delivery.world diff --git a/exercises/static/exercises/package_delivery/web-template/shared/__init__.py b/exercises/static/exercises/package_delivery_rotors/web-template/shared/__init__.py similarity index 100% rename from exercises/static/exercises/package_delivery/web-template/shared/__init__.py rename to exercises/static/exercises/package_delivery_rotors/web-template/shared/__init__.py diff --git a/exercises/static/exercises/package_delivery/web-template/shared/image.py b/exercises/static/exercises/package_delivery_rotors/web-template/shared/image.py similarity index 100% rename from exercises/static/exercises/package_delivery/web-template/shared/image.py rename to exercises/static/exercises/package_delivery_rotors/web-template/shared/image.py diff --git a/exercises/static/exercises/package_delivery/web-template/shared/magnet.py b/exercises/static/exercises/package_delivery_rotors/web-template/shared/magnet.py similarity index 100% rename from exercises/static/exercises/package_delivery/web-template/shared/magnet.py rename to exercises/static/exercises/package_delivery_rotors/web-template/shared/magnet.py diff --git a/exercises/static/exercises/package_delivery/web-template/shared/structure_img.py b/exercises/static/exercises/package_delivery_rotors/web-template/shared/structure_img.py similarity index 100% rename from exercises/static/exercises/package_delivery/web-template/shared/structure_img.py rename to exercises/static/exercises/package_delivery_rotors/web-template/shared/structure_img.py diff --git a/exercises/static/exercises/package_delivery/web-template/shared/value.py b/exercises/static/exercises/package_delivery_rotors/web-template/shared/value.py similarity index 100% rename from exercises/static/exercises/package_delivery/web-template/shared/value.py rename to exercises/static/exercises/package_delivery_rotors/web-template/shared/value.py diff --git a/exercises/static/exercises/package_delivery/web-template/user_functions.py b/exercises/static/exercises/package_delivery_rotors/web-template/user_functions.py similarity index 100% rename from exercises/static/exercises/package_delivery/web-template/user_functions.py rename to exercises/static/exercises/package_delivery_rotors/web-template/user_functions.py diff --git a/exercises/static/exercises/position_control/Beacon.py b/exercises/static/exercises/position_control_rotors/Beacon.py similarity index 100% rename from exercises/static/exercises/position_control/Beacon.py rename to exercises/static/exercises/position_control_rotors/Beacon.py diff --git a/exercises/static/exercises/position_control/README.md b/exercises/static/exercises/position_control_rotors/README.md similarity index 100% rename from exercises/static/exercises/position_control/README.md rename to exercises/static/exercises/position_control_rotors/README.md diff --git a/exercises/static/exercises/position_control/my_solution.py b/exercises/static/exercises/position_control_rotors/my_solution.py similarity index 100% rename from exercises/static/exercises/position_control/my_solution.py rename to exercises/static/exercises/position_control_rotors/my_solution.py diff --git a/exercises/static/exercises/position_control/position_control.launch b/exercises/static/exercises/position_control_rotors/position_control.launch similarity index 100% rename from exercises/static/exercises/position_control/position_control.launch rename to exercises/static/exercises/position_control_rotors/position_control.launch diff --git a/exercises/static/exercises/position_control/position_control.world b/exercises/static/exercises/position_control_rotors/position_control.world similarity index 100% rename from exercises/static/exercises/position_control/position_control.world rename to exercises/static/exercises/position_control_rotors/position_control.world diff --git a/exercises/static/exercises/position_control/web-template/RADI-launch b/exercises/static/exercises/position_control_rotors/web-template/RADI-launch similarity index 100% rename from exercises/static/exercises/position_control/web-template/RADI-launch rename to exercises/static/exercises/position_control_rotors/web-template/RADI-launch diff --git a/exercises/static/exercises/position_control/web-template/README.md b/exercises/static/exercises/position_control_rotors/web-template/README.md similarity index 100% rename from exercises/static/exercises/position_control/web-template/README.md rename to exercises/static/exercises/position_control_rotors/web-template/README.md diff --git a/exercises/static/exercises/position_control/web-template/brain.py b/exercises/static/exercises/position_control_rotors/web-template/brain.py similarity index 100% rename from exercises/static/exercises/position_control/web-template/brain.py rename to exercises/static/exercises/position_control_rotors/web-template/brain.py diff --git a/exercises/static/exercises/position_control/web-template/code/academy.py b/exercises/static/exercises/position_control_rotors/web-template/code/academy.py similarity index 100% rename from exercises/static/exercises/position_control/web-template/code/academy.py rename to exercises/static/exercises/position_control_rotors/web-template/code/academy.py diff --git a/exercises/static/exercises/position_control/web-template/console.py b/exercises/static/exercises/position_control_rotors/web-template/console.py similarity index 100% rename from exercises/static/exercises/position_control/web-template/console.py rename to exercises/static/exercises/position_control_rotors/web-template/console.py diff --git a/exercises/static/exercises/position_control/web-template/exercise.py b/exercises/static/exercises/position_control_rotors/web-template/exercise.py similarity index 100% rename from exercises/static/exercises/position_control/web-template/exercise.py rename to exercises/static/exercises/position_control_rotors/web-template/exercise.py diff --git a/exercises/static/exercises/position_control/web-template/gui.py b/exercises/static/exercises/position_control_rotors/web-template/gui.py similarity index 100% rename from exercises/static/exercises/position_control/web-template/gui.py rename to exercises/static/exercises/position_control_rotors/web-template/gui.py diff --git a/exercises/static/exercises/position_control/web-template/hal.py b/exercises/static/exercises/position_control_rotors/web-template/hal.py similarity index 100% rename from exercises/static/exercises/position_control/web-template/hal.py rename to exercises/static/exercises/position_control_rotors/web-template/hal.py diff --git a/exercises/static/exercises/position_control/web-template/interfaces/__init__.py b/exercises/static/exercises/position_control_rotors/web-template/interfaces/__init__.py similarity index 100% rename from exercises/static/exercises/position_control/web-template/interfaces/__init__.py rename to exercises/static/exercises/position_control_rotors/web-template/interfaces/__init__.py diff --git a/exercises/static/exercises/position_control/web-template/interfaces/camera.py b/exercises/static/exercises/position_control_rotors/web-template/interfaces/camera.py similarity index 100% rename from exercises/static/exercises/position_control/web-template/interfaces/camera.py rename to exercises/static/exercises/position_control_rotors/web-template/interfaces/camera.py diff --git a/exercises/static/exercises/position_control/web-template/interfaces/motors.py b/exercises/static/exercises/position_control_rotors/web-template/interfaces/motors.py similarity index 100% rename from exercises/static/exercises/position_control/web-template/interfaces/motors.py rename to exercises/static/exercises/position_control_rotors/web-template/interfaces/motors.py diff --git a/exercises/static/exercises/position_control/web-template/interfaces/pose3d.py b/exercises/static/exercises/position_control_rotors/web-template/interfaces/pose3d.py similarity index 100% rename from exercises/static/exercises/position_control/web-template/interfaces/pose3d.py rename to exercises/static/exercises/position_control_rotors/web-template/interfaces/pose3d.py diff --git a/exercises/static/exercises/position_control/web-template/interfaces/threadPublisher.py b/exercises/static/exercises/position_control_rotors/web-template/interfaces/threadPublisher.py similarity index 100% rename from exercises/static/exercises/position_control/web-template/interfaces/threadPublisher.py rename to exercises/static/exercises/position_control_rotors/web-template/interfaces/threadPublisher.py diff --git a/exercises/static/exercises/position_control/web-template/interfaces/threadStoppable.py b/exercises/static/exercises/position_control_rotors/web-template/interfaces/threadStoppable.py similarity index 100% rename from exercises/static/exercises/position_control/web-template/interfaces/threadStoppable.py rename to exercises/static/exercises/position_control_rotors/web-template/interfaces/threadStoppable.py diff --git a/exercises/static/exercises/position_control/web-template/launch/position_control.launch b/exercises/static/exercises/position_control_rotors/web-template/launch/position_control.launch similarity index 100% rename from exercises/static/exercises/position_control/web-template/launch/position_control.launch rename to exercises/static/exercises/position_control_rotors/web-template/launch/position_control.launch diff --git a/exercises/static/exercises/position_control/web-template/position_control.world b/exercises/static/exercises/position_control_rotors/web-template/position_control.world similarity index 100% rename from exercises/static/exercises/position_control/web-template/position_control.world rename to exercises/static/exercises/position_control_rotors/web-template/position_control.world diff --git a/exercises/static/exercises/position_control/web-template/shared/Beacon.py b/exercises/static/exercises/position_control_rotors/web-template/shared/Beacon.py similarity index 100% rename from exercises/static/exercises/position_control/web-template/shared/Beacon.py rename to exercises/static/exercises/position_control_rotors/web-template/shared/Beacon.py diff --git a/exercises/static/exercises/position_control/web-template/shared/__init__.py b/exercises/static/exercises/position_control_rotors/web-template/shared/__init__.py similarity index 100% rename from exercises/static/exercises/position_control/web-template/shared/__init__.py rename to exercises/static/exercises/position_control_rotors/web-template/shared/__init__.py diff --git a/exercises/static/exercises/position_control/web-template/shared/image.py b/exercises/static/exercises/position_control_rotors/web-template/shared/image.py similarity index 100% rename from exercises/static/exercises/position_control/web-template/shared/image.py rename to exercises/static/exercises/position_control_rotors/web-template/shared/image.py diff --git a/exercises/static/exercises/position_control/web-template/shared/structure_img.py b/exercises/static/exercises/position_control_rotors/web-template/shared/structure_img.py similarity index 100% rename from exercises/static/exercises/position_control/web-template/shared/structure_img.py rename to exercises/static/exercises/position_control_rotors/web-template/shared/structure_img.py diff --git a/exercises/static/exercises/position_control/web-template/shared/value.py b/exercises/static/exercises/position_control_rotors/web-template/shared/value.py similarity index 100% rename from exercises/static/exercises/position_control/web-template/shared/value.py rename to exercises/static/exercises/position_control_rotors/web-template/shared/value.py diff --git a/exercises/static/exercises/position_control/web-template/user_functions.py b/exercises/static/exercises/position_control_rotors/web-template/user_functions.py similarity index 100% rename from exercises/static/exercises/position_control/web-template/user_functions.py rename to exercises/static/exercises/position_control_rotors/web-template/user_functions.py diff --git a/exercises/static/exercises/power_tower_inspection/README.md b/exercises/static/exercises/power_tower_inspection_rotors/README.md similarity index 100% rename from exercises/static/exercises/power_tower_inspection/README.md rename to exercises/static/exercises/power_tower_inspection_rotors/README.md diff --git a/exercises/static/exercises/power_tower_inspection/my_solution.py b/exercises/static/exercises/power_tower_inspection_rotors/my_solution.py similarity index 100% rename from exercises/static/exercises/power_tower_inspection/my_solution.py rename to exercises/static/exercises/power_tower_inspection_rotors/my_solution.py diff --git a/exercises/static/exercises/power_tower_inspection/power_tower_inspection.launch b/exercises/static/exercises/power_tower_inspection_rotors/power_tower_inspection.launch similarity index 100% rename from exercises/static/exercises/power_tower_inspection/power_tower_inspection.launch rename to exercises/static/exercises/power_tower_inspection_rotors/power_tower_inspection.launch diff --git a/exercises/static/exercises/power_tower_inspection/power_tower_inspection.world b/exercises/static/exercises/power_tower_inspection_rotors/power_tower_inspection.world similarity index 100% rename from exercises/static/exercises/power_tower_inspection/power_tower_inspection.world rename to exercises/static/exercises/power_tower_inspection_rotors/power_tower_inspection.world diff --git a/exercises/static/exercises/power_tower_inspection/web-template/README.md b/exercises/static/exercises/power_tower_inspection_rotors/web-template/README.md similarity index 100% rename from exercises/static/exercises/power_tower_inspection/web-template/README.md rename to exercises/static/exercises/power_tower_inspection_rotors/web-template/README.md diff --git a/exercises/static/exercises/power_tower_inspection/web-template/brain.py b/exercises/static/exercises/power_tower_inspection_rotors/web-template/brain.py similarity index 100% rename from exercises/static/exercises/power_tower_inspection/web-template/brain.py rename to exercises/static/exercises/power_tower_inspection_rotors/web-template/brain.py diff --git a/exercises/static/exercises/power_tower_inspection/web-template/code/academy.py b/exercises/static/exercises/power_tower_inspection_rotors/web-template/code/academy.py similarity index 100% rename from exercises/static/exercises/power_tower_inspection/web-template/code/academy.py rename to exercises/static/exercises/power_tower_inspection_rotors/web-template/code/academy.py diff --git a/exercises/static/exercises/power_tower_inspection/web-template/console.py b/exercises/static/exercises/power_tower_inspection_rotors/web-template/console.py similarity index 100% rename from exercises/static/exercises/power_tower_inspection/web-template/console.py rename to exercises/static/exercises/power_tower_inspection_rotors/web-template/console.py diff --git a/exercises/static/exercises/power_tower_inspection/web-template/exercise.py b/exercises/static/exercises/power_tower_inspection_rotors/web-template/exercise.py similarity index 100% rename from exercises/static/exercises/power_tower_inspection/web-template/exercise.py rename to exercises/static/exercises/power_tower_inspection_rotors/web-template/exercise.py diff --git a/exercises/static/exercises/power_tower_inspection/web-template/gui.py b/exercises/static/exercises/power_tower_inspection_rotors/web-template/gui.py similarity index 100% rename from exercises/static/exercises/power_tower_inspection/web-template/gui.py rename to exercises/static/exercises/power_tower_inspection_rotors/web-template/gui.py diff --git a/exercises/static/exercises/power_tower_inspection/web-template/hal.py b/exercises/static/exercises/power_tower_inspection_rotors/web-template/hal.py similarity index 100% rename from exercises/static/exercises/power_tower_inspection/web-template/hal.py rename to exercises/static/exercises/power_tower_inspection_rotors/web-template/hal.py diff --git a/exercises/static/exercises/power_tower_inspection/web-template/interfaces/__init__.py b/exercises/static/exercises/power_tower_inspection_rotors/web-template/interfaces/__init__.py similarity index 100% rename from exercises/static/exercises/power_tower_inspection/web-template/interfaces/__init__.py rename to exercises/static/exercises/power_tower_inspection_rotors/web-template/interfaces/__init__.py diff --git a/exercises/static/exercises/power_tower_inspection/web-template/interfaces/camera.py b/exercises/static/exercises/power_tower_inspection_rotors/web-template/interfaces/camera.py similarity index 100% rename from exercises/static/exercises/power_tower_inspection/web-template/interfaces/camera.py rename to exercises/static/exercises/power_tower_inspection_rotors/web-template/interfaces/camera.py diff --git a/exercises/static/exercises/power_tower_inspection/web-template/interfaces/motors.py b/exercises/static/exercises/power_tower_inspection_rotors/web-template/interfaces/motors.py similarity index 100% rename from exercises/static/exercises/power_tower_inspection/web-template/interfaces/motors.py rename to exercises/static/exercises/power_tower_inspection_rotors/web-template/interfaces/motors.py diff --git a/exercises/static/exercises/power_tower_inspection/web-template/interfaces/pose3d.py b/exercises/static/exercises/power_tower_inspection_rotors/web-template/interfaces/pose3d.py similarity index 100% rename from exercises/static/exercises/power_tower_inspection/web-template/interfaces/pose3d.py rename to exercises/static/exercises/power_tower_inspection_rotors/web-template/interfaces/pose3d.py diff --git a/exercises/static/exercises/power_tower_inspection/web-template/interfaces/threadPublisher.py b/exercises/static/exercises/power_tower_inspection_rotors/web-template/interfaces/threadPublisher.py similarity index 100% rename from exercises/static/exercises/power_tower_inspection/web-template/interfaces/threadPublisher.py rename to exercises/static/exercises/power_tower_inspection_rotors/web-template/interfaces/threadPublisher.py diff --git a/exercises/static/exercises/power_tower_inspection/web-template/launch/power_tower_inspection.launch b/exercises/static/exercises/power_tower_inspection_rotors/web-template/launch/power_tower_inspection.launch similarity index 100% rename from exercises/static/exercises/power_tower_inspection/web-template/launch/power_tower_inspection.launch rename to exercises/static/exercises/power_tower_inspection_rotors/web-template/launch/power_tower_inspection.launch diff --git a/exercises/static/exercises/power_tower_inspection/web-template/power_tower_inspection.world b/exercises/static/exercises/power_tower_inspection_rotors/web-template/power_tower_inspection.world similarity index 100% rename from exercises/static/exercises/power_tower_inspection/web-template/power_tower_inspection.world rename to exercises/static/exercises/power_tower_inspection_rotors/web-template/power_tower_inspection.world diff --git a/exercises/static/exercises/power_tower_inspection/web-template/shared/__init__.py b/exercises/static/exercises/power_tower_inspection_rotors/web-template/shared/__init__.py similarity index 100% rename from exercises/static/exercises/power_tower_inspection/web-template/shared/__init__.py rename to exercises/static/exercises/power_tower_inspection_rotors/web-template/shared/__init__.py diff --git a/exercises/static/exercises/power_tower_inspection/web-template/shared/image.py b/exercises/static/exercises/power_tower_inspection_rotors/web-template/shared/image.py similarity index 100% rename from exercises/static/exercises/power_tower_inspection/web-template/shared/image.py rename to exercises/static/exercises/power_tower_inspection_rotors/web-template/shared/image.py diff --git a/exercises/static/exercises/power_tower_inspection/web-template/shared/structure_img.py b/exercises/static/exercises/power_tower_inspection_rotors/web-template/shared/structure_img.py similarity index 100% rename from exercises/static/exercises/power_tower_inspection/web-template/shared/structure_img.py rename to exercises/static/exercises/power_tower_inspection_rotors/web-template/shared/structure_img.py diff --git a/exercises/static/exercises/power_tower_inspection/web-template/shared/value.py b/exercises/static/exercises/power_tower_inspection_rotors/web-template/shared/value.py similarity index 100% rename from exercises/static/exercises/power_tower_inspection/web-template/shared/value.py rename to exercises/static/exercises/power_tower_inspection_rotors/web-template/shared/value.py diff --git a/exercises/static/exercises/power_tower_inspection/web-template/user_functions.py b/exercises/static/exercises/power_tower_inspection_rotors/web-template/user_functions.py similarity index 100% rename from exercises/static/exercises/power_tower_inspection/web-template/user_functions.py rename to exercises/static/exercises/power_tower_inspection_rotors/web-template/user_functions.py diff --git a/exercises/static/exercises/rescue_people/README.md b/exercises/static/exercises/rescue_people_rotors/README.md similarity index 100% rename from exercises/static/exercises/rescue_people/README.md rename to exercises/static/exercises/rescue_people_rotors/README.md diff --git a/exercises/static/exercises/rescue_people/haarcascade_frontalface_default.xml b/exercises/static/exercises/rescue_people_rotors/haarcascade_frontalface_default.xml similarity index 100% rename from exercises/static/exercises/rescue_people/haarcascade_frontalface_default.xml rename to exercises/static/exercises/rescue_people_rotors/haarcascade_frontalface_default.xml diff --git a/exercises/static/exercises/rescue_people/my_solution.py b/exercises/static/exercises/rescue_people_rotors/my_solution.py similarity index 100% rename from exercises/static/exercises/rescue_people/my_solution.py rename to exercises/static/exercises/rescue_people_rotors/my_solution.py diff --git a/exercises/static/exercises/rescue_people/rescue_people.launch b/exercises/static/exercises/rescue_people_rotors/rescue_people.launch similarity index 100% rename from exercises/static/exercises/rescue_people/rescue_people.launch rename to exercises/static/exercises/rescue_people_rotors/rescue_people.launch diff --git a/exercises/static/exercises/rescue_people/rescue_people.world b/exercises/static/exercises/rescue_people_rotors/rescue_people.world similarity index 100% rename from exercises/static/exercises/rescue_people/rescue_people.world rename to exercises/static/exercises/rescue_people_rotors/rescue_people.world diff --git a/exercises/static/exercises/rescue_people/web-template/RADI-launch b/exercises/static/exercises/rescue_people_rotors/web-template/RADI-launch similarity index 100% rename from exercises/static/exercises/rescue_people/web-template/RADI-launch rename to exercises/static/exercises/rescue_people_rotors/web-template/RADI-launch diff --git a/exercises/static/exercises/rescue_people/web-template/README.md b/exercises/static/exercises/rescue_people_rotors/web-template/README.md similarity index 100% rename from exercises/static/exercises/rescue_people/web-template/README.md rename to exercises/static/exercises/rescue_people_rotors/web-template/README.md diff --git a/exercises/static/exercises/rescue_people/web-template/brain.py b/exercises/static/exercises/rescue_people_rotors/web-template/brain.py similarity index 100% rename from exercises/static/exercises/rescue_people/web-template/brain.py rename to exercises/static/exercises/rescue_people_rotors/web-template/brain.py diff --git a/exercises/static/exercises/rescue_people/web-template/code/academy.py b/exercises/static/exercises/rescue_people_rotors/web-template/code/academy.py similarity index 100% rename from exercises/static/exercises/rescue_people/web-template/code/academy.py rename to exercises/static/exercises/rescue_people_rotors/web-template/code/academy.py diff --git a/exercises/static/exercises/rescue_people/web-template/console.py b/exercises/static/exercises/rescue_people_rotors/web-template/console.py similarity index 100% rename from exercises/static/exercises/rescue_people/web-template/console.py rename to exercises/static/exercises/rescue_people_rotors/web-template/console.py diff --git a/exercises/static/exercises/rescue_people/web-template/exercise.py b/exercises/static/exercises/rescue_people_rotors/web-template/exercise.py similarity index 100% rename from exercises/static/exercises/rescue_people/web-template/exercise.py rename to exercises/static/exercises/rescue_people_rotors/web-template/exercise.py diff --git a/exercises/static/exercises/rescue_people/web-template/gui.py b/exercises/static/exercises/rescue_people_rotors/web-template/gui.py similarity index 100% rename from exercises/static/exercises/rescue_people/web-template/gui.py rename to exercises/static/exercises/rescue_people_rotors/web-template/gui.py diff --git a/exercises/static/exercises/rescue_people/web-template/hal.py b/exercises/static/exercises/rescue_people_rotors/web-template/hal.py similarity index 100% rename from exercises/static/exercises/rescue_people/web-template/hal.py rename to exercises/static/exercises/rescue_people_rotors/web-template/hal.py diff --git a/exercises/static/exercises/rescue_people/web-template/interfaces/__init__.py b/exercises/static/exercises/rescue_people_rotors/web-template/interfaces/__init__.py similarity index 100% rename from exercises/static/exercises/rescue_people/web-template/interfaces/__init__.py rename to exercises/static/exercises/rescue_people_rotors/web-template/interfaces/__init__.py diff --git a/exercises/static/exercises/rescue_people/web-template/interfaces/camera.py b/exercises/static/exercises/rescue_people_rotors/web-template/interfaces/camera.py similarity index 100% rename from exercises/static/exercises/rescue_people/web-template/interfaces/camera.py rename to exercises/static/exercises/rescue_people_rotors/web-template/interfaces/camera.py diff --git a/exercises/static/exercises/rescue_people/web-template/interfaces/motors.py b/exercises/static/exercises/rescue_people_rotors/web-template/interfaces/motors.py similarity index 100% rename from exercises/static/exercises/rescue_people/web-template/interfaces/motors.py rename to exercises/static/exercises/rescue_people_rotors/web-template/interfaces/motors.py diff --git a/exercises/static/exercises/rescue_people/web-template/interfaces/pose3d.py b/exercises/static/exercises/rescue_people_rotors/web-template/interfaces/pose3d.py similarity index 100% rename from exercises/static/exercises/rescue_people/web-template/interfaces/pose3d.py rename to exercises/static/exercises/rescue_people_rotors/web-template/interfaces/pose3d.py diff --git a/exercises/static/exercises/rescue_people/web-template/interfaces/threadPublisher.py b/exercises/static/exercises/rescue_people_rotors/web-template/interfaces/threadPublisher.py similarity index 100% rename from exercises/static/exercises/rescue_people/web-template/interfaces/threadPublisher.py rename to exercises/static/exercises/rescue_people_rotors/web-template/interfaces/threadPublisher.py diff --git a/exercises/static/exercises/rescue_people/web-template/interfaces/threadStoppable.py b/exercises/static/exercises/rescue_people_rotors/web-template/interfaces/threadStoppable.py similarity index 100% rename from exercises/static/exercises/rescue_people/web-template/interfaces/threadStoppable.py rename to exercises/static/exercises/rescue_people_rotors/web-template/interfaces/threadStoppable.py diff --git a/exercises/static/exercises/rescue_people/web-template/launch/rescue_people.launch b/exercises/static/exercises/rescue_people_rotors/web-template/launch/rescue_people.launch similarity index 100% rename from exercises/static/exercises/rescue_people/web-template/launch/rescue_people.launch rename to exercises/static/exercises/rescue_people_rotors/web-template/launch/rescue_people.launch diff --git a/exercises/static/exercises/rescue_people/web-template/launch/rescue_people.sh b/exercises/static/exercises/rescue_people_rotors/web-template/launch/rescue_people.sh similarity index 100% rename from exercises/static/exercises/rescue_people/web-template/launch/rescue_people.sh rename to exercises/static/exercises/rescue_people_rotors/web-template/launch/rescue_people.sh diff --git a/exercises/static/exercises/rescue_people/web-template/rescue_people.world b/exercises/static/exercises/rescue_people_rotors/web-template/rescue_people.world similarity index 100% rename from exercises/static/exercises/rescue_people/web-template/rescue_people.world rename to exercises/static/exercises/rescue_people_rotors/web-template/rescue_people.world diff --git a/exercises/static/exercises/rescue_people/web-template/shared/__init__.py b/exercises/static/exercises/rescue_people_rotors/web-template/shared/__init__.py similarity index 100% rename from exercises/static/exercises/rescue_people/web-template/shared/__init__.py rename to exercises/static/exercises/rescue_people_rotors/web-template/shared/__init__.py diff --git a/exercises/static/exercises/rescue_people/web-template/shared/image.py b/exercises/static/exercises/rescue_people_rotors/web-template/shared/image.py similarity index 100% rename from exercises/static/exercises/rescue_people/web-template/shared/image.py rename to exercises/static/exercises/rescue_people_rotors/web-template/shared/image.py diff --git a/exercises/static/exercises/rescue_people/web-template/shared/light.py b/exercises/static/exercises/rescue_people_rotors/web-template/shared/light.py similarity index 100% rename from exercises/static/exercises/rescue_people/web-template/shared/light.py rename to exercises/static/exercises/rescue_people_rotors/web-template/shared/light.py diff --git a/exercises/static/exercises/rescue_people/web-template/shared/structure_img.py b/exercises/static/exercises/rescue_people_rotors/web-template/shared/structure_img.py similarity index 100% rename from exercises/static/exercises/rescue_people/web-template/shared/structure_img.py rename to exercises/static/exercises/rescue_people_rotors/web-template/shared/structure_img.py diff --git a/exercises/static/exercises/rescue_people/web-template/shared/value.py b/exercises/static/exercises/rescue_people_rotors/web-template/shared/value.py similarity index 100% rename from exercises/static/exercises/rescue_people/web-template/shared/value.py rename to exercises/static/exercises/rescue_people_rotors/web-template/shared/value.py diff --git a/exercises/static/exercises/rescue_people/web-template/user_functions.py b/exercises/static/exercises/rescue_people_rotors/web-template/user_functions.py similarity index 100% rename from exercises/static/exercises/rescue_people/web-template/user_functions.py rename to exercises/static/exercises/rescue_people_rotors/web-template/user_functions.py diff --git a/exercises/static/exercises/visual_lander/README.md b/exercises/static/exercises/visual_lander_rotors/README.md similarity index 100% rename from exercises/static/exercises/visual_lander/README.md rename to exercises/static/exercises/visual_lander_rotors/README.md diff --git a/exercises/static/exercises/visual_lander/my_solution.py b/exercises/static/exercises/visual_lander_rotors/my_solution.py similarity index 100% rename from exercises/static/exercises/visual_lander/my_solution.py rename to exercises/static/exercises/visual_lander_rotors/my_solution.py diff --git a/exercises/static/exercises/visual_lander/visual_lander.launch b/exercises/static/exercises/visual_lander_rotors/visual_lander.launch similarity index 100% rename from exercises/static/exercises/visual_lander/visual_lander.launch rename to exercises/static/exercises/visual_lander_rotors/visual_lander.launch diff --git a/exercises/static/exercises/visual_lander/visual_lander.world b/exercises/static/exercises/visual_lander_rotors/visual_lander.world similarity index 100% rename from exercises/static/exercises/visual_lander/visual_lander.world rename to exercises/static/exercises/visual_lander_rotors/visual_lander.world diff --git a/exercises/static/exercises/visual_lander/web-template/RADI-launch b/exercises/static/exercises/visual_lander_rotors/web-template/RADI-launch similarity index 100% rename from exercises/static/exercises/visual_lander/web-template/RADI-launch rename to exercises/static/exercises/visual_lander_rotors/web-template/RADI-launch diff --git a/exercises/static/exercises/visual_lander/web-template/README.md b/exercises/static/exercises/visual_lander_rotors/web-template/README.md similarity index 100% rename from exercises/static/exercises/visual_lander/web-template/README.md rename to exercises/static/exercises/visual_lander_rotors/web-template/README.md diff --git a/exercises/static/exercises/visual_lander/web-template/brain.py b/exercises/static/exercises/visual_lander_rotors/web-template/brain.py similarity index 100% rename from exercises/static/exercises/visual_lander/web-template/brain.py rename to exercises/static/exercises/visual_lander_rotors/web-template/brain.py diff --git a/exercises/static/exercises/visual_lander/web-template/code/academy.py b/exercises/static/exercises/visual_lander_rotors/web-template/code/academy.py similarity index 100% rename from exercises/static/exercises/visual_lander/web-template/code/academy.py rename to exercises/static/exercises/visual_lander_rotors/web-template/code/academy.py diff --git a/exercises/static/exercises/visual_lander/web-template/console.py b/exercises/static/exercises/visual_lander_rotors/web-template/console.py similarity index 100% rename from exercises/static/exercises/visual_lander/web-template/console.py rename to exercises/static/exercises/visual_lander_rotors/web-template/console.py diff --git a/exercises/static/exercises/visual_lander/web-template/exercise.py b/exercises/static/exercises/visual_lander_rotors/web-template/exercise.py similarity index 100% rename from exercises/static/exercises/visual_lander/web-template/exercise.py rename to exercises/static/exercises/visual_lander_rotors/web-template/exercise.py diff --git a/exercises/static/exercises/visual_lander/web-template/gui.py b/exercises/static/exercises/visual_lander_rotors/web-template/gui.py similarity index 100% rename from exercises/static/exercises/visual_lander/web-template/gui.py rename to exercises/static/exercises/visual_lander_rotors/web-template/gui.py diff --git a/exercises/static/exercises/visual_lander/web-template/hal.py b/exercises/static/exercises/visual_lander_rotors/web-template/hal.py similarity index 100% rename from exercises/static/exercises/visual_lander/web-template/hal.py rename to exercises/static/exercises/visual_lander_rotors/web-template/hal.py diff --git a/exercises/static/exercises/visual_lander/web-template/interfaces/__init__.py b/exercises/static/exercises/visual_lander_rotors/web-template/interfaces/__init__.py similarity index 100% rename from exercises/static/exercises/visual_lander/web-template/interfaces/__init__.py rename to exercises/static/exercises/visual_lander_rotors/web-template/interfaces/__init__.py diff --git a/exercises/static/exercises/visual_lander/web-template/interfaces/camera.py b/exercises/static/exercises/visual_lander_rotors/web-template/interfaces/camera.py similarity index 100% rename from exercises/static/exercises/visual_lander/web-template/interfaces/camera.py rename to exercises/static/exercises/visual_lander_rotors/web-template/interfaces/camera.py diff --git a/exercises/static/exercises/visual_lander/web-template/interfaces/motors.py b/exercises/static/exercises/visual_lander_rotors/web-template/interfaces/motors.py similarity index 100% rename from exercises/static/exercises/visual_lander/web-template/interfaces/motors.py rename to exercises/static/exercises/visual_lander_rotors/web-template/interfaces/motors.py diff --git a/exercises/static/exercises/visual_lander/web-template/interfaces/pose3d.py b/exercises/static/exercises/visual_lander_rotors/web-template/interfaces/pose3d.py similarity index 100% rename from exercises/static/exercises/visual_lander/web-template/interfaces/pose3d.py rename to exercises/static/exercises/visual_lander_rotors/web-template/interfaces/pose3d.py diff --git a/exercises/static/exercises/visual_lander/web-template/interfaces/threadPublisher.py b/exercises/static/exercises/visual_lander_rotors/web-template/interfaces/threadPublisher.py similarity index 100% rename from exercises/static/exercises/visual_lander/web-template/interfaces/threadPublisher.py rename to exercises/static/exercises/visual_lander_rotors/web-template/interfaces/threadPublisher.py diff --git a/exercises/static/exercises/visual_lander/web-template/interfaces/threadStoppable.py b/exercises/static/exercises/visual_lander_rotors/web-template/interfaces/threadStoppable.py similarity index 100% rename from exercises/static/exercises/visual_lander/web-template/interfaces/threadStoppable.py rename to exercises/static/exercises/visual_lander_rotors/web-template/interfaces/threadStoppable.py diff --git a/exercises/static/exercises/visual_lander/web-template/launch/visual_lander.launch b/exercises/static/exercises/visual_lander_rotors/web-template/launch/visual_lander.launch similarity index 100% rename from exercises/static/exercises/visual_lander/web-template/launch/visual_lander.launch rename to exercises/static/exercises/visual_lander_rotors/web-template/launch/visual_lander.launch diff --git a/exercises/static/exercises/visual_lander/web-template/shared/__init__.py b/exercises/static/exercises/visual_lander_rotors/web-template/shared/__init__.py similarity index 100% rename from exercises/static/exercises/visual_lander/web-template/shared/__init__.py rename to exercises/static/exercises/visual_lander_rotors/web-template/shared/__init__.py diff --git a/exercises/static/exercises/visual_lander/web-template/shared/car.py b/exercises/static/exercises/visual_lander_rotors/web-template/shared/car.py similarity index 100% rename from exercises/static/exercises/visual_lander/web-template/shared/car.py rename to exercises/static/exercises/visual_lander_rotors/web-template/shared/car.py diff --git a/exercises/static/exercises/visual_lander/web-template/shared/image.py b/exercises/static/exercises/visual_lander_rotors/web-template/shared/image.py similarity index 100% rename from exercises/static/exercises/visual_lander/web-template/shared/image.py rename to exercises/static/exercises/visual_lander_rotors/web-template/shared/image.py diff --git a/exercises/static/exercises/visual_lander/web-template/shared/structure_img.py b/exercises/static/exercises/visual_lander_rotors/web-template/shared/structure_img.py similarity index 100% rename from exercises/static/exercises/visual_lander/web-template/shared/structure_img.py rename to exercises/static/exercises/visual_lander_rotors/web-template/shared/structure_img.py diff --git a/exercises/static/exercises/visual_lander/web-template/shared/value.py b/exercises/static/exercises/visual_lander_rotors/web-template/shared/value.py similarity index 100% rename from exercises/static/exercises/visual_lander/web-template/shared/value.py rename to exercises/static/exercises/visual_lander_rotors/web-template/shared/value.py diff --git a/exercises/static/exercises/visual_lander/web-template/user_functions.py b/exercises/static/exercises/visual_lander_rotors/web-template/user_functions.py similarity index 100% rename from exercises/static/exercises/visual_lander/web-template/user_functions.py rename to exercises/static/exercises/visual_lander_rotors/web-template/user_functions.py diff --git a/exercises/static/exercises/visual_lander/web-template/visual_lander.world b/exercises/static/exercises/visual_lander_rotors/web-template/visual_lander.world similarity index 100% rename from exercises/static/exercises/visual_lander/web-template/visual_lander.world rename to exercises/static/exercises/visual_lander_rotors/web-template/visual_lander.world From 8dfd99b680554140feaa9ed22b3d7ef3229bebe0 Mon Sep 17 00:00:00 2001 From: Pedro Arias Date: Sun, 2 Oct 2022 13:43:34 +0200 Subject: [PATCH 41/42] restore old drone exercises --- .../exercises/drone_cat_mouse/README.md | 1 + .../drone_cat_mouse/autonomous_mouse.launch | 11 + .../drone_cat_mouse/autonomous_mouse.py | 31 + .../drone_cat_mouse/drone_cat_mouse.launch | 77 + .../drone_cat_mouse/drone_cat_mouse.world | 49 + .../drone_cat_mouse/drone_cat_mouse.yaml | 10 + .../exercises/drone_cat_mouse/my_solution.py | 55 + .../drone_cat_mouse/teleoperated_mouse.py | 70 + .../drone_cat_mouse/web-template/RADI-launch | 2 + .../drone_cat_mouse/web-template/README.md | 1 + .../web-template/code/academy.py | 8 + .../drone_cat_mouse/web-template/console.py | 18 + .../web-template/drone_cat_mouse.world | 81 + .../web-template/drone_cat_mouse.yaml | 10 + .../drone_cat_mouse/web-template/exercise.py | 362 + .../web-template/exercise_guest.py | 318 + .../drone_cat_mouse/web-template/gui.py | 277 + .../drone_cat_mouse/web-template/gui_guest.py | 277 + .../drone_cat_mouse/web-template/hal.py | 81 + .../drone_cat_mouse/web-template/hal_guest.py | 81 + .../web-template/interfaces/__init__.py | 0 .../interfaces/autonomous_mouse.py | 31 + .../web-template/interfaces/camera.py | 89 + .../web-template/interfaces/motors.py | 123 + .../web-template/interfaces/pose3d.py | 176 + .../interfaces/threadPublisher.py | 46 + .../interfaces/threadStoppable.py | 36 + .../launch/drone_cat_mouse.launch | 99 + .../drone_cat_mouse/web-template/mouse.py | 69 + .../static/exercises/drone_gymkhana/README.md | 1 + .../drone_gymkhana/drone_gymkhana.launch | 17 + .../drone_gymkhana/drone_gymkhana.world | 144 + .../exercises/drone_gymkhana/my_solution.py | 62 + .../drone_gymkhana/web-template/RADI-launch | 2 + .../drone_gymkhana/web-template/README.md | 1 + .../web-template/code/academy.py | 8 + .../drone_gymkhana/web-template/console.py | 20 + .../web-template/drone_gymkhana.world | 158 + .../drone_gymkhana/web-template/exercise.py | 362 + .../drone_gymkhana/web-template/gui.py | 248 + .../drone_gymkhana/web-template/hal.py | 82 + .../web-template/interfaces/__init__.py | 0 .../web-template/interfaces/camera.py | 89 + .../web-template/interfaces/motors.py | 123 + .../web-template/interfaces/pose3d.py | 176 + .../interfaces/threadPublisher.py | 46 + .../interfaces/threadStoppable.py | 36 + .../web-template/launch/gazebo.launch | 24 + .../web-template/launch/launch.py | 131 + .../web-template/launch/mavros.launch | 13 + .../web-template/launch/px4.launch | 19 + .../static/exercises/drone_hangar/README.md | 1 + .../drone_hangar/drone_hangar.launch | 16 + .../exercises/drone_hangar/drone_hangar.world | 185 + .../exercises/drone_hangar/my_solution.py | 51 + .../drone_hangar/web-template/RADI-launch | 2 + .../drone_hangar/web-template/README.md | 1 + .../drone_hangar/web-template/code/academy.py | 8 + .../drone_hangar/web-template/console.py | 20 + .../web-template/drone_hangar.world | 193 + .../drone_hangar/web-template/exercise.py | 362 + .../drone_hangar/web-template/gui.py | 248 + .../drone_hangar/web-template/hal.py | 82 + .../web-template/interfaces/__init__.py | 0 .../web-template/interfaces/camera.py | 89 + .../web-template/interfaces/motors.py | 123 + .../web-template/interfaces/pose3d.py | 176 + .../interfaces/threadPublisher.py | 46 + .../interfaces/threadStoppable.py | 36 + .../web-template/launch/gazebo.launch | 24 + .../web-template/launch/launch.py | 131 + .../web-template/launch/mavros.launch | 13 + .../web-template/launch/px4.launch | 20 + .../exercises/follow_turtlebot/README.md | 1 + .../follow_turtlebot/follow_turtlebot.launch | 22 + .../follow_turtlebot/follow_turtlebot.world | 54 + .../exercises/follow_turtlebot/my_solution.py | 55 + .../follow_turtlebot/web-template/RADI-launch | 2 + .../follow_turtlebot/web-template/README.md | 1 + .../web-template/code/academy.py | 8 + .../follow_turtlebot/web-template/console.py | 18 + .../follow_turtlebot/web-template/exercise.py | 367 + .../web-template/follow_turtlebot.world | 83 + .../follow_turtlebot/web-template/gui.py | 257 + .../follow_turtlebot/web-template/hal.py | 86 + .../web-template/interfaces/__init__.py | 0 .../web-template/interfaces/camera.py | 89 + .../web-template/interfaces/motors.py | 123 + .../web-template/interfaces/pose3d.py | 176 + .../interfaces/threadPublisher.py | 46 + .../interfaces/threadStoppable.py | 36 + .../launch/follow_turtlebot.launch | 64 + .../web-template/turtlebot.py | 81 + .../exercises/labyrinth_escape/README.md | 1 + .../labyrinth_escape/arrows/down.png | Bin 0 -> 2008 bytes .../labyrinth_escape/arrows/left.png | Bin 0 -> 1986 bytes .../labyrinth_escape/arrows/right.png | Bin 0 -> 1974 bytes .../exercises/labyrinth_escape/arrows/up.png | Bin 0 -> 2026 bytes .../labyrinth_escape/labyrinth.world | 89 + .../labyrinth_escape/labyrinth_escape.launch | 16 + .../exercises/labyrinth_escape/my_solution.py | 51 + .../labyrinth_escape/web-template/RADI-launch | 2 + .../labyrinth_escape/web-template/README.md | 1 + .../web-template/code/academy.py | 8 + .../labyrinth_escape/web-template/console.py | 20 + .../labyrinth_escape/web-template/exercise.py | 362 + .../labyrinth_escape/web-template/gui.py | 248 + .../labyrinth_escape/web-template/hal.py | 82 + .../web-template/interfaces/__init__.py | 0 .../web-template/interfaces/camera.py | 89 + .../web-template/interfaces/motors.py | 123 + .../web-template/interfaces/pose3d.py | 176 + .../interfaces/threadPublisher.py | 46 + .../interfaces/threadStoppable.py | 36 + .../web-template/labyrinth_escape.world | 114 + .../web-template/launch/gazebo.launch | 24 + .../web-template/launch/launch.py | 131 + .../web-template/launch/mavros.launch | 13 + .../web-template/launch/px4.launch | 20 + .../exercises/package_delivery/README.md | 1 + .../exercises/package_delivery/magnet.py | 73 + .../exercises/package_delivery/my_solution.py | 55 + .../package_delivery/package_delivery.launch | 43 + .../package_delivery/package_delivery.world | 238 + .../package_delivery/package_delivery.yaml | 1 + .../package_delivery/web-template/README.md | 1 + .../web-template/code/academy.py | 8 + .../package_delivery/web-template/console.py | 18 + .../package_delivery/web-template/exercise.py | 357 + .../package_delivery/web-template/gui.py | 250 + .../package_delivery/web-template/hal.py | 95 + .../web-template/interfaces/__init__.py | 0 .../web-template/interfaces/camera.py | 89 + .../web-template/interfaces/motors.py | 123 + .../web-template/interfaces/pose3d.py | 176 + .../interfaces/threadPublisher.py | 46 + .../web-template/launch/gazebo.launch | 26 + .../web-template/launch/launch.py | 131 + .../web-template/launch/mavros.launch | 13 + .../web-template/launch/px4.launch | 19 + .../package_delivery/web-template/magnet.py | 73 + .../web-template/package_delivery.world | 238 + .../web-template/package_delivery.yaml | 1 + .../exercises/position_control/Beacon.py | 27 + .../exercises/position_control/README.md | 1 + .../exercises/position_control/my_solution.py | 70 + .../position_control/position_control.launch | 13 + .../position_control/position_control.world | 65 + .../position_control/web-template/Beacon.py | 27 + .../position_control/web-template/RADI-launch | 2 + .../position_control/web-template/README.md | 1 + .../web-template/code/academy.py | 8 + .../position_control/web-template/console.py | 20 + .../position_control/web-template/exercise.py | 364 + .../position_control/web-template/gui.py | 248 + .../position_control/web-template/hal.py | 98 + .../web-template/interfaces/__init__.py | 0 .../web-template/interfaces/camera.py | 89 + .../web-template/interfaces/motors.py | 123 + .../web-template/interfaces/pose3d.py | 176 + .../interfaces/threadPublisher.py | 46 + .../interfaces/threadStoppable.py | 36 + .../web-template/launch/gazebo.launch | 24 + .../web-template/launch/launch.py | 131 + .../web-template/launch/mavros.launch | 13 + .../web-template/launch/px4.launch | 18 + .../web-template/position_control.world | 90 + .../power_tower_inspection/README.md | 1 + .../power_tower_inspection/my_solution.py | 55 + .../power_tower_inspection.launch | 40 + .../power_tower_inspection.world | 353 + .../web-template/README.md | 1 + .../web-template/code/academy.py | 8 + .../web-template/console.py | 18 + .../web-template/exercise.py | 355 + .../web-template/gui.py | 250 + .../web-template/hal.py | 86 + .../web-template/interfaces/__init__.py | 0 .../web-template/interfaces/camera.py | 89 + .../web-template/interfaces/motors.py | 123 + .../web-template/interfaces/pose3d.py | 176 + .../interfaces/threadPublisher.py | 46 + .../web-template/launch/gazebo.launch | 24 + .../web-template/launch/launch.py | 131 + .../web-template/launch/mavros.launch | 13 + .../web-template/launch/px4.launch | 20 + .../web-template/power_tower_inspection.world | 352 + .../static/exercises/rescue_people/README.md | 1 + .../haarcascade_frontalface_default.xml | 33314 ++++++++++++++++ .../exercises/rescue_people/my_solution.py | 53 + .../rescue_people/rescue_people.launch | 15 + .../rescue_people/rescue_people.world | 81 + .../rescue_people/web-template/RADI-launch | 2 + .../rescue_people/web-template/README.md | 1 + .../web-template/code/academy.py | 8 + .../rescue_people/web-template/console.py | 20 + .../rescue_people/web-template/exercise.py | 361 + .../rescue_people/web-template/gui.py | 248 + .../rescue_people/web-template/hal.py | 82 + .../web-template/interfaces/__init__.py | 0 .../web-template/interfaces/camera.py | 89 + .../web-template/interfaces/motors.py | 123 + .../web-template/interfaces/pose3d.py | 176 + .../interfaces/threadPublisher.py | 46 + .../interfaces/threadStoppable.py | 36 + .../web-template/launch/gazebo.launch | 24 + .../web-template/launch/launch.py | 131 + .../web-template/launch/mavros.launch | 13 + .../web-template/launch/px4.launch | 20 + .../web-template/rescue_people.world | 83 + .../static/exercises/visual_lander/README.md | 1 + .../exercises/visual_lander/my_solution.py | 51 + .../visual_lander/visual_lander.launch | 13 + .../visual_lander/visual_lander.world | 52 + .../visual_lander/web-template/RADI-launch | 2 + .../visual_lander/web-template/README.md | 1 + .../visual_lander/web-template/car.py | 72 + .../web-template/code/academy.py | 8 + .../visual_lander/web-template/console.py | 20 + .../visual_lander/web-template/exercise.py | 363 + .../visual_lander/web-template/gui.py | 254 + .../visual_lander/web-template/hal.py | 82 + .../web-template/interfaces/__init__.py | 0 .../web-template/interfaces/camera.py | 89 + .../web-template/interfaces/motors.py | 123 + .../web-template/interfaces/pose3d.py | 176 + .../interfaces/threadPublisher.py | 46 + .../interfaces/threadStoppable.py | 36 + .../web-template/launch/gazebo.launch | 24 + .../web-template/launch/launch.py | 131 + .../web-template/launch/mavros.launch | 13 + .../web-template/launch/px4.launch | 18 + .../web-template/visual_lander.world | 63 + 233 files changed, 51770 insertions(+) create mode 100644 exercises/static/exercises/drone_cat_mouse/README.md create mode 100644 exercises/static/exercises/drone_cat_mouse/autonomous_mouse.launch create mode 100644 exercises/static/exercises/drone_cat_mouse/autonomous_mouse.py create mode 100644 exercises/static/exercises/drone_cat_mouse/drone_cat_mouse.launch create mode 100644 exercises/static/exercises/drone_cat_mouse/drone_cat_mouse.world create mode 100644 exercises/static/exercises/drone_cat_mouse/drone_cat_mouse.yaml create mode 100755 exercises/static/exercises/drone_cat_mouse/my_solution.py create mode 100644 exercises/static/exercises/drone_cat_mouse/teleoperated_mouse.py create mode 100755 exercises/static/exercises/drone_cat_mouse/web-template/RADI-launch create mode 100644 exercises/static/exercises/drone_cat_mouse/web-template/README.md create mode 100644 exercises/static/exercises/drone_cat_mouse/web-template/code/academy.py create mode 100644 exercises/static/exercises/drone_cat_mouse/web-template/console.py create mode 100644 exercises/static/exercises/drone_cat_mouse/web-template/drone_cat_mouse.world create mode 100644 exercises/static/exercises/drone_cat_mouse/web-template/drone_cat_mouse.yaml create mode 100644 exercises/static/exercises/drone_cat_mouse/web-template/exercise.py create mode 100644 exercises/static/exercises/drone_cat_mouse/web-template/exercise_guest.py create mode 100644 exercises/static/exercises/drone_cat_mouse/web-template/gui.py create mode 100644 exercises/static/exercises/drone_cat_mouse/web-template/gui_guest.py create mode 100644 exercises/static/exercises/drone_cat_mouse/web-template/hal.py create mode 100644 exercises/static/exercises/drone_cat_mouse/web-template/hal_guest.py create mode 100644 exercises/static/exercises/drone_cat_mouse/web-template/interfaces/__init__.py create mode 100644 exercises/static/exercises/drone_cat_mouse/web-template/interfaces/autonomous_mouse.py create mode 100644 exercises/static/exercises/drone_cat_mouse/web-template/interfaces/camera.py create mode 100644 exercises/static/exercises/drone_cat_mouse/web-template/interfaces/motors.py create mode 100644 exercises/static/exercises/drone_cat_mouse/web-template/interfaces/pose3d.py create mode 100644 exercises/static/exercises/drone_cat_mouse/web-template/interfaces/threadPublisher.py create mode 100644 exercises/static/exercises/drone_cat_mouse/web-template/interfaces/threadStoppable.py create mode 100644 exercises/static/exercises/drone_cat_mouse/web-template/launch/drone_cat_mouse.launch create mode 100644 exercises/static/exercises/drone_cat_mouse/web-template/mouse.py create mode 100755 exercises/static/exercises/drone_gymkhana/README.md create mode 100644 exercises/static/exercises/drone_gymkhana/drone_gymkhana.launch create mode 100644 exercises/static/exercises/drone_gymkhana/drone_gymkhana.world create mode 100755 exercises/static/exercises/drone_gymkhana/my_solution.py create mode 100755 exercises/static/exercises/drone_gymkhana/web-template/RADI-launch create mode 100644 exercises/static/exercises/drone_gymkhana/web-template/README.md create mode 100644 exercises/static/exercises/drone_gymkhana/web-template/code/academy.py create mode 100644 exercises/static/exercises/drone_gymkhana/web-template/console.py create mode 100644 exercises/static/exercises/drone_gymkhana/web-template/drone_gymkhana.world create mode 100644 exercises/static/exercises/drone_gymkhana/web-template/exercise.py create mode 100644 exercises/static/exercises/drone_gymkhana/web-template/gui.py create mode 100644 exercises/static/exercises/drone_gymkhana/web-template/hal.py create mode 100644 exercises/static/exercises/drone_gymkhana/web-template/interfaces/__init__.py create mode 100644 exercises/static/exercises/drone_gymkhana/web-template/interfaces/camera.py create mode 100644 exercises/static/exercises/drone_gymkhana/web-template/interfaces/motors.py create mode 100644 exercises/static/exercises/drone_gymkhana/web-template/interfaces/pose3d.py create mode 100644 exercises/static/exercises/drone_gymkhana/web-template/interfaces/threadPublisher.py create mode 100644 exercises/static/exercises/drone_gymkhana/web-template/interfaces/threadStoppable.py create mode 100644 exercises/static/exercises/drone_gymkhana/web-template/launch/gazebo.launch create mode 100644 exercises/static/exercises/drone_gymkhana/web-template/launch/launch.py create mode 100644 exercises/static/exercises/drone_gymkhana/web-template/launch/mavros.launch create mode 100644 exercises/static/exercises/drone_gymkhana/web-template/launch/px4.launch create mode 100644 exercises/static/exercises/drone_hangar/README.md create mode 100644 exercises/static/exercises/drone_hangar/drone_hangar.launch create mode 100644 exercises/static/exercises/drone_hangar/drone_hangar.world create mode 100755 exercises/static/exercises/drone_hangar/my_solution.py create mode 100755 exercises/static/exercises/drone_hangar/web-template/RADI-launch create mode 100644 exercises/static/exercises/drone_hangar/web-template/README.md create mode 100644 exercises/static/exercises/drone_hangar/web-template/code/academy.py create mode 100644 exercises/static/exercises/drone_hangar/web-template/console.py create mode 100644 exercises/static/exercises/drone_hangar/web-template/drone_hangar.world create mode 100644 exercises/static/exercises/drone_hangar/web-template/exercise.py create mode 100644 exercises/static/exercises/drone_hangar/web-template/gui.py create mode 100644 exercises/static/exercises/drone_hangar/web-template/hal.py create mode 100644 exercises/static/exercises/drone_hangar/web-template/interfaces/__init__.py create mode 100644 exercises/static/exercises/drone_hangar/web-template/interfaces/camera.py create mode 100644 exercises/static/exercises/drone_hangar/web-template/interfaces/motors.py create mode 100644 exercises/static/exercises/drone_hangar/web-template/interfaces/pose3d.py create mode 100644 exercises/static/exercises/drone_hangar/web-template/interfaces/threadPublisher.py create mode 100644 exercises/static/exercises/drone_hangar/web-template/interfaces/threadStoppable.py create mode 100644 exercises/static/exercises/drone_hangar/web-template/launch/gazebo.launch create mode 100644 exercises/static/exercises/drone_hangar/web-template/launch/launch.py create mode 100644 exercises/static/exercises/drone_hangar/web-template/launch/mavros.launch create mode 100644 exercises/static/exercises/drone_hangar/web-template/launch/px4.launch create mode 100644 exercises/static/exercises/follow_turtlebot/README.md create mode 100644 exercises/static/exercises/follow_turtlebot/follow_turtlebot.launch create mode 100644 exercises/static/exercises/follow_turtlebot/follow_turtlebot.world create mode 100644 exercises/static/exercises/follow_turtlebot/my_solution.py create mode 100755 exercises/static/exercises/follow_turtlebot/web-template/RADI-launch create mode 100644 exercises/static/exercises/follow_turtlebot/web-template/README.md create mode 100644 exercises/static/exercises/follow_turtlebot/web-template/code/academy.py create mode 100644 exercises/static/exercises/follow_turtlebot/web-template/console.py create mode 100644 exercises/static/exercises/follow_turtlebot/web-template/exercise.py create mode 100644 exercises/static/exercises/follow_turtlebot/web-template/follow_turtlebot.world create mode 100644 exercises/static/exercises/follow_turtlebot/web-template/gui.py create mode 100644 exercises/static/exercises/follow_turtlebot/web-template/hal.py create mode 100644 exercises/static/exercises/follow_turtlebot/web-template/interfaces/__init__.py create mode 100644 exercises/static/exercises/follow_turtlebot/web-template/interfaces/camera.py create mode 100644 exercises/static/exercises/follow_turtlebot/web-template/interfaces/motors.py create mode 100644 exercises/static/exercises/follow_turtlebot/web-template/interfaces/pose3d.py create mode 100644 exercises/static/exercises/follow_turtlebot/web-template/interfaces/threadPublisher.py create mode 100644 exercises/static/exercises/follow_turtlebot/web-template/interfaces/threadStoppable.py create mode 100644 exercises/static/exercises/follow_turtlebot/web-template/launch/follow_turtlebot.launch create mode 100644 exercises/static/exercises/follow_turtlebot/web-template/turtlebot.py create mode 100644 exercises/static/exercises/labyrinth_escape/README.md create mode 100644 exercises/static/exercises/labyrinth_escape/arrows/down.png create mode 100644 exercises/static/exercises/labyrinth_escape/arrows/left.png create mode 100644 exercises/static/exercises/labyrinth_escape/arrows/right.png create mode 100644 exercises/static/exercises/labyrinth_escape/arrows/up.png create mode 100644 exercises/static/exercises/labyrinth_escape/labyrinth.world create mode 100644 exercises/static/exercises/labyrinth_escape/labyrinth_escape.launch create mode 100755 exercises/static/exercises/labyrinth_escape/my_solution.py create mode 100755 exercises/static/exercises/labyrinth_escape/web-template/RADI-launch create mode 100644 exercises/static/exercises/labyrinth_escape/web-template/README.md create mode 100644 exercises/static/exercises/labyrinth_escape/web-template/code/academy.py create mode 100644 exercises/static/exercises/labyrinth_escape/web-template/console.py create mode 100644 exercises/static/exercises/labyrinth_escape/web-template/exercise.py create mode 100644 exercises/static/exercises/labyrinth_escape/web-template/gui.py create mode 100644 exercises/static/exercises/labyrinth_escape/web-template/hal.py create mode 100644 exercises/static/exercises/labyrinth_escape/web-template/interfaces/__init__.py create mode 100644 exercises/static/exercises/labyrinth_escape/web-template/interfaces/camera.py create mode 100644 exercises/static/exercises/labyrinth_escape/web-template/interfaces/motors.py create mode 100644 exercises/static/exercises/labyrinth_escape/web-template/interfaces/pose3d.py create mode 100644 exercises/static/exercises/labyrinth_escape/web-template/interfaces/threadPublisher.py create mode 100644 exercises/static/exercises/labyrinth_escape/web-template/interfaces/threadStoppable.py create mode 100644 exercises/static/exercises/labyrinth_escape/web-template/labyrinth_escape.world create mode 100644 exercises/static/exercises/labyrinth_escape/web-template/launch/gazebo.launch create mode 100644 exercises/static/exercises/labyrinth_escape/web-template/launch/launch.py create mode 100644 exercises/static/exercises/labyrinth_escape/web-template/launch/mavros.launch create mode 100644 exercises/static/exercises/labyrinth_escape/web-template/launch/px4.launch create mode 100755 exercises/static/exercises/package_delivery/README.md create mode 100644 exercises/static/exercises/package_delivery/magnet.py create mode 100644 exercises/static/exercises/package_delivery/my_solution.py create mode 100644 exercises/static/exercises/package_delivery/package_delivery.launch create mode 100644 exercises/static/exercises/package_delivery/package_delivery.world create mode 100644 exercises/static/exercises/package_delivery/package_delivery.yaml create mode 100644 exercises/static/exercises/package_delivery/web-template/README.md create mode 100644 exercises/static/exercises/package_delivery/web-template/code/academy.py create mode 100755 exercises/static/exercises/package_delivery/web-template/console.py create mode 100755 exercises/static/exercises/package_delivery/web-template/exercise.py create mode 100755 exercises/static/exercises/package_delivery/web-template/gui.py create mode 100644 exercises/static/exercises/package_delivery/web-template/hal.py create mode 100644 exercises/static/exercises/package_delivery/web-template/interfaces/__init__.py create mode 100644 exercises/static/exercises/package_delivery/web-template/interfaces/camera.py create mode 100644 exercises/static/exercises/package_delivery/web-template/interfaces/motors.py create mode 100644 exercises/static/exercises/package_delivery/web-template/interfaces/pose3d.py create mode 100644 exercises/static/exercises/package_delivery/web-template/interfaces/threadPublisher.py create mode 100644 exercises/static/exercises/package_delivery/web-template/launch/gazebo.launch create mode 100644 exercises/static/exercises/package_delivery/web-template/launch/launch.py create mode 100644 exercises/static/exercises/package_delivery/web-template/launch/mavros.launch create mode 100644 exercises/static/exercises/package_delivery/web-template/launch/px4.launch create mode 100644 exercises/static/exercises/package_delivery/web-template/magnet.py create mode 100644 exercises/static/exercises/package_delivery/web-template/package_delivery.world create mode 100644 exercises/static/exercises/package_delivery/web-template/package_delivery.yaml create mode 100644 exercises/static/exercises/position_control/Beacon.py create mode 100644 exercises/static/exercises/position_control/README.md create mode 100644 exercises/static/exercises/position_control/my_solution.py create mode 100644 exercises/static/exercises/position_control/position_control.launch create mode 100644 exercises/static/exercises/position_control/position_control.world create mode 100644 exercises/static/exercises/position_control/web-template/Beacon.py create mode 100755 exercises/static/exercises/position_control/web-template/RADI-launch create mode 100644 exercises/static/exercises/position_control/web-template/README.md create mode 100644 exercises/static/exercises/position_control/web-template/code/academy.py create mode 100644 exercises/static/exercises/position_control/web-template/console.py create mode 100644 exercises/static/exercises/position_control/web-template/exercise.py create mode 100644 exercises/static/exercises/position_control/web-template/gui.py create mode 100644 exercises/static/exercises/position_control/web-template/hal.py create mode 100644 exercises/static/exercises/position_control/web-template/interfaces/__init__.py create mode 100644 exercises/static/exercises/position_control/web-template/interfaces/camera.py create mode 100644 exercises/static/exercises/position_control/web-template/interfaces/motors.py create mode 100644 exercises/static/exercises/position_control/web-template/interfaces/pose3d.py create mode 100644 exercises/static/exercises/position_control/web-template/interfaces/threadPublisher.py create mode 100644 exercises/static/exercises/position_control/web-template/interfaces/threadStoppable.py create mode 100644 exercises/static/exercises/position_control/web-template/launch/gazebo.launch create mode 100644 exercises/static/exercises/position_control/web-template/launch/launch.py create mode 100644 exercises/static/exercises/position_control/web-template/launch/mavros.launch create mode 100644 exercises/static/exercises/position_control/web-template/launch/px4.launch create mode 100644 exercises/static/exercises/position_control/web-template/position_control.world create mode 100755 exercises/static/exercises/power_tower_inspection/README.md create mode 100755 exercises/static/exercises/power_tower_inspection/my_solution.py create mode 100644 exercises/static/exercises/power_tower_inspection/power_tower_inspection.launch create mode 100644 exercises/static/exercises/power_tower_inspection/power_tower_inspection.world create mode 100644 exercises/static/exercises/power_tower_inspection/web-template/README.md create mode 100644 exercises/static/exercises/power_tower_inspection/web-template/code/academy.py create mode 100755 exercises/static/exercises/power_tower_inspection/web-template/console.py create mode 100755 exercises/static/exercises/power_tower_inspection/web-template/exercise.py create mode 100755 exercises/static/exercises/power_tower_inspection/web-template/gui.py create mode 100644 exercises/static/exercises/power_tower_inspection/web-template/hal.py create mode 100644 exercises/static/exercises/power_tower_inspection/web-template/interfaces/__init__.py create mode 100644 exercises/static/exercises/power_tower_inspection/web-template/interfaces/camera.py create mode 100644 exercises/static/exercises/power_tower_inspection/web-template/interfaces/motors.py create mode 100644 exercises/static/exercises/power_tower_inspection/web-template/interfaces/pose3d.py create mode 100644 exercises/static/exercises/power_tower_inspection/web-template/interfaces/threadPublisher.py create mode 100644 exercises/static/exercises/power_tower_inspection/web-template/launch/gazebo.launch create mode 100644 exercises/static/exercises/power_tower_inspection/web-template/launch/launch.py create mode 100644 exercises/static/exercises/power_tower_inspection/web-template/launch/mavros.launch create mode 100644 exercises/static/exercises/power_tower_inspection/web-template/launch/px4.launch create mode 100644 exercises/static/exercises/power_tower_inspection/web-template/power_tower_inspection.world create mode 100644 exercises/static/exercises/rescue_people/README.md create mode 100644 exercises/static/exercises/rescue_people/haarcascade_frontalface_default.xml create mode 100644 exercises/static/exercises/rescue_people/my_solution.py create mode 100644 exercises/static/exercises/rescue_people/rescue_people.launch create mode 100644 exercises/static/exercises/rescue_people/rescue_people.world create mode 100755 exercises/static/exercises/rescue_people/web-template/RADI-launch create mode 100644 exercises/static/exercises/rescue_people/web-template/README.md create mode 100644 exercises/static/exercises/rescue_people/web-template/code/academy.py create mode 100644 exercises/static/exercises/rescue_people/web-template/console.py create mode 100644 exercises/static/exercises/rescue_people/web-template/exercise.py create mode 100644 exercises/static/exercises/rescue_people/web-template/gui.py create mode 100644 exercises/static/exercises/rescue_people/web-template/hal.py create mode 100644 exercises/static/exercises/rescue_people/web-template/interfaces/__init__.py create mode 100644 exercises/static/exercises/rescue_people/web-template/interfaces/camera.py create mode 100644 exercises/static/exercises/rescue_people/web-template/interfaces/motors.py create mode 100644 exercises/static/exercises/rescue_people/web-template/interfaces/pose3d.py create mode 100644 exercises/static/exercises/rescue_people/web-template/interfaces/threadPublisher.py create mode 100644 exercises/static/exercises/rescue_people/web-template/interfaces/threadStoppable.py create mode 100644 exercises/static/exercises/rescue_people/web-template/launch/gazebo.launch create mode 100644 exercises/static/exercises/rescue_people/web-template/launch/launch.py create mode 100644 exercises/static/exercises/rescue_people/web-template/launch/mavros.launch create mode 100644 exercises/static/exercises/rescue_people/web-template/launch/px4.launch create mode 100644 exercises/static/exercises/rescue_people/web-template/rescue_people.world create mode 100644 exercises/static/exercises/visual_lander/README.md create mode 100644 exercises/static/exercises/visual_lander/my_solution.py create mode 100644 exercises/static/exercises/visual_lander/visual_lander.launch create mode 100644 exercises/static/exercises/visual_lander/visual_lander.world create mode 100755 exercises/static/exercises/visual_lander/web-template/RADI-launch create mode 100644 exercises/static/exercises/visual_lander/web-template/README.md create mode 100644 exercises/static/exercises/visual_lander/web-template/car.py create mode 100644 exercises/static/exercises/visual_lander/web-template/code/academy.py create mode 100644 exercises/static/exercises/visual_lander/web-template/console.py create mode 100644 exercises/static/exercises/visual_lander/web-template/exercise.py create mode 100644 exercises/static/exercises/visual_lander/web-template/gui.py create mode 100644 exercises/static/exercises/visual_lander/web-template/hal.py create mode 100644 exercises/static/exercises/visual_lander/web-template/interfaces/__init__.py create mode 100644 exercises/static/exercises/visual_lander/web-template/interfaces/camera.py create mode 100644 exercises/static/exercises/visual_lander/web-template/interfaces/motors.py create mode 100644 exercises/static/exercises/visual_lander/web-template/interfaces/pose3d.py create mode 100644 exercises/static/exercises/visual_lander/web-template/interfaces/threadPublisher.py create mode 100644 exercises/static/exercises/visual_lander/web-template/interfaces/threadStoppable.py create mode 100644 exercises/static/exercises/visual_lander/web-template/launch/gazebo.launch create mode 100644 exercises/static/exercises/visual_lander/web-template/launch/launch.py create mode 100644 exercises/static/exercises/visual_lander/web-template/launch/mavros.launch create mode 100644 exercises/static/exercises/visual_lander/web-template/launch/px4.launch create mode 100644 exercises/static/exercises/visual_lander/web-template/visual_lander.world diff --git a/exercises/static/exercises/drone_cat_mouse/README.md b/exercises/static/exercises/drone_cat_mouse/README.md new file mode 100644 index 000000000..d864f56df --- /dev/null +++ b/exercises/static/exercises/drone_cat_mouse/README.md @@ -0,0 +1 @@ +[Exercise Documentation Website](https://jderobot.github.io/RoboticsAcademy/exercises/Drones/drone_cat_mouse) diff --git a/exercises/static/exercises/drone_cat_mouse/autonomous_mouse.launch b/exercises/static/exercises/drone_cat_mouse/autonomous_mouse.launch new file mode 100644 index 000000000..988e8e330 --- /dev/null +++ b/exercises/static/exercises/drone_cat_mouse/autonomous_mouse.launch @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/exercises/static/exercises/drone_cat_mouse/autonomous_mouse.py b/exercises/static/exercises/drone_cat_mouse/autonomous_mouse.py new file mode 100644 index 000000000..fc9fbbb10 --- /dev/null +++ b/exercises/static/exercises/drone_cat_mouse/autonomous_mouse.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python + +from drone_wrapper import DroneWrapper +from math import sin as s +from math import cos as c +from random import uniform as u +from time import time as t + +import sys + +a = 0 + + +def path_obfuscated(O00OO00O0OO00O0OO): + global a;O0O0000O0O00O0O00=round;O0OO0OOO00OO000O0=(lambda O00O0000000O00O00,O00OO0O0O0O000O0O:(lambda OOO00OO000O0OOO0O:((lambda OO00000O000O0OOOO:3 if O0O0000O0O00O0O00(OO00000O000O0OOOO/2)%2==0 else 0)(OOO00OO000O0OOO0O),(lambda O0OOO00000000OO0O:0)(OOO00OO000O0OOO0O),(lambda O0OO0O0OOOO0O0O0O:0)(OOO00OO000O0OOO0O),(lambda O00O0O0000O000O00:0)(OOO00OO000O0OOO0O)))(O00OO0O0O0O000O0O)if O00O0000000O00O00==0 else ((lambda O0O000O00000OOO0O:((lambda OO000O0000000OO00:1)(O0O000O00000OOO0O),(lambda OO000O0000O0OOOOO:-2 if O0O0000O0O00O0O00(OO000O0000O0OOOOO/4)%2==0 else 2)(O0O000O00000OOO0O),(lambda O000O0000000OO0O0:0)(O0O000O00000OOO0O),(lambda O00OOO000OOOOO000:0)(O0O000O00000OOO0O)))(O00OO0O0O0O000O0O)if O00O0000000O00O00==1 else ((lambda O0O0O0OOOO0O0000O:((lambda O00OOO0O000OOOO00:1)(O0O0O0OOOO0O0000O),(lambda O000O000OOOO0O000:s(O000O000OOOO0O000/2)*1)(O0O0O0OOOO0O0000O),(lambda O0OOOO00O00O00O00:c(O0OOOO00O00O00O00/2)*1)(O0O0O0OOOO0O0000O),(lambda O00000O00O0OO0O00:0)(O0O0O0OOOO0O0000O)))(O00OO0O0O0O000O0O)if O00O0000000O00O00==2 else ((lambda O0OO0000000OO0OOO:((lambda OO00OOO00O000000O:1)(O0OO0000000OO0OOO),(lambda O0O0O0000OO00OOO0:0)(O0OO0000000OO0OOO),(lambda O0O0OOOOO0OOOO000 :0)(O0OO0000000OO0OOO),(lambda O0O0OOO0000OO0OOO:u(-1,1)if O0O0000O0O00O0O00(O0O0OOO0000OO0OOO/2%2,2)<=0.05 else a)(O0OO0000000OO0OOO)))(O00OO0O0O0O000O0O)if O00O0000000O00O00==3 else None))))(O00OO00O0OO00O0OO,t());a =(O0OO0OOO00OO000O0[3]if O0OO0OOO00OO000O0 is not None else 0);return O0OO0OOO00OO000O0 + + +if __name__ == "__main__": + path = int(sys.argv[1]) + + drone = DroneWrapper() + drone.takeoff(h=5) + + while True: + v = path_obfuscated(path) + if v is not None: + vx, vy, vz, yaw = v + drone.set_cmd_vel(vx, vy, vz, yaw) + else: + print("[Mouse] Path {} not available".format(path)) + break diff --git a/exercises/static/exercises/drone_cat_mouse/drone_cat_mouse.launch b/exercises/static/exercises/drone_cat_mouse/drone_cat_mouse.launch new file mode 100644 index 000000000..b350e8807 --- /dev/null +++ b/exercises/static/exercises/drone_cat_mouse/drone_cat_mouse.launch @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/exercises/static/exercises/drone_cat_mouse/drone_cat_mouse.world b/exercises/static/exercises/drone_cat_mouse/drone_cat_mouse.world new file mode 100644 index 000000000..92044fd13 --- /dev/null +++ b/exercises/static/exercises/drone_cat_mouse/drone_cat_mouse.world @@ -0,0 +1,49 @@ + + + + + model://sun + + + + model://grass_plane + + + + logo + model://logoJdeRobot + 0 -4 0 0 0 0 + + + + + + 12 + + + + + + + 0 0 -9.8066 + + + quick + 10 + 1.3 + 0 + + + 0 + 0.2 + 100 + 0.001 + + + 0.004 + 1 + 250 + 6.0e-6 2.3e-5 -4.2e-5 + + + \ No newline at end of file diff --git a/exercises/static/exercises/drone_cat_mouse/drone_cat_mouse.yaml b/exercises/static/exercises/drone_cat_mouse/drone_cat_mouse.yaml new file mode 100644 index 000000000..9a60b540e --- /dev/null +++ b/exercises/static/exercises/drone_cat_mouse/drone_cat_mouse.yaml @@ -0,0 +1,10 @@ +cat: + cam_frontal_topic: '/iris/cam_frontal/image_raw' + cam_ventral_topic: '/iris/cam_ventral/image_raw' +mouse: + cam_frontal_topic: '/none/cam_frontal/image_raw' + cam_ventral_topic: '/none/cam_ventral/image_raw' + Xmax: 10 + Ymax: 10 + Zmax: 5 + Yawmax: 1 \ No newline at end of file diff --git a/exercises/static/exercises/drone_cat_mouse/my_solution.py b/exercises/static/exercises/drone_cat_mouse/my_solution.py new file mode 100755 index 000000000..87736399b --- /dev/null +++ b/exercises/static/exercises/drone_cat_mouse/my_solution.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python + +import rospy +import numpy as np +import cv2 +from drone_wrapper import DroneWrapper +from std_msgs.msg import Bool, Float64 +from sensor_msgs.msg import Image +from geometry_msgs.msg import Twist, Pose + +code_live_flag = False + +def gui_play_stop_cb(msg): + global code_live_flag, code_live_timer + if msg.data == True: + if not code_live_flag: + code_live_flag = True + code_live_timer = rospy.Timer(rospy.Duration(nsecs=50000000), execute) + else: + if code_live_flag: + code_live_flag = False + code_live_timer.shutdown() + + +def set_image_filtered(img): + gui_filtered_img_pub.publish(HAL.bridge.cv2_to_imgmsg(img)) + + +def set_image_threshed(img): + gui_threshed_img_pub.publish(HAL.bridge.cv2_to_imgmsg(img)) + + +def execute(event): + global HAL + img_frontal = HAL.get_frontal_image() + img_ventral = HAL.get_ventral_image() + # Both the above images are cv2 images + ################# Insert your code here ################################# + + set_image_filtered(img_frontal) + set_image_threshed(img_ventral) + + ######################################################################### + + +if __name__ == "__main__": + HAL = DroneWrapper() # Hardware Abstraction Layer aka the drone + rospy.Subscriber('gui/play_stop', Bool, gui_play_stop_cb) + gui_filtered_img_pub = rospy.Publisher('interface/filtered_img', Image, queue_size = 1) + gui_threshed_img_pub = rospy.Publisher('interface/threshed_img', Image, queue_size = 1) + code_live_flag = False + code_live_timer = rospy.Timer(rospy.Duration(nsecs=50000000), execute) + code_live_timer.shutdown() + while not rospy.is_shutdown(): + rospy.spin() diff --git a/exercises/static/exercises/drone_cat_mouse/teleoperated_mouse.py b/exercises/static/exercises/drone_cat_mouse/teleoperated_mouse.py new file mode 100644 index 000000000..a7eb95889 --- /dev/null +++ b/exercises/static/exercises/drone_cat_mouse/teleoperated_mouse.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python + +import rospy +from drone_wrapper import DroneWrapper +from std_msgs.msg import Bool +from geometry_msgs.msg import Twist +from math import sin as s +from math import cos as c +from random import uniform as u +from time import time as t + +code_live_flag = False +a = 0 + +############################## +# FOUR PATH LEVELS AVAILABLE # +############################## +PATH = 0 # 0, 1, 2, 3 + + +def gui_takeoff_cb(msg): + if msg.data: + drone.takeoff() + else: + drone.land() + + +def gui_play_stop_cb(msg): + global code_live_flag, code_live_timer + if msg.data == True: + if not code_live_flag: + code_live_flag = True + code_live_timer = rospy.Timer(rospy.Duration(nsecs=50000000), execute) + else: + if code_live_flag: + code_live_flag = False + code_live_timer.shutdown() + + +def path_obfuscated(b): + global a;r=round;d=(lambda x,y:(lambda x:((lambda x:3 if r(x/2)%2==0 else 0)(x),(lambda x:0)(x),(lambda x:0)(x),(lambda x:0)(x)))(y)if x==0 else((lambda x:((lambda x:1)(x),(lambda x:-2 if r(x/4)%2==0 else 2)(x),(lambda x:0)(x),(lambda x:0)(x)))(y)if x==1 else((lambda x:((lambda x:1)(x),(lambda x:s(x/2)*1)(x),(lambda x:c(x/2)*1)(x),(lambda x:0)(x)))(y)if x==2 else((lambda x:((lambda x:1)(x),(lambda x:0)(x),(lambda x:0)(x),(lambda x:u(-1,1) if r(x/2%2,2)<=0.05 else a)(x)))(y)if x==3 else None))))(b,t());a=(d[3]if d is not None else 0);return d + + +def execute(event): + global drone + + v = path_obfuscated(PATH) + if v is not None: + vx, vy, vz, yaw = v + drone.set_cmd_vel(vx, vy, vz, yaw) + else: + print("[Mouse] Path {} not available".format(PATH)) + + +def gui_twist_cb(msg): + global drone + drone.set_cmd_vel(msg.linear.x, msg.linear.y, msg.linear.z, msg.angular.z) + + +if __name__ == "__main__": + drone = DroneWrapper() + rospy.Subscriber('gui/takeoff_land', Bool, gui_takeoff_cb) + rospy.Subscriber('gui/play_stop', Bool, gui_play_stop_cb) + rospy.Subscriber('gui/twist', Twist, gui_twist_cb) + + code_live_flag = False + code_live_timer = rospy.Timer(rospy.Duration(nsecs=50000000), execute) + code_live_timer.shutdown() + while not rospy.is_shutdown(): + rospy.spin() \ No newline at end of file diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/RADI-launch b/exercises/static/exercises/drone_cat_mouse/web-template/RADI-launch new file mode 100755 index 000000000..afde9c081 --- /dev/null +++ b/exercises/static/exercises/drone_cat_mouse/web-template/RADI-launch @@ -0,0 +1,2 @@ +#!/bin/sh +docker run -it --rm -p 8000:8000 -p 2303:2303 -p 1905:1905 -p 8765:8765 -p 6080:6080 -p 1108:1108 jderobot/robotics-academy:3.1.2 ./start-3.1.sh diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/README.md b/exercises/static/exercises/drone_cat_mouse/web-template/README.md new file mode 100644 index 000000000..d864f56df --- /dev/null +++ b/exercises/static/exercises/drone_cat_mouse/web-template/README.md @@ -0,0 +1 @@ +[Exercise Documentation Website](https://jderobot.github.io/RoboticsAcademy/exercises/Drones/drone_cat_mouse) diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/code/academy.py b/exercises/static/exercises/drone_cat_mouse/web-template/code/academy.py new file mode 100644 index 000000000..7a59ce7f5 --- /dev/null +++ b/exercises/static/exercises/drone_cat_mouse/web-template/code/academy.py @@ -0,0 +1,8 @@ +# Enter sequential code! +from GUI import GUI +from HAL import HAL + +while True: + # Enter iterative code! + img = HAL.get_ventral_image() + GUI.showImage(img) \ No newline at end of file diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/console.py b/exercises/static/exercises/drone_cat_mouse/web-template/console.py new file mode 100644 index 000000000..23d0efad3 --- /dev/null +++ b/exercises/static/exercises/drone_cat_mouse/web-template/console.py @@ -0,0 +1,18 @@ +# Functions to start and close console +import os +import sys + +def start_console(): + # Get all the file descriptors and choose the latest one + fds = os.listdir("/dev/pts/") + fds.sort() + console_fd = fds[-2] + + sys.stderr = open('/dev/pts/' + console_fd, 'w') + sys.stdout = open('/dev/pts/' + console_fd, 'w') + sys.stdin = open('/dev/pts/' + console_fd, 'w') + +def close_console(): + sys.stderr.close() + sys.stdout.close() + sys.stdin.close() \ No newline at end of file diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/drone_cat_mouse.world b/exercises/static/exercises/drone_cat_mouse/web-template/drone_cat_mouse.world new file mode 100644 index 000000000..78a8e0612 --- /dev/null +++ b/exercises/static/exercises/drone_cat_mouse/web-template/drone_cat_mouse.world @@ -0,0 +1,81 @@ + + + + + + + + + model://sun + + + + + model://grass_plane + + + + + logo + model://logoJdeRobot + 0 -4 0 0 0 0 + + + + + 0 + 0.4 0.4 0.4 1.0 + 0.7 0.7 0.7 1 + + + 12 + + + + + + + + + + + + EARTH_WGS84 + 47.3667 + 8.5500 + 500.0 + 0 + + + + + + + quick + 1000 + 1.3 + + + 0 + 0.2 + 100 + 0.001 + + + 0.004 + 1 + 250 + 6.0e-06 2.3e-05 -4.2e-05 + 0 0 -9.8 + + + + + false + + + + + + diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/drone_cat_mouse.yaml b/exercises/static/exercises/drone_cat_mouse/web-template/drone_cat_mouse.yaml new file mode 100644 index 000000000..9a60b540e --- /dev/null +++ b/exercises/static/exercises/drone_cat_mouse/web-template/drone_cat_mouse.yaml @@ -0,0 +1,10 @@ +cat: + cam_frontal_topic: '/iris/cam_frontal/image_raw' + cam_ventral_topic: '/iris/cam_ventral/image_raw' +mouse: + cam_frontal_topic: '/none/cam_frontal/image_raw' + cam_ventral_topic: '/none/cam_ventral/image_raw' + Xmax: 10 + Ymax: 10 + Zmax: 5 + Yawmax: 1 \ No newline at end of file diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/exercise.py b/exercises/static/exercises/drone_cat_mouse/web-template/exercise.py new file mode 100644 index 000000000..c5cb647da --- /dev/null +++ b/exercises/static/exercises/drone_cat_mouse/web-template/exercise.py @@ -0,0 +1,362 @@ +#!/usr/bin/env python + +from __future__ import print_function + +from websocket_server import WebsocketServer +import time +import threading +import subprocess +import sys +from datetime import datetime +import re +import json +import importlib + +import rospy +from std_srvs.srv import Empty + +from gui import GUI, ThreadGUI +from hal import HAL +from console import start_console, close_console + + +class Template: + # Initialize class variables + # self.ideal_cycle to run an execution for at least 1 second + # self.process for the current running process + def __init__(self): + self.measure_thread = None + self.thread = None + self.reload = False + self.stop_brain = True + self.user_code = "" + + # Time variables + self.ideal_cycle = 80 + self.measured_cycle = 80 + self.iteration_counter = 0 + self.real_time_factor = 0 + self.frequency_message = {'brain': '', 'gui': '', 'rtf': ''} + + self.server = None + self.client = None + self.host = sys.argv[1] + + # Initialize the GUI, HAL and Console behind the scenes + self.hal = HAL() + self.gui = GUI(self.host, self.hal) + + # Function to parse the code + # A few assumptions: + # 1. The user always passes sequential and iterative codes + # 2. Only a single infinite loop + def parse_code(self, source_code): + sequential_code, iterative_code = self.seperate_seq_iter(source_code) + return iterative_code, sequential_code + + # Function to separate the iterative and sequential code + def seperate_seq_iter(self, source_code): + if source_code == "": + return "", "" + + # Search for an instance of while True + infinite_loop = re.search(r'[^ ]while\s*\(\s*True\s*\)\s*:|[^ ]while\s*True\s*:|[^ ]while\s*1\s*:|[^ ]while\s*\(\s*1\s*\)\s*:', source_code) + + # Separate the content inside while True and the other + # (Separating the sequential and iterative part!) + try: + start_index = infinite_loop.start() + iterative_code = source_code[start_index:] + sequential_code = source_code[:start_index] + + # Remove while True: syntax from the code + # And remove the the 4 spaces indentation before each command + iterative_code = re.sub(r'[^ ]while\s*\(\s*True\s*\)\s*:|[^ ]while\s*True\s*:|[^ ]while\s*1\s*:|[^ ]while\s*\(\s*1\s*\)\s*:', '', iterative_code) + # Add newlines to match line on bug report + extra_lines = sequential_code.count('\n') + while (extra_lines >= 0): + iterative_code = '\n' + iterative_code + extra_lines -= 1 + iterative_code = re.sub(r'^[ ]{4}', '', iterative_code, flags=re.M) + + except: + sequential_code = source_code + iterative_code = "" + + return sequential_code, iterative_code + + # The process function + def process_code(self, source_code): + # Redirect the information to console + start_console() + + iterative_code, sequential_code = self.parse_code(source_code) + + # print(sequential_code) + # print(iterative_code) + + # The Python exec function + # Run the sequential part + gui_module, hal_module = self.generate_modules() + reference_environment = {"GUI": gui_module, "HAL": hal_module} + while (self.stop_brain == True): + if (self.reload == True): + return + time.sleep(0.1) + exec(sequential_code, reference_environment) + + # Run the iterative part inside template + # and keep the check for flag + while self.reload == False: + while (self.stop_brain == True): + if (self.reload == True): + return + time.sleep(0.1) + + start_time = datetime.now() + + # Execute the iterative portion + exec(iterative_code, reference_environment) + + # Template specifics to run! + finish_time = datetime.now() + dt = finish_time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + + # Keep updating the iteration counter + if (iterative_code == ""): + self.iteration_counter = 0 + else: + self.iteration_counter = self.iteration_counter + 1 + + # The code should be run for atleast the target time step + # If it's less put to sleep + if (ms < self.ideal_cycle): + time.sleep((self.ideal_cycle - ms) / 1000.0) + + close_console() + print("Current Thread Joined!") + + # Function to generate the modules for use in ACE Editor + def generate_modules(self): + # Define HAL module + hal_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) + hal_module.HAL = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) + # hal_module.drone = imp.new_module("drone") + # motors# hal_module.HAL.motors = imp.new_module("motors") + + # Add HAL functions + hal_module.HAL.get_frontal_image = self.hal.get_frontal_image + hal_module.HAL.get_ventral_image = self.hal.get_ventral_image + hal_module.HAL.get_position = self.hal.get_position + hal_module.HAL.get_velocity = self.hal.get_velocity + hal_module.HAL.get_yaw_rate = self.hal.get_yaw_rate + hal_module.HAL.get_orientation = self.hal.get_orientation + hal_module.HAL.get_roll = self.hal.get_roll + hal_module.HAL.get_pitch = self.hal.get_pitch + hal_module.HAL.get_yaw = self.hal.get_yaw + hal_module.HAL.get_landed_state = self.hal.get_landed_state + hal_module.HAL.set_cmd_pos = self.hal.set_cmd_pos + hal_module.HAL.set_cmd_vel = self.hal.set_cmd_vel + hal_module.HAL.set_cmd_mix = self.hal.set_cmd_mix + hal_module.HAL.takeoff = self.hal.takeoff + hal_module.HAL.land = self.hal.land + + # Define GUI module + gui_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) + gui_module.GUI = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) + + # Add GUI functions + gui_module.GUI.showImage = self.gui.showImage + gui_module.GUI.showLeftImage = self.gui.showLeftImage + + # Adding modules to system + # Protip: The names should be different from + # other modules, otherwise some errors + sys.modules["HAL"] = hal_module + sys.modules["GUI"] = gui_module + + return gui_module, hal_module + + # Function to measure the frequency of iterations + def measure_frequency(self): + previous_time = datetime.now() + # An infinite loop + while True: + # Sleep for 2 seconds + time.sleep(2) + + # Measure the current time and subtract from the previous time to get real time interval + current_time = datetime.now() + dt = current_time - previous_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + previous_time = current_time + + # Get the time period + try: + # Division by zero + self.measured_cycle = ms / self.iteration_counter + except: + self.measured_cycle = 0 + + # Reset the counter + self.iteration_counter = 0 + + # Send to client + self.send_frequency_message() + + # Function to generate and send frequency messages + def send_frequency_message(self): + # This function generates and sends frequency measures of the brain and gui + brain_frequency = 0; gui_frequency = 0 + try: + brain_frequency = round(1000 / self.measured_cycle, 1) + except ZeroDivisionError: + brain_frequency = 0 + + try: + gui_frequency = round(1000 / self.thread_gui.measured_cycle, 1) + except ZeroDivisionError: + gui_frequency = 0 + + self.frequency_message["brain"] = brain_frequency + self.frequency_message["gui"] = gui_frequency + self.frequency_message["rtf"] = self.real_time_factor + + message = "#freq" + json.dumps(self.frequency_message) + self.server.send_message(self.client, message) + + def send_ping_message(self): + self.server.send_message(self.client, "#ping") + + # Function to notify the front end that the code was received and sent to execution + def send_code_message(self): + self.server.send_message(self.client, "#exec") + + # Function to track the real time factor from Gazebo statistics + # https://stackoverflow.com/a/17698359 + # (For reference, Python3 solution specified in the same answer) + def track_stats(self): + args = ["gz", "stats", "-p"] + # Prints gz statistics. "-p": Output comma-separated values containing- + # real-time factor (percent), simtime (sec), realtime (sec), paused (T or F) + stats_process = subprocess.Popen(args, stdout=subprocess.PIPE, universal_newlines=True) + # bufsize=1 enables line-bufferred mode (the input buffer is flushed + # automatically on newlines if you would write to process.stdin ) + with stats_process.stdout: + for line in iter(stats_process.stdout.readline, ''): + stats_list = [x.strip() for x in line.split(',')] + self.real_time_factor = stats_list[0] + + # Function to maintain thread execution + def execute_thread(self, source_code): + # Keep checking until the thread is alive + # The thread will die when the coming iteration reads the flag + if self.thread is not None: + while self.thread.is_alive(): + time.sleep(0.2) + + # Turn the flag down, the iteration has successfully stopped! + self.reload = False + # New thread execution + self.thread = threading.Thread(target=self.process_code, args=[source_code]) + self.thread.start() + self.send_code_message() + print("New Thread Started!") + + # Function to read and set frequency from incoming message + def read_frequency_message(self, message): + frequency_message = json.loads(message) + + # Set brain frequency + frequency = float(frequency_message["brain"]) + self.ideal_cycle = 1000.0 / frequency + + # Set gui frequency + frequency = float(frequency_message["gui"]) + self.thread_gui.ideal_cycle = 1000.0 / frequency + + return + + # The websocket function + # Gets called when there is an incoming message from the client + def handle(self, client, server, message): + if message[:5] == "#freq": + frequency_message = message[5:] + self.read_frequency_message(frequency_message) + time.sleep(1) + return + + elif(message[:5] == "#ping"): + time.sleep(1) + self.send_ping_message() + return + + elif (message[:5] == "#code"): + try: + # Once received turn the reload flag up and send it to execute_thread function + self.user_code = message[6:] + # print(repr(code)) + self.reload = True + self.execute_thread(self.user_code) + except: + pass + + elif (message[:5] == "#rest"): + try: + self.reload = True + self.stop_brain = True + self.execute_thread(self.user_code) + except: + pass + + elif (message[:5] == "#stop"): + self.stop_brain = True + + elif (message[:5] == "#play"): + self.stop_brain = False + + # Function that gets called when the server is connected + def connected(self, client, server): + self.client = client + # Start the GUI update thread + self.thread_gui = ThreadGUI(self.gui) + self.thread_gui.start() + + # Start the real time factor tracker thread + self.stats_thread = threading.Thread(target=self.track_stats) + self.stats_thread.start() + + # Start measure frequency + self.measure_thread = threading.Thread(target=self.measure_frequency) + self.measure_thread.start() + + print(client, 'connected') + + # Function that gets called when the connected closes + def handle_close(self, client, server): + print(client, 'closed') + + def run_server(self): + self.server = WebsocketServer(port=1905, host=self.host) + self.server.set_fn_new_client(self.connected) + self.server.set_fn_client_left(self.handle_close) + self.server.set_fn_message_received(self.handle) + + logged = False + while not logged: + try: + f = open("/ws_code.log", "w") + f.write("websocket_code=ready") + f.close() + logged = True + except: + time.sleep(0.1) + + self.server.run_forever() + + +# Execute! +if __name__ == "__main__": + server = Template() + server.run_server() \ No newline at end of file diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/exercise_guest.py b/exercises/static/exercises/drone_cat_mouse/web-template/exercise_guest.py new file mode 100644 index 000000000..3ccaf1926 --- /dev/null +++ b/exercises/static/exercises/drone_cat_mouse/web-template/exercise_guest.py @@ -0,0 +1,318 @@ +#!/usr/bin/env python + +from __future__ import print_function + +from websocket_server import WebsocketServer +import time +import threading +import subprocess +import sys +from datetime import datetime +import re +import json +import importlib + +import rospy +from std_srvs.srv import Empty + +from gui_guest import GUI, ThreadGUI +from hal_guest import HAL +from console import start_console, close_console + + +class Template: + # Initialize class variables + # self.ideal_cycle to run an execution for at least 1 second + # self.process for the current running process + def __init__(self): + self.measure_thread = None + self.thread = None + self.reload = False + + # Time variables + self.ideal_cycle = 80 + self.measured_cycle = 80 + self.iteration_counter = 0 + self.real_time_factor = 0 + self.frequency_message = {'brain': '', 'gui': '', 'rtf': ''} + + self.server = None + self.client = None + self.host = sys.argv[1] + + # Initialize the GUI, HAL and Console behind the scenes + self.hal = HAL() + self.gui = GUI(self.host, self.hal) + + # Function to parse the code + # A few assumptions: + # 1. The user always passes sequential and iterative codes + # 2. Only a single infinite loop + def parse_code(self, source_code): + sequential_code, iterative_code = self.seperate_seq_iter(source_code) + return iterative_code, sequential_code + + # Function to separate the iterative and sequential code + def seperate_seq_iter(self, source_code): + if source_code == "": + return "", "" + + # Search for an instance of while True + infinite_loop = re.search(r'[^ ]while\s*\(\s*True\s*\)\s*:|[^ ]while\s*True\s*:|[^ ]while\s*1\s*:|[^ ]while\s*\(\s*1\s*\)\s*:', source_code) + + # Separate the content inside while True and the other + # (Separating the sequential and iterative part!) + try: + start_index = infinite_loop.start() + iterative_code = source_code[start_index:] + sequential_code = source_code[:start_index] + + # Remove while True: syntax from the code + # And remove the the 4 spaces indentation before each command + iterative_code = re.sub(r'[^ ]while\s*\(\s*True\s*\)\s*:|[^ ]while\s*True\s*:|[^ ]while\s*1\s*:|[^ ]while\s*\(\s*1\s*\)\s*:', '', iterative_code) + iterative_code = re.sub(r'^[ ]{4}', '', iterative_code, flags=re.M) + + except: + sequential_code = source_code + iterative_code = "" + + return sequential_code, iterative_code + + # The process function + def process_code(self, source_code): + # Redirect the information to console + start_console() + + iterative_code, sequential_code = self.parse_code(source_code) + + # print(sequential_code) + # print(iterative_code) + + # The Python exec function + # Run the sequential part + gui_module, hal_module = self.generate_modules() + reference_environment = {"GUI": gui_module, "HAL": hal_module} + exec(sequential_code, reference_environment) + + # Run the iterative part inside template + # and keep the check for flag + while self.reload == False: + start_time = datetime.now() + + # Execute the iterative portion + exec(iterative_code, reference_environment) + + # Template specifics to run! + finish_time = datetime.now() + dt = finish_time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + + # Keep updating the iteration counter + if (iterative_code == ""): + self.iteration_counter = 0 + else: + self.iteration_counter = self.iteration_counter + 1 + + # The code should be run for atleast the target time step + # If it's less put to sleep + if (ms < self.ideal_cycle): + time.sleep((self.ideal_cycle - ms) / 1000.0) + + close_console() + print("Current Thread Joined!") + + # Function to generate the modules for use in ACE Editor + def generate_modules(self): + # Define HAL module + hal_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) + hal_module.HAL = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) + # hal_module.drone = imp.new_module("drone") + # motors# hal_module.HAL.motors = imp.new_module("motors") + + # Add HAL functions + hal_module.HAL.get_frontal_image = self.hal.get_frontal_image + hal_module.HAL.get_ventral_image = self.hal.get_ventral_image + hal_module.HAL.get_position = self.hal.get_position + hal_module.HAL.get_velocity = self.hal.get_velocity + hal_module.HAL.get_yaw_rate = self.hal.get_yaw_rate + hal_module.HAL.get_orientation = self.hal.get_orientation + hal_module.HAL.get_roll = self.hal.get_roll + hal_module.HAL.get_pitch = self.hal.get_pitch + hal_module.HAL.get_yaw = self.hal.get_yaw + hal_module.HAL.get_landed_state = self.hal.get_landed_state + hal_module.HAL.set_cmd_pos = self.hal.set_cmd_pos + hal_module.HAL.set_cmd_vel = self.hal.set_cmd_vel + hal_module.HAL.set_cmd_mix = self.hal.set_cmd_mix + hal_module.HAL.takeoff = self.hal.takeoff + hal_module.HAL.land = self.hal.land + + # Define GUI module + gui_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) + gui_module.GUI = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) + + # Add GUI functions + gui_module.GUI.showImage = self.gui.showImage + gui_module.GUI.showLeftImage = self.gui.showLeftImage + + # Adding modules to system + # Protip: The names should be different from + # other modules, otherwise some errors + sys.modules["HAL"] = hal_module + sys.modules["GUI"] = gui_module + + return gui_module, hal_module + + # Function to measure the frequency of iterations + def measure_frequency(self): + previous_time = datetime.now() + # An infinite loop + while True: + # Sleep for 2 seconds + time.sleep(2) + + # Measure the current time and subtract from the previous time to get real time interval + current_time = datetime.now() + dt = current_time - previous_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + previous_time = current_time + + # Get the time period + try: + # Division by zero + self.measured_cycle = ms / self.iteration_counter + except: + self.measured_cycle = 0 + + # Reset the counter + self.iteration_counter = 0 + + # Send to client + self.send_frequency_message() + + # Function to generate and send frequency messages + def send_frequency_message(self): + # This function generates and sends frequency measures of the brain and gui + brain_frequency = 0; gui_frequency = 0 + try: + brain_frequency = round(1000 / self.measured_cycle, 1) + except ZeroDivisionError: + brain_frequency = 0 + + try: + gui_frequency = round(1000 / self.thread_gui.measured_cycle, 1) + except ZeroDivisionError: + gui_frequency = 0 + + self.frequency_message["brain"] = brain_frequency + self.frequency_message["gui"] = gui_frequency + self.frequency_message["rtf"] = self.real_time_factor + + message = "#freq" + json.dumps(self.frequency_message) + self.server.send_message(self.client, message) + + # Function to track the real time factor from Gazebo statistics + # https://stackoverflow.com/a/17698359 + # (For reference, Python3 solution specified in the same answer) + def track_stats(self): + args = ["gz", "stats", "-p"] + # Prints gz statistics. "-p": Output comma-separated values containing- + # real-time factor (percent), simtime (sec), realtime (sec), paused (T or F) + stats_process = subprocess.Popen(args, stdout=subprocess.PIPE, bufsize=1, universal_newlines=True) + # bufsize=1 enables line-bufferred mode (the input buffer is flushed + # automatically on newlines if you would write to process.stdin ) + with stats_process.stdout: + for line in iter(stats_process.stdout.readline, ''): + stats_list = [x.strip() for x in line.split(',')] + self.real_time_factor = stats_list[0] + + # Function to maintain thread execution + def execute_thread(self, source_code): + # Keep checking until the thread is alive + # The thread will die when the coming iteration reads the flag + if self.thread is not None: + while self.thread.is_alive(): + time.sleep(0.2) + + # Turn the flag down, the iteration has successfully stopped! + self.reload = False + # New thread execution + self.thread = threading.Thread(target=self.process_code, args=[source_code]) + self.thread.start() + print("New Thread Started!") + + # Function to read and set frequency from incoming message + def read_frequency_message(self, message): + frequency_message = json.loads(message) + + # Set brain frequency + frequency = float(frequency_message["brain"]) + self.ideal_cycle = 1000.0 / frequency + + # Set gui frequency + frequency = float(frequency_message["gui"]) + self.thread_gui.ideal_cycle = 1000.0 / frequency + + return + + # The websocket function + # Gets called when there is an incoming message from the client + def handle(self, client, server, message): + if message[:5] == "#freq": + frequency_message = message[5:] + self.read_frequency_message(frequency_message) + time.sleep(1) + return + + try: + # Once received turn the reload flag up and send it to execute_thread function + code = message + # print(repr(code)) + self.reload = True + self.execute_thread(code) + except: + pass + + # Function that gets called when the server is connected + def connected(self, client, server): + self.client = client + # Start the GUI update thread + self.thread_gui = ThreadGUI(self.gui) + self.thread_gui.start() + + # Start the real time factor tracker thread + self.stats_thread = threading.Thread(target=self.track_stats) + self.stats_thread.start() + + # Start measure frequency + self.measure_thread = threading.Thread(target=self.measure_frequency) + self.measure_thread.start() + + print(client, 'connected') + + # Function that gets called when the connected closes + def handle_close(self, client, server): + print(client, 'closed') + + def run_server(self): + self.server = WebsocketServer(port=1904, host=self.host) + self.server.set_fn_new_client(self.connected) + self.server.set_fn_client_left(self.handle_close) + self.server.set_fn_message_received(self.handle) + + logged = False + while not logged: + try: + f = open("/ws_code_guest.log", "w") + f.write("websocket_code_guest=ready") + f.close() + logged = True + except: + time.sleep(0.1) + + self.server.run_forever() + + +# Execute! +if __name__ == "__main__": + server = Template() + server.run_server() \ No newline at end of file diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/gui.py b/exercises/static/exercises/drone_cat_mouse/web-template/gui.py new file mode 100644 index 000000000..7ab9f1055 --- /dev/null +++ b/exercises/static/exercises/drone_cat_mouse/web-template/gui.py @@ -0,0 +1,277 @@ +import json +import cv2 +import base64 +import threading +import time +from datetime import datetime +from websocket_server import WebsocketServer +import logging +import numpy as np +from hal import HAL + +# Graphical User Interface Class +class GUI: + # Initialization function + # The actual initialization + def __init__(self, host, hal): + t = threading.Thread(target=self.run_server) + + self.payload = {'image': ''} + self.left_payload = {'image': ''} + self.dist = {'dist': '', 'ready': ''} + self.server = None + self.client = None + + self.host = host + + # Image variables + self.image_to_be_shown = None + self.image_to_be_shown_updated = False + self.image_show_lock = threading.Lock() + + self.left_image_to_be_shown = None + self.left_image_to_be_shown_updated = False + self.left_image_show_lock = threading.Lock() + + self.acknowledge = False + self.acknowledge_lock = threading.Lock() + + # Take the console object to set the same websocket and client + self.hal = hal + t.start() + + # Explicit initialization function + # Class method, so user can call it without instantiation + @classmethod + def initGUI(cls, host): + # self.payload = {'image': '', 'shape': []} + new_instance = cls(host) + return new_instance + + # Function to prepare image payload + # Encodes the image as a JSON string and sends through the WS + def payloadImage(self): + self.image_show_lock.acquire() + image_to_be_shown_updated = self.image_to_be_shown_updated + image_to_be_shown = self.image_to_be_shown + self.image_show_lock.release() + + image = image_to_be_shown + payload = {'image': '', 'shape': ''} + + if not image_to_be_shown_updated: + return payload + + shape = image.shape + frame = cv2.imencode('.JPEG', image)[1] + encoded_image = base64.b64encode(frame) + + payload['image'] = encoded_image.decode('utf-8') + payload['shape'] = shape + + self.image_show_lock.acquire() + self.image_to_be_shown_updated = False + self.image_show_lock.release() + + return payload + + # Function to prepare image payload + # Encodes the image as a JSON string and sends through the WS + def payloadLeftImage(self): + self.left_image_show_lock.acquire() + left_image_to_be_shown_updated = self.left_image_to_be_shown_updated + left_image_to_be_shown = self.left_image_to_be_shown + self.left_image_show_lock.release() + + image = left_image_to_be_shown + payload = {'image': '', 'shape': ''} + + if not left_image_to_be_shown_updated: + return payload + + shape = image.shape + frame = cv2.imencode('.JPEG', image)[1] + encoded_image = base64.b64encode(frame) + + payload['image'] = encoded_image.decode('utf-8') + payload['shape'] = shape + + self.left_image_show_lock.acquire() + self.left_image_to_be_shown_updated = False + self.left_image_show_lock.release() + + return payload + + # Function for student to call + def showImage(self, image): + self.image_show_lock.acquire() + self.image_to_be_shown = image + self.image_to_be_shown_updated = True + self.image_show_lock.release() + + # Function for student to call + def showLeftImage(self, image): + self.left_image_show_lock.acquire() + self.left_image_to_be_shown = image + self.left_image_to_be_shown_updated = True + self.left_image_show_lock.release() + + # Function to get the client + # Called when a new client is received + def get_client(self, client, server): + self.client = client + + # Function to get value of Acknowledge + def get_acknowledge(self): + self.acknowledge_lock.acquire() + acknowledge = self.acknowledge + self.acknowledge_lock.release() + + return acknowledge + + # Function to get value of Acknowledge + def set_acknowledge(self, value): + self.acknowledge_lock.acquire() + self.acknowledge = value + self.acknowledge_lock.release() + + # Update the gui + def update_gui(self): + # Payload Image Message + payload = self.payloadImage() + self.payload["image"] = json.dumps(payload) + # Add position to payload + cat_x, cat_y, cat_z = self.hal.get_position() + self.payload["pos"] = [cat_x, cat_y, cat_z] + + message = "#gui" + json.dumps(self.payload) + self.server.send_message(self.client, message) + + # Payload Left Image Message + left_payload = self.payloadLeftImage() + self.left_payload["image"] = json.dumps(left_payload) + + message = "#gul" + json.dumps(self.left_payload) + self.server.send_message(self.client, message) + + # Update distance between cat and mouse drones + def update_dist(self): + cat_x, cat_y, cat_z = self.hal.get_position() + mouse_x, mouse_y, mouse_z = self.mouse.get_position() + + if mouse_z > 0.1: self.dist["ready"] = "true" + else: self.dist["ready"] = "false" + + dist = np.sqrt((mouse_x+2-cat_x)**2 + (mouse_y-cat_y)**2 + (mouse_z-cat_z)**2) + dist = int(dist*100)/100 + self.dist["dist"] = dist + + message = '#dst' + json.dumps(self.dist) + self.server.send_message(self.client, message) + + # Function to read the message from websocket + # Gets called when there is an incoming message from the client + def get_message(self, client, server, message): + # Acknowledge Message for GUI Thread + if message[:4] == "#ack": + self.set_acknowledge(True) + # elif message[:4] == "#mou": + # self.mouse.start_mouse(int(message[4:5])) + # elif message[:4] == "#stp": + # self.mouse.stop_mouse() + # elif message[:4] == "#rst": + # self.mouse.reset_mouse() + + # Activate the server + def run_server(self): + self.server = WebsocketServer(port=2303, host=self.host) + self.server.set_fn_new_client(self.get_client) + self.server.set_fn_message_received(self.get_message) + + logged = False + while not logged: + try: + f = open("/ws_gui.log", "w") + f.write("websocket_gui=ready") + f.close() + logged = True + except: + time.sleep(0.1) + + self.server.run_forever() + + # Function to reset + def reset_gui(self): + pass + + +# This class decouples the user thread +# and the GUI update thread +class ThreadGUI: + def __init__(self, gui): + self.gui = gui + + # Time variables + self.ideal_cycle = 80 + self.measured_cycle = 80 + self.iteration_counter = 0 + + # Function to start the execution of threads + def start(self): + self.measure_thread = threading.Thread(target=self.measure_thread) + self.thread = threading.Thread(target=self.run) + + self.measure_thread.start() + self.thread.start() + + print("GUI Thread Started!") + + # The measuring thread to measure frequency + def measure_thread(self): + while self.gui.client is None: + pass + + previous_time = datetime.now() + while True: + # Sleep for 2 seconds + time.sleep(2) + + # Measure the current time and subtract from previous time to get real time interval + current_time = datetime.now() + dt = current_time - previous_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + previous_time = current_time + + # Get the time period + try: + # Division by zero + self.measured_cycle = ms / self.iteration_counter + except: + self.measured_cycle = 0 + + # Reset the counter + self.iteration_counter = 0 + + # The main thread of execution + def run(self): + while self.gui.client is None: + pass + + while True: + start_time = datetime.now() + self.gui.update_gui() + #self.gui.update_dist() + acknowledge_message = self.gui.get_acknowledge() + + while not acknowledge_message: + acknowledge_message = self.gui.get_acknowledge() + + self.gui.set_acknowledge(False) + + finish_time = datetime.now() + self.iteration_counter = self.iteration_counter + 1 + + dt = finish_time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + if ms < self.ideal_cycle: + time.sleep((self.ideal_cycle-ms) / 1000.0) \ No newline at end of file diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/gui_guest.py b/exercises/static/exercises/drone_cat_mouse/web-template/gui_guest.py new file mode 100644 index 000000000..7aac25b87 --- /dev/null +++ b/exercises/static/exercises/drone_cat_mouse/web-template/gui_guest.py @@ -0,0 +1,277 @@ +import json +import cv2 +import base64 +import threading +import time +from datetime import datetime +from websocket_server import WebsocketServer +import logging +import numpy as np +from hal_guest import HAL + +# Graphical User Interface Class +class GUI: + # Initialization function + # The actual initialization + def __init__(self, host, hal): + t = threading.Thread(target=self.run_server) + + self.payload = {'image': ''} + self.left_payload = {'image': ''} + self.dist = {'dist': '', 'ready': ''} + self.server = None + self.client = None + + self.host = host + + # Image variables + self.image_to_be_shown = None + self.image_to_be_shown_updated = False + self.image_show_lock = threading.Lock() + + self.left_image_to_be_shown = None + self.left_image_to_be_shown_updated = False + self.left_image_show_lock = threading.Lock() + + self.acknowledge = False + self.acknowledge_lock = threading.Lock() + + # Take the console object to set the same websocket and client + self.hal = hal + t.start() + + # Explicit initialization function + # Class method, so user can call it without instantiation + @classmethod + def initGUI(cls, host): + # self.payload = {'image': '', 'shape': []} + new_instance = cls(host) + return new_instance + + # Function to prepare image payload + # Encodes the image as a JSON string and sends through the WS + def payloadImage(self): + self.image_show_lock.acquire() + image_to_be_shown_updated = self.image_to_be_shown_updated + image_to_be_shown = self.image_to_be_shown + self.image_show_lock.release() + + image = image_to_be_shown + payload = {'image': '', 'shape': ''} + + if not image_to_be_shown_updated: + return payload + + shape = image.shape + frame = cv2.imencode('.JPEG', image)[1] + encoded_image = base64.b64encode(frame) + + payload['image'] = encoded_image.decode('utf-8') + payload['shape'] = shape + + self.image_show_lock.acquire() + self.image_to_be_shown_updated = False + self.image_show_lock.release() + + return payload + + # Function to prepare image payload + # Encodes the image as a JSON string and sends through the WS + def payloadLeftImage(self): + self.left_image_show_lock.acquire() + left_image_to_be_shown_updated = self.left_image_to_be_shown_updated + left_image_to_be_shown = self.left_image_to_be_shown + self.left_image_show_lock.release() + + image = left_image_to_be_shown + payload = {'image': '', 'shape': ''} + + if not left_image_to_be_shown_updated: + return payload + + shape = image.shape + frame = cv2.imencode('.JPEG', image)[1] + encoded_image = base64.b64encode(frame) + + payload['image'] = encoded_image.decode('utf-8') + payload['shape'] = shape + + self.left_image_show_lock.acquire() + self.left_image_to_be_shown_updated = False + self.left_image_show_lock.release() + + return payload + + # Function for student to call + def showImage(self, image): + self.image_show_lock.acquire() + self.image_to_be_shown = image + self.image_to_be_shown_updated = True + self.image_show_lock.release() + + # Function for student to call + def showLeftImage(self, image): + self.left_image_show_lock.acquire() + self.left_image_to_be_shown = image + self.left_image_to_be_shown_updated = True + self.left_image_show_lock.release() + + # Function to get the client + # Called when a new client is received + def get_client(self, client, server): + self.client = client + + # Function to get value of Acknowledge + def get_acknowledge(self): + self.acknowledge_lock.acquire() + acknowledge = self.acknowledge + self.acknowledge_lock.release() + + return acknowledge + + # Function to get value of Acknowledge + def set_acknowledge(self, value): + self.acknowledge_lock.acquire() + self.acknowledge = value + self.acknowledge_lock.release() + + # Update the gui + def update_gui(self): + # Payload Image Message + payload = self.payloadImage() + self.payload["image"] = json.dumps(payload) + # Add position to payload + mouse_x, mouse_y, mouse_z = self.hal.get_position() + self.payload["pos"] = [mouse_x, mouse_y, mouse_z] + + message = "#gui" + json.dumps(self.payload) + self.server.send_message(self.client, message) + + # Payload Left Image Message + left_payload = self.payloadLeftImage() + self.left_payload["image"] = json.dumps(left_payload) + + message = "#gul" + json.dumps(self.left_payload) + self.server.send_message(self.client, message) + + # Update distance between cat and mouse drones + def update_dist(self): + cat_x, cat_y, cat_z = self.hal.get_position() + mouse_x, mouse_y, mouse_z = self.mouse.get_position() + + if mouse_z > 0.1: self.dist["ready"] = "true" + else: self.dist["ready"] = "false" + + dist = np.sqrt((mouse_x+2-cat_x)**2 + (mouse_y-cat_y)**2 + (mouse_z-cat_z)**2) + dist = int(dist*100)/100 + self.dist["dist"] = dist + + message = '#dst' + json.dumps(self.dist) + self.server.send_message(self.client, message) + + # Function to read the message from websocket + # Gets called when there is an incoming message from the client + def get_message(self, client, server, message): + # Acknowledge Message for GUI Thread + if message[:4] == "#ack": + self.set_acknowledge(True) + # elif message[:4] == "#mou": + # self.mouse.start_mouse(int(message[4:5])) + # elif message[:4] == "#stp": + # self.mouse.stop_mouse() + # elif message[:4] == "#rst": + # self.mouse.reset_mouse() + + # Activate the server + def run_server(self): + self.server = WebsocketServer(port=2304, host=self.host) + self.server.set_fn_new_client(self.get_client) + self.server.set_fn_message_received(self.get_message) + + logged = False + while not logged: + try: + f = open("/ws_gui_code.log", "w") + f.write("websocket_gui_code=ready") + f.close() + logged = True + except: + time.sleep(0.1) + + self.server.run_forever() + + # Function to reset + def reset_gui(self): + pass + + +# This class decouples the user thread +# and the GUI update thread +class ThreadGUI: + def __init__(self, gui): + self.gui = gui + + # Time variables + self.ideal_cycle = 80 + self.measured_cycle = 80 + self.iteration_counter = 0 + + # Function to start the execution of threads + def start(self): + self.measure_thread = threading.Thread(target=self.measure_thread) + self.thread = threading.Thread(target=self.run) + + self.measure_thread.start() + self.thread.start() + + print("GUI Thread Started!") + + # The measuring thread to measure frequency + def measure_thread(self): + while self.gui.client is None: + pass + + previous_time = datetime.now() + while True: + # Sleep for 2 seconds + time.sleep(2) + + # Measure the current time and subtract from previous time to get real time interval + current_time = datetime.now() + dt = current_time - previous_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + previous_time = current_time + + # Get the time period + try: + # Division by zero + self.measured_cycle = ms / self.iteration_counter + except: + self.measured_cycle = 0 + + # Reset the counter + self.iteration_counter = 0 + + # The main thread of execution + def run(self): + while self.gui.client is None: + pass + + while True: + start_time = datetime.now() + self.gui.update_gui() + #self.gui.update_dist() + acknowledge_message = self.gui.get_acknowledge() + + while not acknowledge_message: + acknowledge_message = self.gui.get_acknowledge() + + self.gui.set_acknowledge(False) + + finish_time = datetime.now() + self.iteration_counter = self.iteration_counter + 1 + + dt = finish_time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + if ms < self.ideal_cycle: + time.sleep((self.ideal_cycle-ms) / 1000.0) \ No newline at end of file diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/hal.py b/exercises/static/exercises/drone_cat_mouse/web-template/hal.py new file mode 100644 index 000000000..f039b4c15 --- /dev/null +++ b/exercises/static/exercises/drone_cat_mouse/web-template/hal.py @@ -0,0 +1,81 @@ +import rospy +import cv2 + +from drone_wrapper import DroneWrapper + + +# Hardware Abstraction Layer +class HAL: + IMG_WIDTH = 320 + IMG_HEIGHT = 240 + + def __init__(self): + rospy.init_node("HAL_cat") + + self.image = None + self.cat = DroneWrapper(name="rqt", ns="cat/") + + # Explicit initialization functions + # Class method, so user can call it without instantiation + @classmethod + def initRobot(cls): + new_instance = cls() + return new_instance + + # Get Image from ROS Driver Camera + def get_frontal_image(self): + image = self.cat.get_frontal_image() + image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) + return image_rgb + + def get_ventral_image(self): + image = self.cat.get_ventral_image() + image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) + return image_rgb + + def get_position(self): + pos = self.cat.get_position() + return pos + + def get_velocity(self): + vel = self.cat.get_velocity() + return vel + + def get_yaw_rate(self): + yaw_rate = self.cat.get_yaw_rate() + return yaw_rate + + def get_orientation(self): + orientation = self.cat.get_orientation() + return orientation + + def get_roll(self): + roll = self.cat.get_roll() + return roll + + def get_pitch(self): + pitch = self.cat.get_pitch() + return pitch + + def get_yaw(self): + yaw = self.cat.get_yaw() + return yaw + + def get_landed_state(self): + state = self.cat.get_landed_state() + return state + + def set_cmd_pos(self, x, y, z, az): + self.cat.set_cmd_pos(x, y, z, az) + + def set_cmd_vel(self, vx, vy, vz, az): + self.cat.set_cmd_vel(vx, vy, vz, az) + + def set_cmd_mix(self, vx, vy, z, az): + self.cat.set_cmd_mix(vx, vy, z, az) + + def takeoff(self, h=3): + self.cat.takeoff(h) + + def land(self): + self.cat.land() \ No newline at end of file diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/hal_guest.py b/exercises/static/exercises/drone_cat_mouse/web-template/hal_guest.py new file mode 100644 index 000000000..0dbe1eaba --- /dev/null +++ b/exercises/static/exercises/drone_cat_mouse/web-template/hal_guest.py @@ -0,0 +1,81 @@ +import rospy +import cv2 + +from drone_wrapper import DroneWrapper + + +# Hardware Abstraction Layer +class HAL: + IMG_WIDTH = 320 + IMG_HEIGHT = 240 + + def __init__(self): + rospy.init_node("HAL_mouse") + + self.image = None + self.mouse = DroneWrapper(name="rqt", ns="mouse/") + + # Explicit initialization functions + # Class method, so user can call it without instantiation + @classmethod + def initRobot(cls): + new_instance = cls() + return new_instance + + # Get Image from ROS Driver Camera + def get_frontal_image(self): + image = self.mouse.get_frontal_image() + image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) + return image_rgb + + def get_ventral_image(self): + image = self.mouse.get_ventral_image() + image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) + return image_rgb + + def get_position(self): + pos = self.mouse.get_position() + return pos + + def get_velocity(self): + vel = self.mouse.get_velocity() + return vel + + def get_yaw_rate(self): + yaw_rate = self.mouse.get_yaw_rate() + return yaw_rate + + def get_orientation(self): + orientation = self.mouse.get_orientation() + return orientation + + def get_roll(self): + roll = self.mouse.get_roll() + return roll + + def get_pitch(self): + pitch = self.mouse.get_pitch() + return pitch + + def get_yaw(self): + yaw = self.mouse.get_yaw() + return yaw + + def get_landed_state(self): + state = self.mouse.get_landed_state() + return state + + def set_cmd_pos(self, x, y, z, az): + self.mouse.set_cmd_pos(x, y, z, az) + + def set_cmd_vel(self, vx, vy, vz, az): + self.mouse.set_cmd_vel(vx, vy, vz, az) + + def set_cmd_mix(self, vx, vy, z, az): + self.mouse.set_cmd_mix(vx, vy, z, az) + + def takeoff(self, h=3): + self.mouse.takeoff(h) + + def land(self): + self.mouse.land() \ No newline at end of file diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/interfaces/__init__.py b/exercises/static/exercises/drone_cat_mouse/web-template/interfaces/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/interfaces/autonomous_mouse.py b/exercises/static/exercises/drone_cat_mouse/web-template/interfaces/autonomous_mouse.py new file mode 100644 index 000000000..fc9fbbb10 --- /dev/null +++ b/exercises/static/exercises/drone_cat_mouse/web-template/interfaces/autonomous_mouse.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python + +from drone_wrapper import DroneWrapper +from math import sin as s +from math import cos as c +from random import uniform as u +from time import time as t + +import sys + +a = 0 + + +def path_obfuscated(O00OO00O0OO00O0OO): + global a;O0O0000O0O00O0O00=round;O0OO0OOO00OO000O0=(lambda O00O0000000O00O00,O00OO0O0O0O000O0O:(lambda OOO00OO000O0OOO0O:((lambda OO00000O000O0OOOO:3 if O0O0000O0O00O0O00(OO00000O000O0OOOO/2)%2==0 else 0)(OOO00OO000O0OOO0O),(lambda O0OOO00000000OO0O:0)(OOO00OO000O0OOO0O),(lambda O0OO0O0OOOO0O0O0O:0)(OOO00OO000O0OOO0O),(lambda O00O0O0000O000O00:0)(OOO00OO000O0OOO0O)))(O00OO0O0O0O000O0O)if O00O0000000O00O00==0 else ((lambda O0O000O00000OOO0O:((lambda OO000O0000000OO00:1)(O0O000O00000OOO0O),(lambda OO000O0000O0OOOOO:-2 if O0O0000O0O00O0O00(OO000O0000O0OOOOO/4)%2==0 else 2)(O0O000O00000OOO0O),(lambda O000O0000000OO0O0:0)(O0O000O00000OOO0O),(lambda O00OOO000OOOOO000:0)(O0O000O00000OOO0O)))(O00OO0O0O0O000O0O)if O00O0000000O00O00==1 else ((lambda O0O0O0OOOO0O0000O:((lambda O00OOO0O000OOOO00:1)(O0O0O0OOOO0O0000O),(lambda O000O000OOOO0O000:s(O000O000OOOO0O000/2)*1)(O0O0O0OOOO0O0000O),(lambda O0OOOO00O00O00O00:c(O0OOOO00O00O00O00/2)*1)(O0O0O0OOOO0O0000O),(lambda O00000O00O0OO0O00:0)(O0O0O0OOOO0O0000O)))(O00OO0O0O0O000O0O)if O00O0000000O00O00==2 else ((lambda O0OO0000000OO0OOO:((lambda OO00OOO00O000000O:1)(O0OO0000000OO0OOO),(lambda O0O0O0000OO00OOO0:0)(O0OO0000000OO0OOO),(lambda O0O0OOOOO0OOOO000 :0)(O0OO0000000OO0OOO),(lambda O0O0OOO0000OO0OOO:u(-1,1)if O0O0000O0O00O0O00(O0O0OOO0000OO0OOO/2%2,2)<=0.05 else a)(O0OO0000000OO0OOO)))(O00OO0O0O0O000O0O)if O00O0000000O00O00==3 else None))))(O00OO00O0OO00O0OO,t());a =(O0OO0OOO00OO000O0[3]if O0OO0OOO00OO000O0 is not None else 0);return O0OO0OOO00OO000O0 + + +if __name__ == "__main__": + path = int(sys.argv[1]) + + drone = DroneWrapper() + drone.takeoff(h=5) + + while True: + v = path_obfuscated(path) + if v is not None: + vx, vy, vz, yaw = v + drone.set_cmd_vel(vx, vy, vz, yaw) + else: + print("[Mouse] Path {} not available".format(path)) + break diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/interfaces/camera.py b/exercises/static/exercises/drone_cat_mouse/web-template/interfaces/camera.py new file mode 100644 index 000000000..5a021a13e --- /dev/null +++ b/exercises/static/exercises/drone_cat_mouse/web-template/interfaces/camera.py @@ -0,0 +1,89 @@ +import rospy +from sensor_msgs.msg import Image as ImageROS +import threading +from math import pi as PI +import cv2 +from cv_bridge import CvBridge, CvBridgeError + + +MAXRANGE = 8 # max length received from imageD +MINRANGE = 0 + + +def imageMsg2Image(img, bridge): + + image = Image() + + image.width = img.width + image.height = img.height + image.format = "BGR8" + image.timeStamp = img.header.stamp.secs + (img.header.stamp.nsecs * 1e-9) + cv_image = 0 + if img.encoding[-2:] == "C1": + gray_img_buff = bridge.imgmsg_to_cv2(img, img.encoding) + cv_image = depthToRGB8(gray_img_buff, img.encoding) + else: + cv_image = bridge.imgmsg_to_cv2(img, "bgr8") + image.data = cv_image + return image + + +import numpy as np + + +class Image: + + def __init__(self): + + self.height = 3 # Image height [pixels] + self.width = 3 # Image width [pixels] + self.timeStamp = 0 # Time stamp [s] */ + self.format = "" # Image format string (RGB8, BGR,...) + self.data = np.zeros((self.height, self.width, 3), np.uint8) # The image data itself + self.data.shape = self.height, self.width, 3 + + def __str__(self): + s = "Image: {\n height: " + str(self.height) + "\n width: " + str(self.width) + s = s + "\n format: " + self.format + "\n timeStamp: " + str(self.timeStamp) + s = s + "\n data: " + str(self.data) + "\n}" + return s + + +class ListenerCamera: + + def __init__(self, topic): + + self.topic = topic + self.data = Image() + self.sub = None + self.lock = threading.Lock() + + self.bridge = CvBridge() + self.start() + + def __callback(self, img): + + image = imageMsg2Image(img, self.bridge) + + self.lock.acquire() + self.data = image + self.lock.release() + + def stop(self): + + self.sub.unregister() + + def start(self): + self.sub = rospy.Subscriber(self.topic, ImageROS, self.__callback) + + def getImage(self): + + self.lock.acquire() + image = self.data + self.lock.release() + + return image + + def hasproxy(self): + + return hasattr(self, "sub") and self.sub diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/interfaces/motors.py b/exercises/static/exercises/drone_cat_mouse/web-template/interfaces/motors.py new file mode 100644 index 000000000..70dca8a46 --- /dev/null +++ b/exercises/static/exercises/drone_cat_mouse/web-template/interfaces/motors.py @@ -0,0 +1,123 @@ +import rospy +from geometry_msgs.msg import Twist +import threading +from math import pi as PI +from .threadPublisher import ThreadPublisher + + + +def cmdvel2Twist(vel): + + tw = Twist() + tw.linear.x = vel.vx + tw.linear.y = vel.vy + tw.linear.z = vel.vz + tw.angular.x = vel.ax + tw.angular.y = vel.ay + tw.angular.z = vel.az + + return tw + + +class CMDVel (): + + def __init__(self): + + self.vx = 0 # vel in x[m/s] (use this for V in wheeled robots) + self.vy = 0 # vel in y[m/s] + self.vz = 0 # vel in z[m/s] + self.ax = 0 # angular vel in X axis [rad/s] + self.ay = 0 # angular vel in X axis [rad/s] + self.az = 0 # angular vel in Z axis [rad/s] (use this for W in wheeled robots) + self.timeStamp = 0 # Time stamp [s] + + + def __str__(self): + s = "CMDVel: {\n vx: " + str(self.vx) + "\n vy: " + str(self.vy) + s = s + "\n vz: " + str(self.vz) + "\n ax: " + str(self.ax) + s = s + "\n ay: " + str(self.ay) + "\n az: " + str(self.az) + s = s + "\n timeStamp: " + str(self.timeStamp) + "\n}" + return s + +class PublisherMotors: + + def __init__(self, topic, maxV, maxW): + + self.maxW = maxW + self.maxV = maxV + + self.topic = topic + self.data = CMDVel() + self.pub = rospy.Publisher(self.topic, Twist, queue_size=1) + + self.lock = threading.Lock() + + self.kill_event = threading.Event() + self.thread = ThreadPublisher(self, self.kill_event) + + self.thread.daemon = True + self.start() + + def publish (self): + + self.lock.acquire() + tw = cmdvel2Twist(self.data) + self.lock.release() + self.pub.publish(tw) + + def stop(self): + + self.kill_event.set() + self.pub.unregister() + + def start (self): + + self.kill_event.clear() + self.thread.start() + + + + def getMaxW(self): + return self.maxW + + def getMaxV(self): + return self.maxV + + + def sendVelocities(self, vel): + + self.lock.acquire() + self.data = vel + self.lock.release() + + def sendV(self, v): + + self.sendVX(v) + + def sendL(self, l): + + self.sendVY(l) + + def sendW(self, w): + + self.sendAZ(w) + + def sendVX(self, vx): + + self.lock.acquire() + self.data.vx = vx + self.lock.release() + + def sendVY(self, vy): + + self.lock.acquire() + self.data.vy = vy + self.lock.release() + + def sendAZ(self, az): + + self.lock.acquire() + self.data.az = az + self.lock.release() + + diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/interfaces/pose3d.py b/exercises/static/exercises/drone_cat_mouse/web-template/interfaces/pose3d.py new file mode 100644 index 000000000..fd0bfc37a --- /dev/null +++ b/exercises/static/exercises/drone_cat_mouse/web-template/interfaces/pose3d.py @@ -0,0 +1,176 @@ +import rospy +import threading +from math import asin, atan2, pi +from nav_msgs.msg import Odometry + +def quat2Yaw(qw, qx, qy, qz): + ''' + Translates from Quaternion to Yaw. + + @param qw,qx,qy,qz: Quaternion values + + @type qw,qx,qy,qz: float + + @return Yaw value translated from Quaternion + + ''' + rotateZa0=2.0*(qx*qy + qw*qz) + rotateZa1=qw*qw + qx*qx - qy*qy - qz*qz + rotateZ=0.0 + if(rotateZa0 != 0.0 and rotateZa1 != 0.0): + rotateZ=atan2(rotateZa0,rotateZa1) + return rotateZ + +def quat2Pitch(qw, qx, qy, qz): + ''' + Translates from Quaternion to Pitch. + + @param qw,qx,qy,qz: Quaternion values + + @type qw,qx,qy,qz: float + + @return Pitch value translated from Quaternion + + ''' + + rotateYa0=-2.0*(qx*qz - qw*qy) + rotateY=0.0 + if(rotateYa0 >= 1.0): + rotateY = pi/2.0 + elif(rotateYa0 <= -1.0): + rotateY = -pi/2.0 + else: + rotateY = asin(rotateYa0) + + return rotateY + +def quat2Roll (qw, qx, qy, qz): + ''' + Translates from Quaternion to Roll. + + @param qw,qx,qy,qz: Quaternion values + + @type qw,qx,qy,qz: float + + @return Roll value translated from Quaternion + + ''' + rotateXa0=2.0*(qy*qz + qw*qx) + rotateXa1=qw*qw - qx*qx - qy*qy + qz*qz + rotateX=0.0 + + if(rotateXa0 != 0.0 and rotateXa1 != 0.0): + rotateX=atan2(rotateXa0, rotateXa1) + return rotateX + + +def odometry2Pose3D(odom): + ''' + Translates from ROS Odometry to JderobotTypes Pose3d. + + @param odom: ROS Odometry to translate + + @type odom: Odometry + + @return a Pose3d translated from odom + + ''' + pose = Pose3d() + ori = odom.pose.pose.orientation + + pose.x = odom.pose.pose.position.x + pose.y = odom.pose.pose.position.y + pose.z = odom.pose.pose.position.z + #pose.h = odom.pose.pose.position.h + pose.yaw = quat2Yaw(ori.w, ori.x, ori.y, ori.z) + pose.pitch = quat2Pitch(ori.w, ori.x, ori.y, ori.z) + pose.roll = quat2Roll(ori.w, ori.x, ori.y, ori.z) + pose.q = [ori.w, ori.x, ori.y, ori.z] + pose.timeStamp = odom.header.stamp.secs + (odom.header.stamp.nsecs *1e-9) + + return pose + +class Pose3d (): + + def __init__(self): + + self.x = 0 # X coord [meters] + self.y = 0 # Y coord [meters] + self.z = 0 # Z coord [meters] + self.h = 1 # H param + self.yaw = 0 #Yaw angle[rads] + self.pitch = 0 # Pitch angle[rads] + self.roll = 0 # Roll angle[rads] + self.q = [0,0,0,0] # Quaternion + self.timeStamp = 0 # Time stamp [s] + + + def __str__(self): + s = "Pose3D: {\n x: " + str(self.x) + "\n Y: " + str(self.y) + s = s + "\n Z: " + str(self.z) + "\n H: " + str(self.h) + s = s + "\n Yaw: " + str(self.yaw) + "\n Pitch: " + str(self.pitch) + "\n Roll: " + str(self.roll) + s = s + "\n quaternion: " + str(self.q) + "\n timeStamp: " + str(self.timeStamp) + "\n}" + return s + + +class ListenerPose3d: + ''' + ROS Pose3D Subscriber. Pose3D Client to Receive pose3d from ROS nodes. + ''' + def __init__(self, topic): + ''' + ListenerPose3d Constructor. + + @param topic: ROS topic to subscribe + + @type topic: String + + ''' + self.topic = topic + self.data = Pose3d() + self.sub = None + self.lock = threading.Lock() + self.start() + + def __callback (self, odom): + ''' + Callback function to receive and save Pose3d. + + @param odom: ROS Odometry received + + @type odom: Odometry + + ''' + pose = odometry2Pose3D(odom) + + self.lock.acquire() + self.data = pose + self.lock.release() + + def stop(self): + ''' + Stops (Unregisters) the client. + + ''' + self.sub.unregister() + + def start (self): + ''' + Starts (Subscribes) the client. + + ''' + self.sub = rospy.Subscriber(self.topic, Odometry, self.__callback) + + def getPose3d(self): + ''' + Returns last Pose3d. + + @return last JdeRobotTypes Pose3d saved + + ''' + self.lock.acquire() + pose = self.data + self.lock.release() + + return pose + diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/interfaces/threadPublisher.py b/exercises/static/exercises/drone_cat_mouse/web-template/interfaces/threadPublisher.py new file mode 100644 index 000000000..69aa0ad48 --- /dev/null +++ b/exercises/static/exercises/drone_cat_mouse/web-template/interfaces/threadPublisher.py @@ -0,0 +1,46 @@ +# +# Copyright (C) 1997-2016 JDE Developers Team +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. +# Authors : +# Alberto Martin Florido +# Aitor Martinez Fernandez +# +import threading +import time +from datetime import datetime + +time_cycle = 80 + + +class ThreadPublisher(threading.Thread): + + def __init__(self, pub, kill_event): + self.pub = pub + self.kill_event = kill_event + threading.Thread.__init__(self, args=kill_event) + + def run(self): + while (not self.kill_event.is_set()): + start_time = datetime.now() + + self.pub.publish() + + finish_Time = datetime.now() + + dt = finish_Time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + #print (ms) + if (ms < time_cycle): + time.sleep((time_cycle - ms) / 1000.0) \ No newline at end of file diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/interfaces/threadStoppable.py b/exercises/static/exercises/drone_cat_mouse/web-template/interfaces/threadStoppable.py new file mode 100644 index 000000000..b631d180f --- /dev/null +++ b/exercises/static/exercises/drone_cat_mouse/web-template/interfaces/threadStoppable.py @@ -0,0 +1,36 @@ +import threading +import time +from datetime import datetime + +time_cycle = 80 + + +class StoppableThread(threading.Thread): + """Thread class with a stop() method. The thread itself has to check + regularly for the stopped() condition.""" + + def __init__(self, target, kill_event=threading.Event(), *args, **kwargs): + super(StoppableThread, self).__init__(*args, **kwargs) + self._target = target + self._target_args = kwargs["args"] + self._kill_event = kill_event + + def run(self): + while not self.stopped(): + start_time = datetime.now() + + self._target(*self._target_args) + + finish_time = datetime.now() + + dt = finish_time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + # print (ms) + if ms < time_cycle: + time.sleep((time_cycle - ms) / 1000.0) + + def stop(self): + self._kill_event.set() + + def stopped(self): + return self._kill_event.is_set() diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/launch/drone_cat_mouse.launch b/exercises/static/exercises/drone_cat_mouse/web-template/launch/drone_cat_mouse.launch new file mode 100644 index 000000000..2fd263a6f --- /dev/null +++ b/exercises/static/exercises/drone_cat_mouse/web-template/launch/drone_cat_mouse.launch @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/mouse.py b/exercises/static/exercises/drone_cat_mouse/web-template/mouse.py new file mode 100644 index 000000000..6c6046801 --- /dev/null +++ b/exercises/static/exercises/drone_cat_mouse/web-template/mouse.py @@ -0,0 +1,69 @@ +import threading +import rospy + +from drone_wrapper import DroneWrapper +from interfaces.autonomous_mouse import path_obfuscated +from interfaces.threadStoppable import StoppableThread +from gazebo_msgs.srv import SetModelState +from gazebo_msgs.msg import ModelState + + +class Mouse: + def __init__(self): + self.mouse = DroneWrapper(name="rqt", ns="mouse/") + self.reset_state = rospy.ServiceProxy('/gazebo/set_model_state', SetModelState) + + # Explicit initialization functions + # Class method, so user can call it without instantiation + @classmethod + def initRobot(cls): + new_instance = cls() + return new_instance + + def takeoff(self): + self.mouse.takeoff(h=5) + + def start_mouse(self, path_level): + self.takeoff() + self.thread = StoppableThread(target=self.autonomous_mouse, args=[path_level]) + self.thread.start() + + def land(self): + self.mouse.land() + + def stop_mouse(self): + self.thread.stop() + self.mouse.set_cmd_mix(vx=0, vy=0, z=self.mouse.get_position()[2], az=0) + + def reset_mouse(self): + self.thread.stop() + self.mouse.land() + + req = ModelState() + req.model_name = "firefly_1" + req.pose.position.x = 2.0 + req.pose.position.y = 0.0 + req.pose.position.z = 0.05 + req.pose.orientation.x = 0.0 + req.pose.orientation.y = 0.0 + req.pose.orientation.z = 0.0 + req.pose.orientation.w = 1.0 + req.twist.linear.x = 0.0 + req.twist.linear.y = 0.0 + req.twist.linear.z = 0.0 + req.twist.angular.x = 0.0 + req.twist.angular.y = 0.0 + req.twist.angular.z = 0.0 + self.reset_state(req) + + def autonomous_mouse(self, path_level): + v = path_obfuscated(path_level) + if v is not None: + vx, vy, vz, yaw = v + self.mouse.set_cmd_vel(vx, vy, vz, yaw) + else: + print("[Mouse] Path {} not available".format(path_level)) + + def get_position(self): + pos = self.mouse.get_position() + return pos \ No newline at end of file diff --git a/exercises/static/exercises/drone_gymkhana/README.md b/exercises/static/exercises/drone_gymkhana/README.md new file mode 100755 index 000000000..34ae0241b --- /dev/null +++ b/exercises/static/exercises/drone_gymkhana/README.md @@ -0,0 +1 @@ +[Exercise Documentation Website](https://jderobot.github.io/RoboticsAcademy/exercises/Drones/drone_gymkhana) diff --git a/exercises/static/exercises/drone_gymkhana/drone_gymkhana.launch b/exercises/static/exercises/drone_gymkhana/drone_gymkhana.launch new file mode 100644 index 000000000..1b8189ff1 --- /dev/null +++ b/exercises/static/exercises/drone_gymkhana/drone_gymkhana.launch @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/exercises/static/exercises/drone_gymkhana/drone_gymkhana.world b/exercises/static/exercises/drone_gymkhana/drone_gymkhana.world new file mode 100644 index 000000000..f07953184 --- /dev/null +++ b/exercises/static/exercises/drone_gymkhana/drone_gymkhana.world @@ -0,0 +1,144 @@ + + + + + + + 18.377 -12.9995 8.68736 0 0.383644 2.52736 + orbit + perspective + + + + false + 1 + + + model://grass_plane + + + + + number1 + model://number1 + 8 8 0.4 0 0 0 + + + number2 + model://number2 + 8 -8 0.4 0 0 0 + + + number3 + model://number3 + -8 -8 0.4 0 0 0 + + + number4 + model://number4 + -8 8 0.4 0 0 0 + + + + + logo + model://logoJdeRobot + 0 -4 0 0 0 0 + + + arrow1-2 + model://arrow + 8 0 0.01 0 0 -1.57 + + + arrow2-3 + model://arrow + 0 -8 0.01 0 0 -3.14159 + + + arrow3-4 + model://arrow + -8 0 0.01 0 0 1.57 + + + + + hoop1-1 + model://hoop_red + 8 3 -4 0 0 0 + + + hoop1-2 + model://hoop_red + 8 -3 -4 0 0 0 + + + + hoop2-1 + model://hoop_red + 3 -8 -3 0 0 1.5708 + + + hoop2-2 + model://hoop_red + -3 -8 -2 0 0 1.5708 + + + + hoop3-1 + model://hoop_red + -8 -4 -2 0 0 0 + + + hoop3-2 + model://hoop_red + -6 0 -3 0 0 0 + + + hoop3-3 + model://hoop_red + -8 4 -4 0 0 0 + + + + + takeoff-target + model://custom_box_target_green + 0 0 0.5 0 0 0 + + + land-target + model://custom_box_target_red + 0 8 0.5 0 0 0 + + + + + model://sun + + + + + 0 0 -9.8066 + + + quick + 10 + 1.3 + 0 + + + 0 + 0.2 + 100 + 0.001 + + + 0.004 + 1 + 250 + 6.0e-6 2.3e-5 -4.2e-5 + + + + diff --git a/exercises/static/exercises/drone_gymkhana/my_solution.py b/exercises/static/exercises/drone_gymkhana/my_solution.py new file mode 100755 index 000000000..a2ab79727 --- /dev/null +++ b/exercises/static/exercises/drone_gymkhana/my_solution.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python + +import rospy +import numpy as np +import cv2 +import math +from math import pi +from drone_wrapper import DroneWrapper +from std_msgs.msg import Bool, Float64 +from sensor_msgs.msg import Image +from geometry_msgs.msg import Twist, Pose + +code_live_flag = False + +def gui_play_stop_cb(msg): + global code_live_flag, code_live_timer + if msg.data == True: + if not code_live_flag: + code_live_flag = True + code_live_timer = rospy.Timer(rospy.Duration(nsecs=50000000), execute) + else: + if code_live_flag: + code_live_flag = False + code_live_timer.shutdown() + +def set_image_filtered(img): + gui_filtered_img_pub.publish(drone.bridge.cv2_to_imgmsg(img)) + +def set_image_threshed(img): + gui_threshed_img_pub.publish(drone.bridge.cv2_to_imgmsg(img)) + +################# Build your position control function here ##################### + +def position_control(): + global drone + # Insert your code here + +################################################################################# + +def execute(event): + global drone + + ################# Insert your code here ################################# + # Waypoint list + waypoint_list = [] + + # Navigation using position control + for waypoint in waypoint_list: + position_control() + + ######################################################################### + +if __name__ == "__main__": + drone = DroneWrapper() + rospy.Subscriber('gui/play_stop', Bool, gui_play_stop_cb) + gui_filtered_img_pub = rospy.Publisher('interface/filtered_img', Image, queue_size = 1) + gui_threshed_img_pub = rospy.Publisher('interface/threshed_img', Image, queue_size = 1) + code_live_flag = False + code_live_timer = rospy.Timer(rospy.Duration(nsecs=50000000), execute) + code_live_timer.shutdown() + while not rospy.is_shutdown(): + rospy.spin() diff --git a/exercises/static/exercises/drone_gymkhana/web-template/RADI-launch b/exercises/static/exercises/drone_gymkhana/web-template/RADI-launch new file mode 100755 index 000000000..afde9c081 --- /dev/null +++ b/exercises/static/exercises/drone_gymkhana/web-template/RADI-launch @@ -0,0 +1,2 @@ +#!/bin/sh +docker run -it --rm -p 8000:8000 -p 2303:2303 -p 1905:1905 -p 8765:8765 -p 6080:6080 -p 1108:1108 jderobot/robotics-academy:3.1.2 ./start-3.1.sh diff --git a/exercises/static/exercises/drone_gymkhana/web-template/README.md b/exercises/static/exercises/drone_gymkhana/web-template/README.md new file mode 100644 index 000000000..34ae0241b --- /dev/null +++ b/exercises/static/exercises/drone_gymkhana/web-template/README.md @@ -0,0 +1 @@ +[Exercise Documentation Website](https://jderobot.github.io/RoboticsAcademy/exercises/Drones/drone_gymkhana) diff --git a/exercises/static/exercises/drone_gymkhana/web-template/code/academy.py b/exercises/static/exercises/drone_gymkhana/web-template/code/academy.py new file mode 100644 index 000000000..7a59ce7f5 --- /dev/null +++ b/exercises/static/exercises/drone_gymkhana/web-template/code/academy.py @@ -0,0 +1,8 @@ +# Enter sequential code! +from GUI import GUI +from HAL import HAL + +while True: + # Enter iterative code! + img = HAL.get_ventral_image() + GUI.showImage(img) \ No newline at end of file diff --git a/exercises/static/exercises/drone_gymkhana/web-template/console.py b/exercises/static/exercises/drone_gymkhana/web-template/console.py new file mode 100644 index 000000000..7b72a0913 --- /dev/null +++ b/exercises/static/exercises/drone_gymkhana/web-template/console.py @@ -0,0 +1,20 @@ +# Functions to start and close console +import os +import sys + + +def start_console(): + # Get all the file descriptors and choose the latest one + fds = os.listdir("/dev/pts/") + fds.sort() + console_fd = fds[-2] + + sys.stderr = open('/dev/pts/' + console_fd, 'w') + sys.stdout = open('/dev/pts/' + console_fd, 'w') + sys.stdin = open('/dev/pts/' + console_fd, 'w') + + +def close_console(): + sys.stderr.close() + sys.stdout.close() + sys.stdin.close() diff --git a/exercises/static/exercises/drone_gymkhana/web-template/drone_gymkhana.world b/exercises/static/exercises/drone_gymkhana/web-template/drone_gymkhana.world new file mode 100644 index 000000000..30a4fea15 --- /dev/null +++ b/exercises/static/exercises/drone_gymkhana/web-template/drone_gymkhana.world @@ -0,0 +1,158 @@ + + + + + + + 18.377 -12.9995 8.68736 0 0.383644 2.52736 + orbit + perspective + + + + + model://grass_plane + + + + + number1 + model://number1 + 8 8 0.4 0 0 0 + + + number2 + model://number2 + 8 -8 0.4 0 0 0 + + + number3 + model://number3 + -8 -8 0.4 0 0 0 + + + number4 + model://number4 + -8 8 0.4 0 0 0 + + + + + logo + model://logoJdeRobot + 0 -4 0 0 0 0 + + + arrow1-2 + model://arrow + 8 0 0.01 0 0 -1.57 + + + arrow2-3 + model://arrow + 0 -8 0.01 0 0 -3.14159 + + + arrow3-4 + model://arrow + -8 0 0.01 0 0 1.57 + + + + + hoop1-1 + model://hoop_red + 8 3 -4 0 0 0 + + + hoop1-2 + model://hoop_red + 8 -3 -4 0 0 0 + + + + hoop2-1 + model://hoop_red + 3 -8 -3 0 0 1.5708 + + + hoop2-2 + model://hoop_red + -3 -8 -2 0 0 1.5708 + + + + hoop3-1 + model://hoop_red + -8 -4 -2 0 0 0 + + + hoop3-2 + model://hoop_red + -6 0 -3 0 0 0 + + + hoop3-3 + model://hoop_red + -8 4 -4 0 0 0 + + + + + takeoff-target + model://custom_box_target_green + 0 0 0.5 0 0 0 + + + land-target + model://custom_box_target_red + 0 8 0.5 0 0 0 + + + + + model://logoJdeRobot + -12.0.0 0.0 0.0 0.0 0.0 0.0 + + + + 0 + 0.4 0.4 0.4 1.0 + 0.7 0.7 0.7 1 + + + 12 + + + + + + + model://sun + + + + + 0 0 -9.8066 + + + quick + 10 + 1.3 + 0 + + + 0 + 0.2 + 100 + 0.001 + + + 0.004 + 1 + 250 + 6.0e-6 2.3e-5 -4.2e-5 + + + + diff --git a/exercises/static/exercises/drone_gymkhana/web-template/exercise.py b/exercises/static/exercises/drone_gymkhana/web-template/exercise.py new file mode 100644 index 000000000..8f68f43ed --- /dev/null +++ b/exercises/static/exercises/drone_gymkhana/web-template/exercise.py @@ -0,0 +1,362 @@ +#!/usr/bin/env python + +from __future__ import print_function + +from websocket_server import WebsocketServer +import time +import threading +import subprocess +import sys +from datetime import datetime +import re +import json +import importlib + +import rospy +from std_srvs.srv import Empty + +from gui import GUI, ThreadGUI +from hal import HAL +from console import start_console, close_console + + +class Template: + # Initialize class variables + # self.ideal_cycle to run an execution for at least 1 second + # self.process for the current running process + def __init__(self): + self.measure_thread = None + self.thread = None + self.reload = False + self.stop_brain = True + self.user_code = "" + + # Time variables + self.ideal_cycle = 80 + self.measured_cycle = 80 + self.iteration_counter = 0 + self.real_time_factor = 0 + self.frequency_message = {'brain': '', 'gui': '', 'rtf': ''} + + self.server = None + self.client = None + self.host = sys.argv[1] + + # Initialize the GUI, HAL and Console behind the scenes + self.hal = HAL() + self.gui = GUI(self.host) + + # Function to parse the code + # A few assumptions: + # 1. The user always passes sequential and iterative codes + # 2. Only a single infinite loop + def parse_code(self, source_code): + sequential_code, iterative_code = self.seperate_seq_iter(source_code) + return iterative_code, sequential_code + + # Function to separate the iterative and sequential code + def seperate_seq_iter(self, source_code): + if source_code == "": + return "", "" + + # Search for an instance of while True + infinite_loop = re.search(r'[^ ]while\s*\(\s*True\s*\)\s*:|[^ ]while\s*True\s*:|[^ ]while\s*1\s*:|[^ ]while\s*\(\s*1\s*\)\s*:', source_code) + + # Separate the content inside while True and the other + # (Separating the sequential and iterative part!) + try: + start_index = infinite_loop.start() + iterative_code = source_code[start_index:] + sequential_code = source_code[:start_index] + + # Remove while True: syntax from the code + # And remove the the 4 spaces indentation before each command + iterative_code = re.sub(r'[^ ]while\s*\(\s*True\s*\)\s*:|[^ ]while\s*True\s*:|[^ ]while\s*1\s*:|[^ ]while\s*\(\s*1\s*\)\s*:', '', iterative_code) + # Add newlines to match line on bug report + extra_lines = sequential_code.count('\n') + while (extra_lines >= 0): + iterative_code = '\n' + iterative_code + extra_lines -= 1 + iterative_code = re.sub(r'^[ ]{4}', '', iterative_code, flags=re.M) + + except: + sequential_code = source_code + iterative_code = "" + + return sequential_code, iterative_code + + # The process function + def process_code(self, source_code): + # Redirect the information to console + start_console() + + iterative_code, sequential_code = self.parse_code(source_code) + + # print(sequential_code) + # print(iterative_code) + + # The Python exec function + # Run the sequential part + gui_module, hal_module = self.generate_modules() + reference_environment = {"GUI": gui_module, "HAL": hal_module} + while (self.stop_brain == True): + if (self.reload == True): + return + time.sleep(0.1) + exec(sequential_code, reference_environment) + + # Run the iterative part inside template + # and keep the check for flag + while self.reload == False: + while (self.stop_brain == True): + if (self.reload == True): + return + time.sleep(0.1) + + start_time = datetime.now() + + # Execute the iterative portion + exec(iterative_code, reference_environment) + + # Template specifics to run! + finish_time = datetime.now() + dt = finish_time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + + # Keep updating the iteration counter + if (iterative_code == ""): + self.iteration_counter = 0 + else: + self.iteration_counter = self.iteration_counter + 1 + + # The code should be run for atleast the target time step + # If it's less put to sleep + if (ms < self.ideal_cycle): + time.sleep((self.ideal_cycle - ms) / 1000.0) + + close_console() + print("Current Thread Joined!") + + # Function to generate the modules for use in ACE Editor + def generate_modules(self): + # Define HAL module + hal_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) + hal_module.HAL = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) + # hal_module.drone = imp.new_module("drone") + # motors# hal_module.HAL.motors = imp.new_module("motors") + + # Add HAL functions + hal_module.HAL.get_frontal_image = self.hal.get_frontal_image + hal_module.HAL.get_ventral_image = self.hal.get_ventral_image + hal_module.HAL.get_position = self.hal.get_position + hal_module.HAL.get_velocity = self.hal.get_velocity + hal_module.HAL.get_yaw_rate = self.hal.get_yaw_rate + hal_module.HAL.get_orientation = self.hal.get_orientation + hal_module.HAL.get_roll = self.hal.get_roll + hal_module.HAL.get_pitch = self.hal.get_pitch + hal_module.HAL.get_yaw = self.hal.get_yaw + hal_module.HAL.get_landed_state = self.hal.get_landed_state + hal_module.HAL.set_cmd_pos = self.hal.set_cmd_pos + hal_module.HAL.set_cmd_vel = self.hal.set_cmd_vel + hal_module.HAL.set_cmd_mix = self.hal.set_cmd_mix + hal_module.HAL.takeoff = self.hal.takeoff + hal_module.HAL.land = self.hal.land + + # Define GUI module + gui_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) + gui_module.GUI = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) + + # Add GUI functions + gui_module.GUI.showImage = self.gui.showImage + gui_module.GUI.showLeftImage = self.gui.showLeftImage + + # Adding modules to system + # Protip: The names should be different from + # other modules, otherwise some errors + sys.modules["HAL"] = hal_module + sys.modules["GUI"] = gui_module + + return gui_module, hal_module + + # Function to measure the frequency of iterations + def measure_frequency(self): + previous_time = datetime.now() + # An infinite loop + while True: + # Sleep for 2 seconds + time.sleep(2) + + # Measure the current time and subtract from the previous time to get real time interval + current_time = datetime.now() + dt = current_time - previous_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + previous_time = current_time + + # Get the time period + try: + # Division by zero + self.measured_cycle = ms / self.iteration_counter + except: + self.measured_cycle = 0 + + # Reset the counter + self.iteration_counter = 0 + + # Send to client + self.send_frequency_message() + + # Function to generate and send frequency messages + def send_frequency_message(self): + # This function generates and sends frequency measures of the brain and gui + brain_frequency = 0; gui_frequency = 0 + try: + brain_frequency = round(1000 / self.measured_cycle, 1) + except ZeroDivisionError: + brain_frequency = 0 + + try: + gui_frequency = round(1000 / self.thread_gui.measured_cycle, 1) + except ZeroDivisionError: + gui_frequency = 0 + + self.frequency_message["brain"] = brain_frequency + self.frequency_message["gui"] = gui_frequency + self.frequency_message["rtf"] = self.real_time_factor + + message = "#freq" + json.dumps(self.frequency_message) + self.server.send_message(self.client, message) + + def send_ping_message(self): + self.server.send_message(self.client, "#ping") + + # Function to notify the front end that the code was received and sent to execution + def send_code_message(self): + self.server.send_message(self.client, "#exec") + + # Function to track the real time factor from Gazebo statistics + # https://stackoverflow.com/a/17698359 + # (For reference, Python3 solution specified in the same answer) + def track_stats(self): + args = ["gz", "stats", "-p"] + # Prints gz statistics. "-p": Output comma-separated values containing- + # real-time factor (percent), simtime (sec), realtime (sec), paused (T or F) + stats_process = subprocess.Popen(args, stdout=subprocess.PIPE, bufsize=1, universal_newlines=True) + # bufsize=1 enables line-bufferred mode (the input buffer is flushed + # automatically on newlines if you would write to process.stdin ) + with stats_process.stdout: + for line in iter(stats_process.stdout.readline, ''): + stats_list = [x.strip() for x in line.split(',')] + self.real_time_factor = stats_list[0] + + # Function to maintain thread execution + def execute_thread(self, source_code): + # Keep checking until the thread is alive + # The thread will die when the coming iteration reads the flag + if self.thread is not None: + while self.thread.is_alive(): + time.sleep(0.2) + + # Turn the flag down, the iteration has successfully stopped! + self.reload = False + # New thread execution + self.thread = threading.Thread(target=self.process_code, args=[source_code]) + self.thread.start() + self.send_code_message() + print("New Thread Started!") + + # Function to read and set frequency from incoming message + def read_frequency_message(self, message): + frequency_message = json.loads(message) + + # Set brain frequency + frequency = float(frequency_message["brain"]) + self.ideal_cycle = 1000.0 / frequency + + # Set gui frequency + frequency = float(frequency_message["gui"]) + self.thread_gui.ideal_cycle = 1000.0 / frequency + + return + + # The websocket function + # Gets called when there is an incoming message from the client + def handle(self, client, server, message): + if message[:5] == "#freq": + frequency_message = message[5:] + self.read_frequency_message(frequency_message) + time.sleep(1) + return + + elif(message[:5] == "#ping"): + time.sleep(1) + self.send_ping_message() + return + + elif (message[:5] == "#code"): + try: + # Once received turn the reload flag up and send it to execute_thread function + self.user_code = message[6:] + # print(repr(code)) + self.reload = True + self.execute_thread(self.user_code) + except: + pass + + elif (message[:5] == "#rest"): + try: + self.reload = True + self.stop_brain = True + self.execute_thread(self.user_code) + except: + pass + + elif (message[:5] == "#stop"): + self.stop_brain = True + + elif (message[:5] == "#play"): + self.stop_brain = False + + # Function that gets called when the server is connected + def connected(self, client, server): + self.client = client + # Start the GUI update thread + self.thread_gui = ThreadGUI(self.gui) + self.thread_gui.start() + + # Start the real time factor tracker thread + self.stats_thread = threading.Thread(target=self.track_stats) + self.stats_thread.start() + + # Start measure frequency + self.measure_thread = threading.Thread(target=self.measure_frequency) + self.measure_thread.start() + + print(client, 'connected') + + # Function that gets called when the connected closes + def handle_close(self, client, server): + print(client, 'closed') + + def run_server(self): + self.server = WebsocketServer(port=1905, host=self.host) + self.server.set_fn_new_client(self.connected) + self.server.set_fn_client_left(self.handle_close) + self.server.set_fn_message_received(self.handle) + + logged = False + while not logged: + try: + f = open("/ws_code.log", "w") + f.write("websocket_code=ready") + f.close() + logged = True + except: + time.sleep(0.1) + + self.server.run_forever() + + +# Execute! +if __name__ == "__main__": + server = Template() + server.run_server() \ No newline at end of file diff --git a/exercises/static/exercises/drone_gymkhana/web-template/gui.py b/exercises/static/exercises/drone_gymkhana/web-template/gui.py new file mode 100644 index 000000000..51d327f1a --- /dev/null +++ b/exercises/static/exercises/drone_gymkhana/web-template/gui.py @@ -0,0 +1,248 @@ +import json +import cv2 +import base64 +import threading +import time +from datetime import datetime +from websocket_server import WebsocketServer + + +# Graphical User Interface Class +class GUI: + # Initialization function + # The actual initialization + def __init__(self, host): + t = threading.Thread(target=self.run_server) + + self.payload = {'image': ''} + self.left_payload = {'image': ''} + self.server = None + self.client = None + + self.host = host + + # Image variables + self.image_to_be_shown = None + self.image_to_be_shown_updated = False + self.image_show_lock = threading.Lock() + + self.left_image_to_be_shown = None + self.left_image_to_be_shown_updated = False + self.left_image_show_lock = threading.Lock() + + self.acknowledge = False + self.acknowledge_lock = threading.Lock() + + # Take the console object to set the same websocket and client + t.start() + + # Explicit initialization function + # Class method, so user can call it without instantiation + @classmethod + def initGUI(cls, host): + # self.payload = {'image': '', 'shape': []} + new_instance = cls(host) + return new_instance + + # Function to prepare image payload + # Encodes the image as a JSON string and sends through the WS + def payloadImage(self): + self.image_show_lock.acquire() + image_to_be_shown_updated = self.image_to_be_shown_updated + image_to_be_shown = self.image_to_be_shown + self.image_show_lock.release() + + image = image_to_be_shown + payload = {'image': '', 'shape': ''} + + if not image_to_be_shown_updated: + return payload + + shape = image.shape + frame = cv2.imencode('.JPEG', image)[1] + encoded_image = base64.b64encode(frame) + + payload['image'] = encoded_image.decode('utf-8') + payload['shape'] = shape + + self.image_show_lock.acquire() + self.image_to_be_shown_updated = False + self.image_show_lock.release() + + return payload + + # Function to prepare image payload + # Encodes the image as a JSON string and sends through the WS + def payloadLeftImage(self): + self.left_image_show_lock.acquire() + left_image_to_be_shown_updated = self.left_image_to_be_shown_updated + left_image_to_be_shown = self.left_image_to_be_shown + self.left_image_show_lock.release() + + image = left_image_to_be_shown + payload = {'image': '', 'shape': ''} + + if not left_image_to_be_shown_updated: + return payload + + shape = image.shape + frame = cv2.imencode('.JPEG', image)[1] + encoded_image = base64.b64encode(frame) + + payload['image'] = encoded_image.decode('utf-8') + payload['shape'] = shape + + self.left_image_show_lock.acquire() + self.left_image_to_be_shown_updated = False + self.left_image_show_lock.release() + + return payload + + # Function for student to call + def showImage(self, image): + self.image_show_lock.acquire() + self.image_to_be_shown = image + self.image_to_be_shown_updated = True + self.image_show_lock.release() + + # Function for student to call + def showLeftImage(self, image): + self.left_image_show_lock.acquire() + self.left_image_to_be_shown = image + self.left_image_to_be_shown_updated = True + self.left_image_show_lock.release() + + # Function to get the client + # Called when a new client is received + def get_client(self, client, server): + self.client = client + + # Function to get value of Acknowledge + def get_acknowledge(self): + self.acknowledge_lock.acquire() + acknowledge = self.acknowledge + self.acknowledge_lock.release() + + return acknowledge + + # Function to get value of Acknowledge + def set_acknowledge(self, value): + self.acknowledge_lock.acquire() + self.acknowledge = value + self.acknowledge_lock.release() + + # Update the gui + def update_gui(self): + # Payload Image Message + payload = self.payloadImage() + self.payload["image"] = json.dumps(payload) + + message = "#gui" + json.dumps(self.payload) + self.server.send_message(self.client, message) + + # Payload Left Image Message + left_payload = self.payloadLeftImage() + self.left_payload["image"] = json.dumps(left_payload) + + message = "#gul" + json.dumps(self.left_payload) + self.server.send_message(self.client, message) + + # Function to read the message from websocket + # Gets called when there is an incoming message from the client + def get_message(self, client, server, message): + # Acknowledge Message for GUI Thread + if message[:4] == "#ack": + self.set_acknowledge(True) + + # Activate the server + def run_server(self): + self.server = WebsocketServer(port=2303, host=self.host) + self.server.set_fn_new_client(self.get_client) + self.server.set_fn_message_received(self.get_message) + + logged = False + while not logged: + try: + f = open("/ws_gui.log", "w") + f.write("websocket_gui=ready") + f.close() + logged = True + except: + time.sleep(0.1) + + self.server.run_forever() + + # Function to reset + def reset_gui(self): + pass + + +# This class decouples the user thread +# and the GUI update thread +class ThreadGUI: + def __init__(self, gui): + self.gui = gui + + # Time variables + self.ideal_cycle = 80 + self.measured_cycle = 80 + self.iteration_counter = 0 + + # Function to start the execution of threads + def start(self): + self.measure_thread = threading.Thread(target=self.measure_thread) + self.thread = threading.Thread(target=self.run) + + self.measure_thread.start() + self.thread.start() + + print("GUI Thread Started!") + + # The measuring thread to measure frequency + def measure_thread(self): + while self.gui.client is None: + pass + + previous_time = datetime.now() + while True: + # Sleep for 2 seconds + time.sleep(2) + + # Measure the current time and subtract from previous time to get real time interval + current_time = datetime.now() + dt = current_time - previous_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + previous_time = current_time + + # Get the time period + try: + # Division by zero + self.measured_cycle = ms / self.iteration_counter + except: + self.measured_cycle = 0 + + # Reset the counter + self.iteration_counter = 0 + + # The main thread of execution + def run(self): + while self.gui.client is None: + pass + + while True: + start_time = datetime.now() + self.gui.update_gui() + acknowledge_message = self.gui.get_acknowledge() + + while not acknowledge_message: + acknowledge_message = self.gui.get_acknowledge() + + self.gui.set_acknowledge(False) + + finish_time = datetime.now() + self.iteration_counter = self.iteration_counter + 1 + + dt = finish_time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + if ms < self.ideal_cycle: + time.sleep((self.ideal_cycle-ms) / 1000.0) diff --git a/exercises/static/exercises/drone_gymkhana/web-template/hal.py b/exercises/static/exercises/drone_gymkhana/web-template/hal.py new file mode 100644 index 000000000..25635a119 --- /dev/null +++ b/exercises/static/exercises/drone_gymkhana/web-template/hal.py @@ -0,0 +1,82 @@ +import numpy as np +import rospy +import cv2 + +from drone_wrapper import DroneWrapper + + +# Hardware Abstraction Layer +class HAL: + IMG_WIDTH = 320 + IMG_HEIGHT = 240 + + def __init__(self): + rospy.init_node("HAL") + + self.image = None + self.drone = DroneWrapper(name="rqt") + + # Explicit initialization functions + # Class method, so user can call it without instantiation + @classmethod + def initRobot(cls): + new_instance = cls() + return new_instance + + # Get Image from ROS Driver Camera + def get_frontal_image(self): + image = self.drone.get_frontal_image() + image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) + return image_rgb + + def get_ventral_image(self): + image = self.drone.get_ventral_image() + image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) + return image_rgb + + def get_position(self): + pos = self.drone.get_position() + return pos + + def get_velocity(self): + vel = self.drone.get_velocity() + return vel + + def get_yaw_rate(self): + yaw_rate = self.drone.get_yaw_rate() + return yaw_rate + + def get_orientation(self): + orientation = self.drone.get_orientation() + return orientation + + def get_roll(self): + roll = self.drone.get_roll() + return roll + + def get_pitch(self): + pitch = self.drone.get_pitch() + return pitch + + def get_yaw(self): + yaw = self.drone.get_yaw() + return yaw + + def get_landed_state(self): + state = self.drone.get_landed_state() + return state + + def set_cmd_pos(self, x, y, z, az): + self.drone.set_cmd_pos(x, y, z, az) + + def set_cmd_vel(self, vx, vy, vz, az): + self.drone.set_cmd_vel(vx, vy, vz, az) + + def set_cmd_mix(self, vx, vy, z, az): + self.drone.set_cmd_mix(vx, vy, z, az) + + def takeoff(self, h=3): + self.drone.takeoff(h) + + def land(self): + self.drone.land() diff --git a/exercises/static/exercises/drone_gymkhana/web-template/interfaces/__init__.py b/exercises/static/exercises/drone_gymkhana/web-template/interfaces/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/exercises/static/exercises/drone_gymkhana/web-template/interfaces/camera.py b/exercises/static/exercises/drone_gymkhana/web-template/interfaces/camera.py new file mode 100644 index 000000000..5a021a13e --- /dev/null +++ b/exercises/static/exercises/drone_gymkhana/web-template/interfaces/camera.py @@ -0,0 +1,89 @@ +import rospy +from sensor_msgs.msg import Image as ImageROS +import threading +from math import pi as PI +import cv2 +from cv_bridge import CvBridge, CvBridgeError + + +MAXRANGE = 8 # max length received from imageD +MINRANGE = 0 + + +def imageMsg2Image(img, bridge): + + image = Image() + + image.width = img.width + image.height = img.height + image.format = "BGR8" + image.timeStamp = img.header.stamp.secs + (img.header.stamp.nsecs * 1e-9) + cv_image = 0 + if img.encoding[-2:] == "C1": + gray_img_buff = bridge.imgmsg_to_cv2(img, img.encoding) + cv_image = depthToRGB8(gray_img_buff, img.encoding) + else: + cv_image = bridge.imgmsg_to_cv2(img, "bgr8") + image.data = cv_image + return image + + +import numpy as np + + +class Image: + + def __init__(self): + + self.height = 3 # Image height [pixels] + self.width = 3 # Image width [pixels] + self.timeStamp = 0 # Time stamp [s] */ + self.format = "" # Image format string (RGB8, BGR,...) + self.data = np.zeros((self.height, self.width, 3), np.uint8) # The image data itself + self.data.shape = self.height, self.width, 3 + + def __str__(self): + s = "Image: {\n height: " + str(self.height) + "\n width: " + str(self.width) + s = s + "\n format: " + self.format + "\n timeStamp: " + str(self.timeStamp) + s = s + "\n data: " + str(self.data) + "\n}" + return s + + +class ListenerCamera: + + def __init__(self, topic): + + self.topic = topic + self.data = Image() + self.sub = None + self.lock = threading.Lock() + + self.bridge = CvBridge() + self.start() + + def __callback(self, img): + + image = imageMsg2Image(img, self.bridge) + + self.lock.acquire() + self.data = image + self.lock.release() + + def stop(self): + + self.sub.unregister() + + def start(self): + self.sub = rospy.Subscriber(self.topic, ImageROS, self.__callback) + + def getImage(self): + + self.lock.acquire() + image = self.data + self.lock.release() + + return image + + def hasproxy(self): + + return hasattr(self, "sub") and self.sub diff --git a/exercises/static/exercises/drone_gymkhana/web-template/interfaces/motors.py b/exercises/static/exercises/drone_gymkhana/web-template/interfaces/motors.py new file mode 100644 index 000000000..70dca8a46 --- /dev/null +++ b/exercises/static/exercises/drone_gymkhana/web-template/interfaces/motors.py @@ -0,0 +1,123 @@ +import rospy +from geometry_msgs.msg import Twist +import threading +from math import pi as PI +from .threadPublisher import ThreadPublisher + + + +def cmdvel2Twist(vel): + + tw = Twist() + tw.linear.x = vel.vx + tw.linear.y = vel.vy + tw.linear.z = vel.vz + tw.angular.x = vel.ax + tw.angular.y = vel.ay + tw.angular.z = vel.az + + return tw + + +class CMDVel (): + + def __init__(self): + + self.vx = 0 # vel in x[m/s] (use this for V in wheeled robots) + self.vy = 0 # vel in y[m/s] + self.vz = 0 # vel in z[m/s] + self.ax = 0 # angular vel in X axis [rad/s] + self.ay = 0 # angular vel in X axis [rad/s] + self.az = 0 # angular vel in Z axis [rad/s] (use this for W in wheeled robots) + self.timeStamp = 0 # Time stamp [s] + + + def __str__(self): + s = "CMDVel: {\n vx: " + str(self.vx) + "\n vy: " + str(self.vy) + s = s + "\n vz: " + str(self.vz) + "\n ax: " + str(self.ax) + s = s + "\n ay: " + str(self.ay) + "\n az: " + str(self.az) + s = s + "\n timeStamp: " + str(self.timeStamp) + "\n}" + return s + +class PublisherMotors: + + def __init__(self, topic, maxV, maxW): + + self.maxW = maxW + self.maxV = maxV + + self.topic = topic + self.data = CMDVel() + self.pub = rospy.Publisher(self.topic, Twist, queue_size=1) + + self.lock = threading.Lock() + + self.kill_event = threading.Event() + self.thread = ThreadPublisher(self, self.kill_event) + + self.thread.daemon = True + self.start() + + def publish (self): + + self.lock.acquire() + tw = cmdvel2Twist(self.data) + self.lock.release() + self.pub.publish(tw) + + def stop(self): + + self.kill_event.set() + self.pub.unregister() + + def start (self): + + self.kill_event.clear() + self.thread.start() + + + + def getMaxW(self): + return self.maxW + + def getMaxV(self): + return self.maxV + + + def sendVelocities(self, vel): + + self.lock.acquire() + self.data = vel + self.lock.release() + + def sendV(self, v): + + self.sendVX(v) + + def sendL(self, l): + + self.sendVY(l) + + def sendW(self, w): + + self.sendAZ(w) + + def sendVX(self, vx): + + self.lock.acquire() + self.data.vx = vx + self.lock.release() + + def sendVY(self, vy): + + self.lock.acquire() + self.data.vy = vy + self.lock.release() + + def sendAZ(self, az): + + self.lock.acquire() + self.data.az = az + self.lock.release() + + diff --git a/exercises/static/exercises/drone_gymkhana/web-template/interfaces/pose3d.py b/exercises/static/exercises/drone_gymkhana/web-template/interfaces/pose3d.py new file mode 100644 index 000000000..fd0bfc37a --- /dev/null +++ b/exercises/static/exercises/drone_gymkhana/web-template/interfaces/pose3d.py @@ -0,0 +1,176 @@ +import rospy +import threading +from math import asin, atan2, pi +from nav_msgs.msg import Odometry + +def quat2Yaw(qw, qx, qy, qz): + ''' + Translates from Quaternion to Yaw. + + @param qw,qx,qy,qz: Quaternion values + + @type qw,qx,qy,qz: float + + @return Yaw value translated from Quaternion + + ''' + rotateZa0=2.0*(qx*qy + qw*qz) + rotateZa1=qw*qw + qx*qx - qy*qy - qz*qz + rotateZ=0.0 + if(rotateZa0 != 0.0 and rotateZa1 != 0.0): + rotateZ=atan2(rotateZa0,rotateZa1) + return rotateZ + +def quat2Pitch(qw, qx, qy, qz): + ''' + Translates from Quaternion to Pitch. + + @param qw,qx,qy,qz: Quaternion values + + @type qw,qx,qy,qz: float + + @return Pitch value translated from Quaternion + + ''' + + rotateYa0=-2.0*(qx*qz - qw*qy) + rotateY=0.0 + if(rotateYa0 >= 1.0): + rotateY = pi/2.0 + elif(rotateYa0 <= -1.0): + rotateY = -pi/2.0 + else: + rotateY = asin(rotateYa0) + + return rotateY + +def quat2Roll (qw, qx, qy, qz): + ''' + Translates from Quaternion to Roll. + + @param qw,qx,qy,qz: Quaternion values + + @type qw,qx,qy,qz: float + + @return Roll value translated from Quaternion + + ''' + rotateXa0=2.0*(qy*qz + qw*qx) + rotateXa1=qw*qw - qx*qx - qy*qy + qz*qz + rotateX=0.0 + + if(rotateXa0 != 0.0 and rotateXa1 != 0.0): + rotateX=atan2(rotateXa0, rotateXa1) + return rotateX + + +def odometry2Pose3D(odom): + ''' + Translates from ROS Odometry to JderobotTypes Pose3d. + + @param odom: ROS Odometry to translate + + @type odom: Odometry + + @return a Pose3d translated from odom + + ''' + pose = Pose3d() + ori = odom.pose.pose.orientation + + pose.x = odom.pose.pose.position.x + pose.y = odom.pose.pose.position.y + pose.z = odom.pose.pose.position.z + #pose.h = odom.pose.pose.position.h + pose.yaw = quat2Yaw(ori.w, ori.x, ori.y, ori.z) + pose.pitch = quat2Pitch(ori.w, ori.x, ori.y, ori.z) + pose.roll = quat2Roll(ori.w, ori.x, ori.y, ori.z) + pose.q = [ori.w, ori.x, ori.y, ori.z] + pose.timeStamp = odom.header.stamp.secs + (odom.header.stamp.nsecs *1e-9) + + return pose + +class Pose3d (): + + def __init__(self): + + self.x = 0 # X coord [meters] + self.y = 0 # Y coord [meters] + self.z = 0 # Z coord [meters] + self.h = 1 # H param + self.yaw = 0 #Yaw angle[rads] + self.pitch = 0 # Pitch angle[rads] + self.roll = 0 # Roll angle[rads] + self.q = [0,0,0,0] # Quaternion + self.timeStamp = 0 # Time stamp [s] + + + def __str__(self): + s = "Pose3D: {\n x: " + str(self.x) + "\n Y: " + str(self.y) + s = s + "\n Z: " + str(self.z) + "\n H: " + str(self.h) + s = s + "\n Yaw: " + str(self.yaw) + "\n Pitch: " + str(self.pitch) + "\n Roll: " + str(self.roll) + s = s + "\n quaternion: " + str(self.q) + "\n timeStamp: " + str(self.timeStamp) + "\n}" + return s + + +class ListenerPose3d: + ''' + ROS Pose3D Subscriber. Pose3D Client to Receive pose3d from ROS nodes. + ''' + def __init__(self, topic): + ''' + ListenerPose3d Constructor. + + @param topic: ROS topic to subscribe + + @type topic: String + + ''' + self.topic = topic + self.data = Pose3d() + self.sub = None + self.lock = threading.Lock() + self.start() + + def __callback (self, odom): + ''' + Callback function to receive and save Pose3d. + + @param odom: ROS Odometry received + + @type odom: Odometry + + ''' + pose = odometry2Pose3D(odom) + + self.lock.acquire() + self.data = pose + self.lock.release() + + def stop(self): + ''' + Stops (Unregisters) the client. + + ''' + self.sub.unregister() + + def start (self): + ''' + Starts (Subscribes) the client. + + ''' + self.sub = rospy.Subscriber(self.topic, Odometry, self.__callback) + + def getPose3d(self): + ''' + Returns last Pose3d. + + @return last JdeRobotTypes Pose3d saved + + ''' + self.lock.acquire() + pose = self.data + self.lock.release() + + return pose + diff --git a/exercises/static/exercises/drone_gymkhana/web-template/interfaces/threadPublisher.py b/exercises/static/exercises/drone_gymkhana/web-template/interfaces/threadPublisher.py new file mode 100644 index 000000000..69aa0ad48 --- /dev/null +++ b/exercises/static/exercises/drone_gymkhana/web-template/interfaces/threadPublisher.py @@ -0,0 +1,46 @@ +# +# Copyright (C) 1997-2016 JDE Developers Team +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. +# Authors : +# Alberto Martin Florido +# Aitor Martinez Fernandez +# +import threading +import time +from datetime import datetime + +time_cycle = 80 + + +class ThreadPublisher(threading.Thread): + + def __init__(self, pub, kill_event): + self.pub = pub + self.kill_event = kill_event + threading.Thread.__init__(self, args=kill_event) + + def run(self): + while (not self.kill_event.is_set()): + start_time = datetime.now() + + self.pub.publish() + + finish_Time = datetime.now() + + dt = finish_Time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + #print (ms) + if (ms < time_cycle): + time.sleep((time_cycle - ms) / 1000.0) \ No newline at end of file diff --git a/exercises/static/exercises/drone_gymkhana/web-template/interfaces/threadStoppable.py b/exercises/static/exercises/drone_gymkhana/web-template/interfaces/threadStoppable.py new file mode 100644 index 000000000..b631d180f --- /dev/null +++ b/exercises/static/exercises/drone_gymkhana/web-template/interfaces/threadStoppable.py @@ -0,0 +1,36 @@ +import threading +import time +from datetime import datetime + +time_cycle = 80 + + +class StoppableThread(threading.Thread): + """Thread class with a stop() method. The thread itself has to check + regularly for the stopped() condition.""" + + def __init__(self, target, kill_event=threading.Event(), *args, **kwargs): + super(StoppableThread, self).__init__(*args, **kwargs) + self._target = target + self._target_args = kwargs["args"] + self._kill_event = kill_event + + def run(self): + while not self.stopped(): + start_time = datetime.now() + + self._target(*self._target_args) + + finish_time = datetime.now() + + dt = finish_time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + # print (ms) + if ms < time_cycle: + time.sleep((time_cycle - ms) / 1000.0) + + def stop(self): + self._kill_event.set() + + def stopped(self): + return self._kill_event.is_set() diff --git a/exercises/static/exercises/drone_gymkhana/web-template/launch/gazebo.launch b/exercises/static/exercises/drone_gymkhana/web-template/launch/gazebo.launch new file mode 100644 index 000000000..ff2ad5960 --- /dev/null +++ b/exercises/static/exercises/drone_gymkhana/web-template/launch/gazebo.launch @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/exercises/static/exercises/drone_gymkhana/web-template/launch/launch.py b/exercises/static/exercises/drone_gymkhana/web-template/launch/launch.py new file mode 100644 index 000000000..49f2f0e6b --- /dev/null +++ b/exercises/static/exercises/drone_gymkhana/web-template/launch/launch.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python3 + +import stat +import rospy +from os import lstat +from subprocess import Popen, PIPE + + +DRI_PATH = "/dev/dri/card0" +EXERCISE = "drone_gymkhana" +TIMEOUT = 30 +MAX_ATTEMPT = 2 + + +# Check if acceleration can be enabled +def check_device(device_path): + try: + return stat.S_ISCHR(lstat(device_path)[stat.ST_MODE]) + except: + return False + + +# Spawn new process +def spawn_process(args, insert_vglrun=False): + if insert_vglrun: + args.insert(0, "vglrun") + process = Popen(args, stdout=PIPE, bufsize=1, universal_newlines=True) + return process + + +class Test(): + def gazebo(self): + rospy.logwarn("[GAZEBO] Launching") + try: + rospy.wait_for_service("/gazebo/get_model_properties", TIMEOUT) + return True + except rospy.ROSException: + return False + + def px4(self): + rospy.logwarn("[PX4-SITL] Launching") + start_time = rospy.get_time() + args = ["./PX4-Autopilot/build/px4_sitl_default/bin/px4-commander","--instance", "0", "check"] + while rospy.get_time() - start_time < TIMEOUT: + process = spawn_process(args, insert_vglrun=False) + with process.stdout: + for line in iter(process.stdout.readline, ''): + if ("Prearm check: OK" in line): + return True + rospy.sleep(2) + return False + + def mavros(self, ns=""): + rospy.logwarn("[MAVROS] Launching") + try: + rospy.wait_for_service(ns + "/mavros/cmd/arming", TIMEOUT) + return True + except rospy.ROSException: + return False + + +class Launch(): + def __init__(self): + self.test = Test() + self.acceleration_enabled = check_device(DRI_PATH) + + # Start roscore + args = ["/opt/ros/noetic/bin/roscore"] + spawn_process(args, insert_vglrun=False) + + rospy.init_node("launch", anonymous=True) + + def start(self): + ######## LAUNCH GAZEBO ######## + args = ["/opt/ros/noetic/bin/roslaunch", + "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/gazebo.launch", + "--wait", + "--log" + ] + + attempt = 1 + while True: + spawn_process(args, insert_vglrun=self.acceleration_enabled) + if self.test.gazebo() == True: + break + if attempt == MAX_ATTEMPT: + rospy.logerr("[GAZEBO] Launch Failed") + return + attempt = attempt + 1 + + + ######## LAUNCH PX4 ######## + args = ["/opt/ros/noetic/bin/roslaunch", + "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/px4.launch", + "--log" + ] + + attempt = 1 + while True: + spawn_process(args, insert_vglrun=self.acceleration_enabled) + if self.test.px4() == True: + break + if attempt == MAX_ATTEMPT: + rospy.logerr("[PX4] Launch Failed") + return + attempt = attempt + 1 + + + ######## LAUNCH MAVROS ######## + args = ["/opt/ros/noetic/bin/roslaunch", + "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/mavros.launch", + "--log" + ] + + attempt = 1 + while True: + spawn_process(args, insert_vglrun=self.acceleration_enabled) + if self.test.mavros() == True: + break + if attempt == MAX_ATTEMPT: + rospy.logerr("[MAVROS] Launch Failed") + return + attempt = attempt + 1 + + +if __name__ == "__main__": + launch = Launch() + launch.start() + + with open("/drones_launch.log", "w") as f: + f.write("success") diff --git a/exercises/static/exercises/drone_gymkhana/web-template/launch/mavros.launch b/exercises/static/exercises/drone_gymkhana/web-template/launch/mavros.launch new file mode 100644 index 000000000..b899c0ec1 --- /dev/null +++ b/exercises/static/exercises/drone_gymkhana/web-template/launch/mavros.launch @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/exercises/static/exercises/drone_gymkhana/web-template/launch/px4.launch b/exercises/static/exercises/drone_gymkhana/web-template/launch/px4.launch new file mode 100644 index 000000000..59f3108b3 --- /dev/null +++ b/exercises/static/exercises/drone_gymkhana/web-template/launch/px4.launch @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/exercises/static/exercises/drone_hangar/README.md b/exercises/static/exercises/drone_hangar/README.md new file mode 100644 index 000000000..fcb5719a6 --- /dev/null +++ b/exercises/static/exercises/drone_hangar/README.md @@ -0,0 +1 @@ +[Exercise Documentation Website](https://jderobot.github.io/RoboticsAcademy/exercises/Drones/drone_hangar) diff --git a/exercises/static/exercises/drone_hangar/drone_hangar.launch b/exercises/static/exercises/drone_hangar/drone_hangar.launch new file mode 100644 index 000000000..ae214c8f4 --- /dev/null +++ b/exercises/static/exercises/drone_hangar/drone_hangar.launch @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/exercises/static/exercises/drone_hangar/drone_hangar.world b/exercises/static/exercises/drone_hangar/drone_hangar.world new file mode 100644 index 000000000..42bc0c59a --- /dev/null +++ b/exercises/static/exercises/drone_hangar/drone_hangar.world @@ -0,0 +1,185 @@ + + + + + model://hangar + 0 -2 0 0 0 0 + + + + model://wall1 + 0 5 0 0 0 0 + + + + model://wall2 + 0 0 0 0 0 0 + + + + model://wall3 + 0 -6 0 0 0 0 + + + + model://wall4 + 0 -6 0 0 0 0 + + + + model://sun + + + + model://grass_plane + + + + 7.53258 -10.5204 1 0 -0 0 + 0.5 0.5 0.5 1 + 0.1 0.1 0.1 1 + + 37 + 1 + 0.05 + 0.001 + + 0 + 0 0 -1 + + + + -13.5592 -18.4336 10 0 -0 0 + 0.498039 0.498039 0.498039 1 + 0 0 0 1 + 0 0 -1 + + 37 + 1 + 0.05 + 0.001 + + 1 + + + + -11.0322 15.4092 1 0 -0 0 + 0.5 0.5 0.5 1 + 0.1 0.1 0.1 1 + + 20 + 0.5 + 0.01 + 0.001 + + 0 + 0 0 -1 + + + + 7.67436 15.2643 1 0 -0 0 + 0.5 0.5 0.5 1 + 0.1 0.1 0.1 1 + + 20 + 0.5 + 0.01 + 0.001 + + 0 + 0 0 -1 + + + + -13.7195 0.042174 1 0 -0 0 + 0.5 0.5 0.5 1 + 0.1 0.1 0.1 1 + + 20 + 0.5 + 0.01 + 0.001 + + 0 + 0 0 -1 + + + + 12.95 2.24523 1 0 -0 0 + 0.5 0.5 0.5 1 + 0.1 0.1 0.1 1 + + 20 + 0.5 + 0.01 + 0.001 + + 0 + 0 0 -1 + + + + + 0 0 10 0 -0 0 + + + 10 -30 10 0 -0 0 + + + -10 -30 10 0 -0 0 + + + -11.0322 15.4092 1 0 -0 0 + + + 7.67436 15.2643 1 0 -0 0 + + + -13.7195 0.042174 1 0 -0 0 + + + 12.95 2.24523 1 0 -0 0 + + + + + + -10.5394 -103.514 31.8675 0 0.275642 1.23938 + orbit + perspective + + + + + + + 12 + + + + + + + 0 0 -9.8066 + + + quick + 10 + 1.3 + 0 + + + 0 + 0.2 + 100 + 0.001 + + + 0.004 + 1 + 250 + 6.0e-6 2.3e-5 -4.2e-5 + + + + \ No newline at end of file diff --git a/exercises/static/exercises/drone_hangar/my_solution.py b/exercises/static/exercises/drone_hangar/my_solution.py new file mode 100755 index 000000000..05d74d9d1 --- /dev/null +++ b/exercises/static/exercises/drone_hangar/my_solution.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python + +import rospy +import numpy as np +import cv2 +from drone_wrapper import DroneWrapper +from std_msgs.msg import Bool, Float64 +from sensor_msgs.msg import Image +from geometry_msgs.msg import Twist, Pose + +code_live_flag = False + +def gui_play_stop_cb(msg): + global code_live_flag, code_live_timer + if msg.data == True: + if not code_live_flag: + code_live_flag = True + code_live_timer = rospy.Timer(rospy.Duration(nsecs=50000000), execute) + else: + if code_live_flag: + code_live_flag = False + code_live_timer.shutdown() + +def set_image_filtered(img): + gui_filtered_img_pub.publish(drone.bridge.cv2_to_imgmsg(img)) + +def set_image_threshed(img): + gui_threshed_img_pub.publish(drone.bridge.cv2_to_imgmsg(img)) + +def execute(event): + global drone + img_frontal = drone.get_frontal_image() + img_ventral = drone.get_ventral_image() + # Both the above images are cv2 images + ################# Insert your code here ################################# + + set_image_filtered(img_frontal) + set_image_threshed(img_ventral) + + ######################################################################### + +if __name__ == "__main__": + drone = DroneWrapper() + rospy.Subscriber('gui/play_stop', Bool, gui_play_stop_cb) + gui_filtered_img_pub = rospy.Publisher('interface/filtered_img', Image, queue_size = 1) + gui_threshed_img_pub = rospy.Publisher('interface/threshed_img', Image, queue_size = 1) + code_live_flag = False + code_live_timer = rospy.Timer(rospy.Duration(nsecs=50000000), execute) + code_live_timer.shutdown() + while not rospy.is_shutdown(): + rospy.spin() diff --git a/exercises/static/exercises/drone_hangar/web-template/RADI-launch b/exercises/static/exercises/drone_hangar/web-template/RADI-launch new file mode 100755 index 000000000..afde9c081 --- /dev/null +++ b/exercises/static/exercises/drone_hangar/web-template/RADI-launch @@ -0,0 +1,2 @@ +#!/bin/sh +docker run -it --rm -p 8000:8000 -p 2303:2303 -p 1905:1905 -p 8765:8765 -p 6080:6080 -p 1108:1108 jderobot/robotics-academy:3.1.2 ./start-3.1.sh diff --git a/exercises/static/exercises/drone_hangar/web-template/README.md b/exercises/static/exercises/drone_hangar/web-template/README.md new file mode 100644 index 000000000..fcb5719a6 --- /dev/null +++ b/exercises/static/exercises/drone_hangar/web-template/README.md @@ -0,0 +1 @@ +[Exercise Documentation Website](https://jderobot.github.io/RoboticsAcademy/exercises/Drones/drone_hangar) diff --git a/exercises/static/exercises/drone_hangar/web-template/code/academy.py b/exercises/static/exercises/drone_hangar/web-template/code/academy.py new file mode 100644 index 000000000..7a59ce7f5 --- /dev/null +++ b/exercises/static/exercises/drone_hangar/web-template/code/academy.py @@ -0,0 +1,8 @@ +# Enter sequential code! +from GUI import GUI +from HAL import HAL + +while True: + # Enter iterative code! + img = HAL.get_ventral_image() + GUI.showImage(img) \ No newline at end of file diff --git a/exercises/static/exercises/drone_hangar/web-template/console.py b/exercises/static/exercises/drone_hangar/web-template/console.py new file mode 100644 index 000000000..7b72a0913 --- /dev/null +++ b/exercises/static/exercises/drone_hangar/web-template/console.py @@ -0,0 +1,20 @@ +# Functions to start and close console +import os +import sys + + +def start_console(): + # Get all the file descriptors and choose the latest one + fds = os.listdir("/dev/pts/") + fds.sort() + console_fd = fds[-2] + + sys.stderr = open('/dev/pts/' + console_fd, 'w') + sys.stdout = open('/dev/pts/' + console_fd, 'w') + sys.stdin = open('/dev/pts/' + console_fd, 'w') + + +def close_console(): + sys.stderr.close() + sys.stdout.close() + sys.stdin.close() diff --git a/exercises/static/exercises/drone_hangar/web-template/drone_hangar.world b/exercises/static/exercises/drone_hangar/web-template/drone_hangar.world new file mode 100644 index 000000000..48ff09858 --- /dev/null +++ b/exercises/static/exercises/drone_hangar/web-template/drone_hangar.world @@ -0,0 +1,193 @@ + + + + + + + -10.5394 -103.514 31.8675 0 0.275642 1.23938 + orbit + perspective + + + + + model://hangar + 0 -2 0 0 0 0 + + + model://wall1 + 0 5 0 0 0 0 + + + model://wall2 + 0 0 0 0 0 0 + + + model://wall3 + 0 -6 0 0 0 0 + + + model://wall4 + 0 -6 0 0 0 0 + + + + + model://sun + + + + 7.53258 -10.5204 1 0 -0 0 + 0.5 0.5 0.5 1 + 0.1 0.1 0.1 1 + + 37 + 1 + 0.05 + 0.001 + + 0 + 0 0 -1 + + + + -13.5592 -18.4336 10 0 -0 0 + 0.498039 0.498039 0.498039 1 + 0 0 0 1 + 0 0 -1 + + 37 + 1 + 0.05 + 0.001 + + 1 + + + + -11.0322 15.4092 1 0 -0 0 + 0.5 0.5 0.5 1 + 0.1 0.1 0.1 1 + + 20 + 0.5 + 0.01 + 0.001 + + 0 + 0 0 -1 + + + + 7.67436 15.2643 1 0 -0 0 + 0.5 0.5 0.5 1 + 0.1 0.1 0.1 1 + + 20 + 0.5 + 0.01 + 0.001 + + 0 + 0 0 -1 + + + + -13.7195 0.042174 1 0 -0 0 + 0.5 0.5 0.5 1 + 0.1 0.1 0.1 1 + + 20 + 0.5 + 0.01 + 0.001 + + 0 + 0 0 -1 + + + + 12.95 2.24523 1 0 -0 0 + 0.5 0.5 0.5 1 + 0.1 0.1 0.1 1 + + 20 + 0.5 + 0.01 + 0.001 + + 0 + 0 0 -1 + + + + + 0 0 10 0 -0 0 + + + 10 -30 10 0 -0 0 + + + -10 -30 10 0 -0 0 + + + -11.0322 15.4092 1 0 -0 0 + + + 7.67436 15.2643 1 0 -0 0 + + + -13.7195 0.042174 1 0 -0 0 + + + 12.95 2.24523 1 0 -0 0 + + + + + + model://grass_plane + + + + + model://logoJdeRobot + -12.0.0 0.0 0.0 0.0 0.0 0.0 + + + + 0 + 0.4 0.4 0.4 1.0 + 0.7 0.7 0.7 1 + + + 12 + + + + + + + 0 0 -9.8066 + + + quick + 10 + 1.3 + 0 + + + 0 + 0.2 + 100 + 0.001 + + + 0.004 + 1 + 250 + 6.0e-6 2.3e-5 -4.2e-5 + + + + \ No newline at end of file diff --git a/exercises/static/exercises/drone_hangar/web-template/exercise.py b/exercises/static/exercises/drone_hangar/web-template/exercise.py new file mode 100644 index 000000000..71d5934a8 --- /dev/null +++ b/exercises/static/exercises/drone_hangar/web-template/exercise.py @@ -0,0 +1,362 @@ +#!/usr/bin/env python + +from __future__ import print_function + +from websocket_server import WebsocketServer +import time +import threading +import subprocess +import sys +from datetime import datetime +import re +import json +import importlib + +import rospy +from std_srvs.srv import Empty + +from gui import GUI, ThreadGUI +from hal import HAL +from console import start_console, close_console + + +class Template: + # Initialize class variables + # self.ideal_cycle to run an execution for at least 1 second + # self.process for the current running process + def __init__(self): + self.measure_thread = None + self.thread = None + self.reload = False + self.stop_brain = True + self.user_code = "" + + # Time variables + self.ideal_cycle = 80 + self.measured_cycle = 80 + self.iteration_counter = 0 + self.real_time_factor = 0 + self.frequency_message = {'brain': '', 'gui': '', 'rtf': ''} + + self.server = None + self.client = None + self.host = sys.argv[1] + + # Initialize the GUI, HAL and Console behind the scenes + self.hal = HAL() + self.gui = GUI(self.host) + + # Function to parse the code + # A few assumptions: + # 1. The user always passes sequential and iterative codes + # 2. Only a single infinite loop + def parse_code(self, source_code): + sequential_code, iterative_code = self.seperate_seq_iter(source_code) + return iterative_code, sequential_code + + # Function to separate the iterative and sequential code + def seperate_seq_iter(self, source_code): + if source_code == "": + return "", "" + + # Search for an instance of while True + infinite_loop = re.search(r'[^ ]while\s*\(\s*True\s*\)\s*:|[^ ]while\s*True\s*:|[^ ]while\s*1\s*:|[^ ]while\s*\(\s*1\s*\)\s*:', source_code) + + # Separate the content inside while True and the other + # (Separating the sequential and iterative part!) + try: + start_index = infinite_loop.start() + iterative_code = source_code[start_index:] + sequential_code = source_code[:start_index] + + # Remove while True: syntax from the code + # And remove the the 4 spaces indentation before each command + iterative_code = re.sub(r'[^ ]while\s*\(\s*True\s*\)\s*:|[^ ]while\s*True\s*:|[^ ]while\s*1\s*:|[^ ]while\s*\(\s*1\s*\)\s*:', '', iterative_code) + # Add newlines to match line on bug report + extra_lines = sequential_code.count('\n') + while (extra_lines >= 0): + iterative_code = '\n' + iterative_code + extra_lines -= 1 + iterative_code = re.sub(r'^[ ]{4}', '', iterative_code, flags=re.M) + + except: + sequential_code = source_code + iterative_code = "" + + return sequential_code, iterative_code + + # The process function + def process_code(self, source_code): + # Redirect the information to console + start_console() + + iterative_code, sequential_code = self.parse_code(source_code) + + # print(sequential_code) + # print(iterative_code) + + # The Python exec function + # Run the sequential part + gui_module, hal_module = self.generate_modules() + reference_environment = {"GUI": gui_module, "HAL": hal_module} + while (self.stop_brain == True): + if (self.reload == True): + return + time.sleep(0.1) + exec(sequential_code, reference_environment) + + # Run the iterative part inside template + # and keep the check for flag + while self.reload == False: + while (self.stop_brain == True): + if (self.reload == True): + return + time.sleep(0.1) + + start_time = datetime.now() + + # Execute the iterative portion + exec(iterative_code, reference_environment) + + # Template specifics to run! + finish_time = datetime.now() + dt = finish_time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + + # Keep updating the iteration counter + if (iterative_code == ""): + self.iteration_counter = 0 + else: + self.iteration_counter = self.iteration_counter + 1 + + # The code should be run for atleast the target time step + # If it's less put to sleep + if (ms < self.ideal_cycle): + time.sleep((self.ideal_cycle - ms) / 1000.0) + + close_console() + print("Current Thread Joined!") + + # Function to generate the modules for use in ACE Editor + def generate_modules(self): + # Define HAL module + hal_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) + hal_module.HAL = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) + # hal_module.drone = imp.new_module("drone") + # motors# hal_module.HAL.motors = imp.new_module("motors") + + # Add HAL functions + hal_module.HAL.get_frontal_image = self.hal.get_frontal_image + hal_module.HAL.get_ventral_image = self.hal.get_ventral_image + hal_module.HAL.get_position = self.hal.get_position + hal_module.HAL.get_velocity = self.hal.get_velocity + hal_module.HAL.get_yaw_rate = self.hal.get_yaw_rate + hal_module.HAL.get_orientation = self.hal.get_orientation + hal_module.HAL.get_roll = self.hal.get_roll + hal_module.HAL.get_pitch = self.hal.get_pitch + hal_module.HAL.get_yaw = self.hal.get_yaw + hal_module.HAL.get_landed_state = self.hal.get_landed_state + hal_module.HAL.set_cmd_pos = self.hal.set_cmd_pos + hal_module.HAL.set_cmd_vel = self.hal.set_cmd_vel + hal_module.HAL.set_cmd_mix = self.hal.set_cmd_mix + hal_module.HAL.takeoff = self.hal.takeoff + hal_module.HAL.land = self.hal.land + + # Define GUI module + gui_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) + gui_module.GUI = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) + + # Add GUI functions + gui_module.GUI.showImage = self.gui.showImage + gui_module.GUI.showLeftImage = self.gui.showLeftImage + + # Adding modules to system + # Protip: The names should be different from + # other modules, otherwise some errors + sys.modules["HAL"] = hal_module + sys.modules["GUI"] = gui_module + + return gui_module, hal_module + + # Function to measure the frequency of iterations + def measure_frequency(self): + previous_time = datetime.now() + # An infinite loop + while True: + # Sleep for 2 seconds + time.sleep(2) + + # Measure the current time and subtract from the previous time to get real time interval + current_time = datetime.now() + dt = current_time - previous_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + previous_time = current_time + + # Get the time period + try: + # Division by zero + self.measured_cycle = ms / self.iteration_counter + except: + self.measured_cycle = 0 + + # Reset the counter + self.iteration_counter = 0 + + # Send to client + self.send_frequency_message() + + # Function to generate and send frequency messages + def send_frequency_message(self): + # This function generates and sends frequency measures of the brain and gui + brain_frequency = 0; gui_frequency = 0 + try: + brain_frequency = round(1000 / self.measured_cycle, 1) + except ZeroDivisionError: + brain_frequency = 0 + + try: + gui_frequency = round(1000 / self.thread_gui.measured_cycle, 1) + except ZeroDivisionError: + gui_frequency = 0 + + self.frequency_message["brain"] = brain_frequency + self.frequency_message["gui"] = gui_frequency + self.frequency_message["rtf"] = self.real_time_factor + + message = "#freq" + json.dumps(self.frequency_message) + self.server.send_message(self.client, message) + + def send_ping_message(self): + self.server.send_message(self.client, "#ping") + + # Function to notify the front end that the code was received and sent to execution + def send_code_message(self): + self.server.send_message(self.client, "#exec") + + # Function to track the real time factor from Gazebo statistics + # https://stackoverflow.com/a/17698359 + # (For reference, Python3 solution specified in the same answer) + def track_stats(self): + args = ["gz", "stats", "-p"] + # Prints gz statistics. "-p": Output comma-separated values containing- + # real-time factor (percent), simtime (sec), realtime (sec), paused (T or F) + stats_process = subprocess.Popen(args, stdout=subprocess.PIPE, bufsize=1, universal_newlines=True) + # bufsize=1 enables line-bufferred mode (the input buffer is flushed + # automatically on newlines if you would write to process.stdin ) + with stats_process.stdout: + for line in iter(stats_process.stdout.readline, ''): + stats_list = [x.strip() for x in line.split(',')] + self.real_time_factor = stats_list[0] + + # Function to maintain thread execution + def execute_thread(self, source_code): + # Keep checking until the thread is alive + # The thread will die when the coming iteration reads the flag + if self.thread is not None: + while self.thread.is_alive(): + time.sleep(0.2) + + # Turn the flag down, the iteration has successfully stopped! + self.reload = False + # New thread execution + self.thread = threading.Thread(target=self.process_code, args=[source_code]) + self.thread.start() + self.send_code_message() + print("New Thread Started!") + + # Function to read and set frequency from incoming message + def read_frequency_message(self, message): + frequency_message = json.loads(message) + + # Set brain frequency + frequency = float(frequency_message["brain"]) + self.ideal_cycle = 1000.0 / frequency + + # Set gui frequency + frequency = float(frequency_message["gui"]) + self.thread_gui.ideal_cycle = 1000.0 / frequency + + return + + # The websocket function + # Gets called when there is an incoming message from the client + def handle(self, client, server, message): + if message[:5] == "#freq": + frequency_message = message[5:] + self.read_frequency_message(frequency_message) + time.sleep(1) + return + + elif(message[:5] == "#ping"): + time.sleep(1) + self.send_ping_message() + return + + elif (message[:5] == "#code"): + try: + # Once received turn the reload flag up and send it to execute_thread function + self.user_code = message[6:] + # print(repr(code)) + self.reload = True + self.execute_thread(self.user_code) + except: + pass + + elif (message[:5] == "#rest"): + try: + self.reload = True + self.stop_brain = True + self.execute_thread(self.user_code) + except: + pass + + elif (message[:5] == "#stop"): + self.stop_brain = True + + elif (message[:5] == "#play"): + self.stop_brain = False + + # Function that gets called when the server is connected + def connected(self, client, server): + self.client = client + # Start the GUI update thread + self.thread_gui = ThreadGUI(self.gui) + self.thread_gui.start() + + # Start the real time factor tracker thread + self.stats_thread = threading.Thread(target=self.track_stats) + self.stats_thread.start() + + # Start measure frequency + self.measure_thread = threading.Thread(target=self.measure_frequency) + self.measure_thread.start() + + print(client, 'connected') + + # Function that gets called when the connected closes + def handle_close(self, client, server): + print(client, 'closed') + + def run_server(self): + self.server = WebsocketServer(port=1905, host=self.host) + self.server.set_fn_new_client(self.connected) + self.server.set_fn_client_left(self.handle_close) + self.server.set_fn_message_received(self.handle) + + logged = False + while not logged: + try: + f = open("/ws_code.log", "w") + f.write("websocket_code=ready") + f.close() + logged = True + except: + time.sleep(0.1) + + self.server.run_forever() + + +# Execute! +if __name__ == "__main__": + server = Template() + server.run_server() \ No newline at end of file diff --git a/exercises/static/exercises/drone_hangar/web-template/gui.py b/exercises/static/exercises/drone_hangar/web-template/gui.py new file mode 100644 index 000000000..51d327f1a --- /dev/null +++ b/exercises/static/exercises/drone_hangar/web-template/gui.py @@ -0,0 +1,248 @@ +import json +import cv2 +import base64 +import threading +import time +from datetime import datetime +from websocket_server import WebsocketServer + + +# Graphical User Interface Class +class GUI: + # Initialization function + # The actual initialization + def __init__(self, host): + t = threading.Thread(target=self.run_server) + + self.payload = {'image': ''} + self.left_payload = {'image': ''} + self.server = None + self.client = None + + self.host = host + + # Image variables + self.image_to_be_shown = None + self.image_to_be_shown_updated = False + self.image_show_lock = threading.Lock() + + self.left_image_to_be_shown = None + self.left_image_to_be_shown_updated = False + self.left_image_show_lock = threading.Lock() + + self.acknowledge = False + self.acknowledge_lock = threading.Lock() + + # Take the console object to set the same websocket and client + t.start() + + # Explicit initialization function + # Class method, so user can call it without instantiation + @classmethod + def initGUI(cls, host): + # self.payload = {'image': '', 'shape': []} + new_instance = cls(host) + return new_instance + + # Function to prepare image payload + # Encodes the image as a JSON string and sends through the WS + def payloadImage(self): + self.image_show_lock.acquire() + image_to_be_shown_updated = self.image_to_be_shown_updated + image_to_be_shown = self.image_to_be_shown + self.image_show_lock.release() + + image = image_to_be_shown + payload = {'image': '', 'shape': ''} + + if not image_to_be_shown_updated: + return payload + + shape = image.shape + frame = cv2.imencode('.JPEG', image)[1] + encoded_image = base64.b64encode(frame) + + payload['image'] = encoded_image.decode('utf-8') + payload['shape'] = shape + + self.image_show_lock.acquire() + self.image_to_be_shown_updated = False + self.image_show_lock.release() + + return payload + + # Function to prepare image payload + # Encodes the image as a JSON string and sends through the WS + def payloadLeftImage(self): + self.left_image_show_lock.acquire() + left_image_to_be_shown_updated = self.left_image_to_be_shown_updated + left_image_to_be_shown = self.left_image_to_be_shown + self.left_image_show_lock.release() + + image = left_image_to_be_shown + payload = {'image': '', 'shape': ''} + + if not left_image_to_be_shown_updated: + return payload + + shape = image.shape + frame = cv2.imencode('.JPEG', image)[1] + encoded_image = base64.b64encode(frame) + + payload['image'] = encoded_image.decode('utf-8') + payload['shape'] = shape + + self.left_image_show_lock.acquire() + self.left_image_to_be_shown_updated = False + self.left_image_show_lock.release() + + return payload + + # Function for student to call + def showImage(self, image): + self.image_show_lock.acquire() + self.image_to_be_shown = image + self.image_to_be_shown_updated = True + self.image_show_lock.release() + + # Function for student to call + def showLeftImage(self, image): + self.left_image_show_lock.acquire() + self.left_image_to_be_shown = image + self.left_image_to_be_shown_updated = True + self.left_image_show_lock.release() + + # Function to get the client + # Called when a new client is received + def get_client(self, client, server): + self.client = client + + # Function to get value of Acknowledge + def get_acknowledge(self): + self.acknowledge_lock.acquire() + acknowledge = self.acknowledge + self.acknowledge_lock.release() + + return acknowledge + + # Function to get value of Acknowledge + def set_acknowledge(self, value): + self.acknowledge_lock.acquire() + self.acknowledge = value + self.acknowledge_lock.release() + + # Update the gui + def update_gui(self): + # Payload Image Message + payload = self.payloadImage() + self.payload["image"] = json.dumps(payload) + + message = "#gui" + json.dumps(self.payload) + self.server.send_message(self.client, message) + + # Payload Left Image Message + left_payload = self.payloadLeftImage() + self.left_payload["image"] = json.dumps(left_payload) + + message = "#gul" + json.dumps(self.left_payload) + self.server.send_message(self.client, message) + + # Function to read the message from websocket + # Gets called when there is an incoming message from the client + def get_message(self, client, server, message): + # Acknowledge Message for GUI Thread + if message[:4] == "#ack": + self.set_acknowledge(True) + + # Activate the server + def run_server(self): + self.server = WebsocketServer(port=2303, host=self.host) + self.server.set_fn_new_client(self.get_client) + self.server.set_fn_message_received(self.get_message) + + logged = False + while not logged: + try: + f = open("/ws_gui.log", "w") + f.write("websocket_gui=ready") + f.close() + logged = True + except: + time.sleep(0.1) + + self.server.run_forever() + + # Function to reset + def reset_gui(self): + pass + + +# This class decouples the user thread +# and the GUI update thread +class ThreadGUI: + def __init__(self, gui): + self.gui = gui + + # Time variables + self.ideal_cycle = 80 + self.measured_cycle = 80 + self.iteration_counter = 0 + + # Function to start the execution of threads + def start(self): + self.measure_thread = threading.Thread(target=self.measure_thread) + self.thread = threading.Thread(target=self.run) + + self.measure_thread.start() + self.thread.start() + + print("GUI Thread Started!") + + # The measuring thread to measure frequency + def measure_thread(self): + while self.gui.client is None: + pass + + previous_time = datetime.now() + while True: + # Sleep for 2 seconds + time.sleep(2) + + # Measure the current time and subtract from previous time to get real time interval + current_time = datetime.now() + dt = current_time - previous_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + previous_time = current_time + + # Get the time period + try: + # Division by zero + self.measured_cycle = ms / self.iteration_counter + except: + self.measured_cycle = 0 + + # Reset the counter + self.iteration_counter = 0 + + # The main thread of execution + def run(self): + while self.gui.client is None: + pass + + while True: + start_time = datetime.now() + self.gui.update_gui() + acknowledge_message = self.gui.get_acknowledge() + + while not acknowledge_message: + acknowledge_message = self.gui.get_acknowledge() + + self.gui.set_acknowledge(False) + + finish_time = datetime.now() + self.iteration_counter = self.iteration_counter + 1 + + dt = finish_time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + if ms < self.ideal_cycle: + time.sleep((self.ideal_cycle-ms) / 1000.0) diff --git a/exercises/static/exercises/drone_hangar/web-template/hal.py b/exercises/static/exercises/drone_hangar/web-template/hal.py new file mode 100644 index 000000000..25635a119 --- /dev/null +++ b/exercises/static/exercises/drone_hangar/web-template/hal.py @@ -0,0 +1,82 @@ +import numpy as np +import rospy +import cv2 + +from drone_wrapper import DroneWrapper + + +# Hardware Abstraction Layer +class HAL: + IMG_WIDTH = 320 + IMG_HEIGHT = 240 + + def __init__(self): + rospy.init_node("HAL") + + self.image = None + self.drone = DroneWrapper(name="rqt") + + # Explicit initialization functions + # Class method, so user can call it without instantiation + @classmethod + def initRobot(cls): + new_instance = cls() + return new_instance + + # Get Image from ROS Driver Camera + def get_frontal_image(self): + image = self.drone.get_frontal_image() + image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) + return image_rgb + + def get_ventral_image(self): + image = self.drone.get_ventral_image() + image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) + return image_rgb + + def get_position(self): + pos = self.drone.get_position() + return pos + + def get_velocity(self): + vel = self.drone.get_velocity() + return vel + + def get_yaw_rate(self): + yaw_rate = self.drone.get_yaw_rate() + return yaw_rate + + def get_orientation(self): + orientation = self.drone.get_orientation() + return orientation + + def get_roll(self): + roll = self.drone.get_roll() + return roll + + def get_pitch(self): + pitch = self.drone.get_pitch() + return pitch + + def get_yaw(self): + yaw = self.drone.get_yaw() + return yaw + + def get_landed_state(self): + state = self.drone.get_landed_state() + return state + + def set_cmd_pos(self, x, y, z, az): + self.drone.set_cmd_pos(x, y, z, az) + + def set_cmd_vel(self, vx, vy, vz, az): + self.drone.set_cmd_vel(vx, vy, vz, az) + + def set_cmd_mix(self, vx, vy, z, az): + self.drone.set_cmd_mix(vx, vy, z, az) + + def takeoff(self, h=3): + self.drone.takeoff(h) + + def land(self): + self.drone.land() diff --git a/exercises/static/exercises/drone_hangar/web-template/interfaces/__init__.py b/exercises/static/exercises/drone_hangar/web-template/interfaces/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/exercises/static/exercises/drone_hangar/web-template/interfaces/camera.py b/exercises/static/exercises/drone_hangar/web-template/interfaces/camera.py new file mode 100644 index 000000000..5a021a13e --- /dev/null +++ b/exercises/static/exercises/drone_hangar/web-template/interfaces/camera.py @@ -0,0 +1,89 @@ +import rospy +from sensor_msgs.msg import Image as ImageROS +import threading +from math import pi as PI +import cv2 +from cv_bridge import CvBridge, CvBridgeError + + +MAXRANGE = 8 # max length received from imageD +MINRANGE = 0 + + +def imageMsg2Image(img, bridge): + + image = Image() + + image.width = img.width + image.height = img.height + image.format = "BGR8" + image.timeStamp = img.header.stamp.secs + (img.header.stamp.nsecs * 1e-9) + cv_image = 0 + if img.encoding[-2:] == "C1": + gray_img_buff = bridge.imgmsg_to_cv2(img, img.encoding) + cv_image = depthToRGB8(gray_img_buff, img.encoding) + else: + cv_image = bridge.imgmsg_to_cv2(img, "bgr8") + image.data = cv_image + return image + + +import numpy as np + + +class Image: + + def __init__(self): + + self.height = 3 # Image height [pixels] + self.width = 3 # Image width [pixels] + self.timeStamp = 0 # Time stamp [s] */ + self.format = "" # Image format string (RGB8, BGR,...) + self.data = np.zeros((self.height, self.width, 3), np.uint8) # The image data itself + self.data.shape = self.height, self.width, 3 + + def __str__(self): + s = "Image: {\n height: " + str(self.height) + "\n width: " + str(self.width) + s = s + "\n format: " + self.format + "\n timeStamp: " + str(self.timeStamp) + s = s + "\n data: " + str(self.data) + "\n}" + return s + + +class ListenerCamera: + + def __init__(self, topic): + + self.topic = topic + self.data = Image() + self.sub = None + self.lock = threading.Lock() + + self.bridge = CvBridge() + self.start() + + def __callback(self, img): + + image = imageMsg2Image(img, self.bridge) + + self.lock.acquire() + self.data = image + self.lock.release() + + def stop(self): + + self.sub.unregister() + + def start(self): + self.sub = rospy.Subscriber(self.topic, ImageROS, self.__callback) + + def getImage(self): + + self.lock.acquire() + image = self.data + self.lock.release() + + return image + + def hasproxy(self): + + return hasattr(self, "sub") and self.sub diff --git a/exercises/static/exercises/drone_hangar/web-template/interfaces/motors.py b/exercises/static/exercises/drone_hangar/web-template/interfaces/motors.py new file mode 100644 index 000000000..70dca8a46 --- /dev/null +++ b/exercises/static/exercises/drone_hangar/web-template/interfaces/motors.py @@ -0,0 +1,123 @@ +import rospy +from geometry_msgs.msg import Twist +import threading +from math import pi as PI +from .threadPublisher import ThreadPublisher + + + +def cmdvel2Twist(vel): + + tw = Twist() + tw.linear.x = vel.vx + tw.linear.y = vel.vy + tw.linear.z = vel.vz + tw.angular.x = vel.ax + tw.angular.y = vel.ay + tw.angular.z = vel.az + + return tw + + +class CMDVel (): + + def __init__(self): + + self.vx = 0 # vel in x[m/s] (use this for V in wheeled robots) + self.vy = 0 # vel in y[m/s] + self.vz = 0 # vel in z[m/s] + self.ax = 0 # angular vel in X axis [rad/s] + self.ay = 0 # angular vel in X axis [rad/s] + self.az = 0 # angular vel in Z axis [rad/s] (use this for W in wheeled robots) + self.timeStamp = 0 # Time stamp [s] + + + def __str__(self): + s = "CMDVel: {\n vx: " + str(self.vx) + "\n vy: " + str(self.vy) + s = s + "\n vz: " + str(self.vz) + "\n ax: " + str(self.ax) + s = s + "\n ay: " + str(self.ay) + "\n az: " + str(self.az) + s = s + "\n timeStamp: " + str(self.timeStamp) + "\n}" + return s + +class PublisherMotors: + + def __init__(self, topic, maxV, maxW): + + self.maxW = maxW + self.maxV = maxV + + self.topic = topic + self.data = CMDVel() + self.pub = rospy.Publisher(self.topic, Twist, queue_size=1) + + self.lock = threading.Lock() + + self.kill_event = threading.Event() + self.thread = ThreadPublisher(self, self.kill_event) + + self.thread.daemon = True + self.start() + + def publish (self): + + self.lock.acquire() + tw = cmdvel2Twist(self.data) + self.lock.release() + self.pub.publish(tw) + + def stop(self): + + self.kill_event.set() + self.pub.unregister() + + def start (self): + + self.kill_event.clear() + self.thread.start() + + + + def getMaxW(self): + return self.maxW + + def getMaxV(self): + return self.maxV + + + def sendVelocities(self, vel): + + self.lock.acquire() + self.data = vel + self.lock.release() + + def sendV(self, v): + + self.sendVX(v) + + def sendL(self, l): + + self.sendVY(l) + + def sendW(self, w): + + self.sendAZ(w) + + def sendVX(self, vx): + + self.lock.acquire() + self.data.vx = vx + self.lock.release() + + def sendVY(self, vy): + + self.lock.acquire() + self.data.vy = vy + self.lock.release() + + def sendAZ(self, az): + + self.lock.acquire() + self.data.az = az + self.lock.release() + + diff --git a/exercises/static/exercises/drone_hangar/web-template/interfaces/pose3d.py b/exercises/static/exercises/drone_hangar/web-template/interfaces/pose3d.py new file mode 100644 index 000000000..fd0bfc37a --- /dev/null +++ b/exercises/static/exercises/drone_hangar/web-template/interfaces/pose3d.py @@ -0,0 +1,176 @@ +import rospy +import threading +from math import asin, atan2, pi +from nav_msgs.msg import Odometry + +def quat2Yaw(qw, qx, qy, qz): + ''' + Translates from Quaternion to Yaw. + + @param qw,qx,qy,qz: Quaternion values + + @type qw,qx,qy,qz: float + + @return Yaw value translated from Quaternion + + ''' + rotateZa0=2.0*(qx*qy + qw*qz) + rotateZa1=qw*qw + qx*qx - qy*qy - qz*qz + rotateZ=0.0 + if(rotateZa0 != 0.0 and rotateZa1 != 0.0): + rotateZ=atan2(rotateZa0,rotateZa1) + return rotateZ + +def quat2Pitch(qw, qx, qy, qz): + ''' + Translates from Quaternion to Pitch. + + @param qw,qx,qy,qz: Quaternion values + + @type qw,qx,qy,qz: float + + @return Pitch value translated from Quaternion + + ''' + + rotateYa0=-2.0*(qx*qz - qw*qy) + rotateY=0.0 + if(rotateYa0 >= 1.0): + rotateY = pi/2.0 + elif(rotateYa0 <= -1.0): + rotateY = -pi/2.0 + else: + rotateY = asin(rotateYa0) + + return rotateY + +def quat2Roll (qw, qx, qy, qz): + ''' + Translates from Quaternion to Roll. + + @param qw,qx,qy,qz: Quaternion values + + @type qw,qx,qy,qz: float + + @return Roll value translated from Quaternion + + ''' + rotateXa0=2.0*(qy*qz + qw*qx) + rotateXa1=qw*qw - qx*qx - qy*qy + qz*qz + rotateX=0.0 + + if(rotateXa0 != 0.0 and rotateXa1 != 0.0): + rotateX=atan2(rotateXa0, rotateXa1) + return rotateX + + +def odometry2Pose3D(odom): + ''' + Translates from ROS Odometry to JderobotTypes Pose3d. + + @param odom: ROS Odometry to translate + + @type odom: Odometry + + @return a Pose3d translated from odom + + ''' + pose = Pose3d() + ori = odom.pose.pose.orientation + + pose.x = odom.pose.pose.position.x + pose.y = odom.pose.pose.position.y + pose.z = odom.pose.pose.position.z + #pose.h = odom.pose.pose.position.h + pose.yaw = quat2Yaw(ori.w, ori.x, ori.y, ori.z) + pose.pitch = quat2Pitch(ori.w, ori.x, ori.y, ori.z) + pose.roll = quat2Roll(ori.w, ori.x, ori.y, ori.z) + pose.q = [ori.w, ori.x, ori.y, ori.z] + pose.timeStamp = odom.header.stamp.secs + (odom.header.stamp.nsecs *1e-9) + + return pose + +class Pose3d (): + + def __init__(self): + + self.x = 0 # X coord [meters] + self.y = 0 # Y coord [meters] + self.z = 0 # Z coord [meters] + self.h = 1 # H param + self.yaw = 0 #Yaw angle[rads] + self.pitch = 0 # Pitch angle[rads] + self.roll = 0 # Roll angle[rads] + self.q = [0,0,0,0] # Quaternion + self.timeStamp = 0 # Time stamp [s] + + + def __str__(self): + s = "Pose3D: {\n x: " + str(self.x) + "\n Y: " + str(self.y) + s = s + "\n Z: " + str(self.z) + "\n H: " + str(self.h) + s = s + "\n Yaw: " + str(self.yaw) + "\n Pitch: " + str(self.pitch) + "\n Roll: " + str(self.roll) + s = s + "\n quaternion: " + str(self.q) + "\n timeStamp: " + str(self.timeStamp) + "\n}" + return s + + +class ListenerPose3d: + ''' + ROS Pose3D Subscriber. Pose3D Client to Receive pose3d from ROS nodes. + ''' + def __init__(self, topic): + ''' + ListenerPose3d Constructor. + + @param topic: ROS topic to subscribe + + @type topic: String + + ''' + self.topic = topic + self.data = Pose3d() + self.sub = None + self.lock = threading.Lock() + self.start() + + def __callback (self, odom): + ''' + Callback function to receive and save Pose3d. + + @param odom: ROS Odometry received + + @type odom: Odometry + + ''' + pose = odometry2Pose3D(odom) + + self.lock.acquire() + self.data = pose + self.lock.release() + + def stop(self): + ''' + Stops (Unregisters) the client. + + ''' + self.sub.unregister() + + def start (self): + ''' + Starts (Subscribes) the client. + + ''' + self.sub = rospy.Subscriber(self.topic, Odometry, self.__callback) + + def getPose3d(self): + ''' + Returns last Pose3d. + + @return last JdeRobotTypes Pose3d saved + + ''' + self.lock.acquire() + pose = self.data + self.lock.release() + + return pose + diff --git a/exercises/static/exercises/drone_hangar/web-template/interfaces/threadPublisher.py b/exercises/static/exercises/drone_hangar/web-template/interfaces/threadPublisher.py new file mode 100644 index 000000000..69aa0ad48 --- /dev/null +++ b/exercises/static/exercises/drone_hangar/web-template/interfaces/threadPublisher.py @@ -0,0 +1,46 @@ +# +# Copyright (C) 1997-2016 JDE Developers Team +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. +# Authors : +# Alberto Martin Florido +# Aitor Martinez Fernandez +# +import threading +import time +from datetime import datetime + +time_cycle = 80 + + +class ThreadPublisher(threading.Thread): + + def __init__(self, pub, kill_event): + self.pub = pub + self.kill_event = kill_event + threading.Thread.__init__(self, args=kill_event) + + def run(self): + while (not self.kill_event.is_set()): + start_time = datetime.now() + + self.pub.publish() + + finish_Time = datetime.now() + + dt = finish_Time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + #print (ms) + if (ms < time_cycle): + time.sleep((time_cycle - ms) / 1000.0) \ No newline at end of file diff --git a/exercises/static/exercises/drone_hangar/web-template/interfaces/threadStoppable.py b/exercises/static/exercises/drone_hangar/web-template/interfaces/threadStoppable.py new file mode 100644 index 000000000..b631d180f --- /dev/null +++ b/exercises/static/exercises/drone_hangar/web-template/interfaces/threadStoppable.py @@ -0,0 +1,36 @@ +import threading +import time +from datetime import datetime + +time_cycle = 80 + + +class StoppableThread(threading.Thread): + """Thread class with a stop() method. The thread itself has to check + regularly for the stopped() condition.""" + + def __init__(self, target, kill_event=threading.Event(), *args, **kwargs): + super(StoppableThread, self).__init__(*args, **kwargs) + self._target = target + self._target_args = kwargs["args"] + self._kill_event = kill_event + + def run(self): + while not self.stopped(): + start_time = datetime.now() + + self._target(*self._target_args) + + finish_time = datetime.now() + + dt = finish_time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + # print (ms) + if ms < time_cycle: + time.sleep((time_cycle - ms) / 1000.0) + + def stop(self): + self._kill_event.set() + + def stopped(self): + return self._kill_event.is_set() diff --git a/exercises/static/exercises/drone_hangar/web-template/launch/gazebo.launch b/exercises/static/exercises/drone_hangar/web-template/launch/gazebo.launch new file mode 100644 index 000000000..94c0670e1 --- /dev/null +++ b/exercises/static/exercises/drone_hangar/web-template/launch/gazebo.launch @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/exercises/static/exercises/drone_hangar/web-template/launch/launch.py b/exercises/static/exercises/drone_hangar/web-template/launch/launch.py new file mode 100644 index 000000000..4fc8f94d8 --- /dev/null +++ b/exercises/static/exercises/drone_hangar/web-template/launch/launch.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python3 + +import stat +import rospy +from os import lstat +from subprocess import Popen, PIPE + + +DRI_PATH = "/dev/dri/card0" +EXERCISE = "drone_hangar" +TIMEOUT = 30 +MAX_ATTEMPT = 2 + + +# Check if acceleration can be enabled +def check_device(device_path): + try: + return stat.S_ISCHR(lstat(device_path)[stat.ST_MODE]) + except: + return False + + +# Spawn new process +def spawn_process(args, insert_vglrun=False): + if insert_vglrun: + args.insert(0, "vglrun") + process = Popen(args, stdout=PIPE, bufsize=1, universal_newlines=True) + return process + + +class Test(): + def gazebo(self): + rospy.logwarn("[GAZEBO] Launching") + try: + rospy.wait_for_service("/gazebo/get_model_properties", TIMEOUT) + return True + except rospy.ROSException: + return False + + def px4(self): + rospy.logwarn("[PX4-SITL] Launching") + start_time = rospy.get_time() + args = ["./PX4-Autopilot/build/px4_sitl_default/bin/px4-commander","--instance", "0", "check"] + while rospy.get_time() - start_time < TIMEOUT: + process = spawn_process(args, insert_vglrun=False) + with process.stdout: + for line in iter(process.stdout.readline, ''): + if ("Prearm check: OK" in line): + return True + rospy.sleep(2) + return False + + def mavros(self, ns=""): + rospy.logwarn("[MAVROS] Launching") + try: + rospy.wait_for_service(ns + "/mavros/cmd/arming", TIMEOUT) + return True + except rospy.ROSException: + return False + + +class Launch(): + def __init__(self): + self.test = Test() + self.acceleration_enabled = check_device(DRI_PATH) + + # Start roscore + args = ["/opt/ros/noetic/bin/roscore"] + spawn_process(args, insert_vglrun=False) + + rospy.init_node("launch", anonymous=True) + + def start(self): + ######## LAUNCH GAZEBO ######## + args = ["/opt/ros/noetic/bin/roslaunch", + "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/gazebo.launch", + "--wait", + "--log" + ] + + attempt = 1 + while True: + spawn_process(args, insert_vglrun=self.acceleration_enabled) + if self.test.gazebo() == True: + break + if attempt == MAX_ATTEMPT: + rospy.logerr("[GAZEBO] Launch Failed") + return + attempt = attempt + 1 + + + ######## LAUNCH PX4 ######## + args = ["/opt/ros/noetic/bin/roslaunch", + "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/px4.launch", + "--log" + ] + + attempt = 1 + while True: + spawn_process(args, insert_vglrun=self.acceleration_enabled) + if self.test.px4() == True: + break + if attempt == MAX_ATTEMPT: + rospy.logerr("[PX4] Launch Failed") + return + attempt = attempt + 1 + + + ######## LAUNCH MAVROS ######## + args = ["/opt/ros/noetic/bin/roslaunch", + "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/mavros.launch", + "--log" + ] + + attempt = 1 + while True: + spawn_process(args, insert_vglrun=self.acceleration_enabled) + if self.test.mavros() == True: + break + if attempt == MAX_ATTEMPT: + rospy.logerr("[MAVROS] Launch Failed") + return + attempt = attempt + 1 + + +if __name__ == "__main__": + launch = Launch() + launch.start() + + with open("/drones_launch.log", "w") as f: + f.write("success") diff --git a/exercises/static/exercises/drone_hangar/web-template/launch/mavros.launch b/exercises/static/exercises/drone_hangar/web-template/launch/mavros.launch new file mode 100644 index 000000000..b899c0ec1 --- /dev/null +++ b/exercises/static/exercises/drone_hangar/web-template/launch/mavros.launch @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/exercises/static/exercises/drone_hangar/web-template/launch/px4.launch b/exercises/static/exercises/drone_hangar/web-template/launch/px4.launch new file mode 100644 index 000000000..0de35df37 --- /dev/null +++ b/exercises/static/exercises/drone_hangar/web-template/launch/px4.launch @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/exercises/static/exercises/follow_turtlebot/README.md b/exercises/static/exercises/follow_turtlebot/README.md new file mode 100644 index 000000000..631a3d6c2 --- /dev/null +++ b/exercises/static/exercises/follow_turtlebot/README.md @@ -0,0 +1 @@ +[Exercise Documentation Website](https://jderobot.github.io/RoboticsAcademy/exercises/Drones/follow_turtlebot) diff --git a/exercises/static/exercises/follow_turtlebot/follow_turtlebot.launch b/exercises/static/exercises/follow_turtlebot/follow_turtlebot.launch new file mode 100644 index 000000000..4555f45f3 --- /dev/null +++ b/exercises/static/exercises/follow_turtlebot/follow_turtlebot.launch @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/exercises/static/exercises/follow_turtlebot/follow_turtlebot.world b/exercises/static/exercises/follow_turtlebot/follow_turtlebot.world new file mode 100644 index 000000000..6a2d5a0d9 --- /dev/null +++ b/exercises/static/exercises/follow_turtlebot/follow_turtlebot.world @@ -0,0 +1,54 @@ + + + + + + + model://sun + + + + + model://ground_plane + + + + + + + 12 + + + + + + + logo + model://logoJdeRobot + 0 -4 0 0 0 0 + + + + + 0 0 -9.8066 + + + quick + 10 + 1.3 + 0 + + + 0 + 0.2 + 100 + 0.001 + + + 0.004 + 1 + 250 + 6.0e-6 2.3e-5 -4.2e-5 + + + diff --git a/exercises/static/exercises/follow_turtlebot/my_solution.py b/exercises/static/exercises/follow_turtlebot/my_solution.py new file mode 100644 index 000000000..6a397e08a --- /dev/null +++ b/exercises/static/exercises/follow_turtlebot/my_solution.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python + +import rospy +import numpy as np +import cv2 +from drone_wrapper import DroneWrapper +from std_msgs.msg import Bool, Float64 +from sensor_msgs.msg import Image +from geometry_msgs.msg import Twist, Pose + +code_live_flag = False + +def gui_play_stop_cb(msg): + global code_live_flag, code_live_timer + if msg.data == True: + if not code_live_flag: + code_live_flag = True + code_live_timer = rospy.Timer(rospy.Duration(nsecs=50000000), execute) + else: + if code_live_flag: + code_live_flag = False + code_live_timer.shutdown() + + +def set_image_filtered(img): + gui_filtered_img_pub.publish(HAL.bridge.cv2_to_imgmsg(img)) + + +def set_image_threshed(img): + gui_threshed_img_pub.publish(HAL.bridge.cv2_to_imgmsg(img)) + + +def execute(event): + global HAL + img_frontal = HAL.get_frontal_image() + img_ventral = HAL.get_ventral_image() + # Both the above images are cv2 images + ################# Insert your code here ################################# + + set_image_filtered(img_frontal) + set_image_threshed(img_ventral) + + ######################################################################### + + +if __name__ == "__main__": + HAL = DroneWrapper() + rospy.Subscriber('gui/play_stop', Bool, gui_play_stop_cb) + gui_filtered_img_pub = rospy.Publisher('interface/filtered_img', Image, queue_size = 1) + gui_threshed_img_pub = rospy.Publisher('interface/threshed_img', Image, queue_size = 1) + code_live_flag = False + code_live_timer = rospy.Timer(rospy.Duration(nsecs=50000000), execute) + code_live_timer.shutdown() + while not rospy.is_shutdown(): + rospy.spin() diff --git a/exercises/static/exercises/follow_turtlebot/web-template/RADI-launch b/exercises/static/exercises/follow_turtlebot/web-template/RADI-launch new file mode 100755 index 000000000..afde9c081 --- /dev/null +++ b/exercises/static/exercises/follow_turtlebot/web-template/RADI-launch @@ -0,0 +1,2 @@ +#!/bin/sh +docker run -it --rm -p 8000:8000 -p 2303:2303 -p 1905:1905 -p 8765:8765 -p 6080:6080 -p 1108:1108 jderobot/robotics-academy:3.1.2 ./start-3.1.sh diff --git a/exercises/static/exercises/follow_turtlebot/web-template/README.md b/exercises/static/exercises/follow_turtlebot/web-template/README.md new file mode 100644 index 000000000..631a3d6c2 --- /dev/null +++ b/exercises/static/exercises/follow_turtlebot/web-template/README.md @@ -0,0 +1 @@ +[Exercise Documentation Website](https://jderobot.github.io/RoboticsAcademy/exercises/Drones/follow_turtlebot) diff --git a/exercises/static/exercises/follow_turtlebot/web-template/code/academy.py b/exercises/static/exercises/follow_turtlebot/web-template/code/academy.py new file mode 100644 index 000000000..7a59ce7f5 --- /dev/null +++ b/exercises/static/exercises/follow_turtlebot/web-template/code/academy.py @@ -0,0 +1,8 @@ +# Enter sequential code! +from GUI import GUI +from HAL import HAL + +while True: + # Enter iterative code! + img = HAL.get_ventral_image() + GUI.showImage(img) \ No newline at end of file diff --git a/exercises/static/exercises/follow_turtlebot/web-template/console.py b/exercises/static/exercises/follow_turtlebot/web-template/console.py new file mode 100644 index 000000000..23d0efad3 --- /dev/null +++ b/exercises/static/exercises/follow_turtlebot/web-template/console.py @@ -0,0 +1,18 @@ +# Functions to start and close console +import os +import sys + +def start_console(): + # Get all the file descriptors and choose the latest one + fds = os.listdir("/dev/pts/") + fds.sort() + console_fd = fds[-2] + + sys.stderr = open('/dev/pts/' + console_fd, 'w') + sys.stdout = open('/dev/pts/' + console_fd, 'w') + sys.stdin = open('/dev/pts/' + console_fd, 'w') + +def close_console(): + sys.stderr.close() + sys.stdout.close() + sys.stdin.close() \ No newline at end of file diff --git a/exercises/static/exercises/follow_turtlebot/web-template/exercise.py b/exercises/static/exercises/follow_turtlebot/web-template/exercise.py new file mode 100644 index 000000000..b083c2c8c --- /dev/null +++ b/exercises/static/exercises/follow_turtlebot/web-template/exercise.py @@ -0,0 +1,367 @@ + + +#!/usr/bin/env python + +from __future__ import print_function + +from websocket_server import WebsocketServer +import time +import threading +import subprocess +import sys +from datetime import datetime +import re +import json +import importlib + +import rospy +from std_srvs.srv import Empty +import cv2 + +from gui import GUI, ThreadGUI +from hal import HAL +from turtlebot import Turtlebot +from console import start_console, close_console + + +class Template: + # Initialize class variables + # self.ideal_cycle to run an execution for at least 1 second + # self.process for the current running process + def __init__(self): + self.measure_thread = None + self.thread = None + self.reload = False + self.stop_brain = True + self.user_code = "" + + # Time variables + self.ideal_cycle = 80 + self.measured_cycle = 80 + self.iteration_counter = 0 + self.real_time_factor = 0 + self.frequency_message = {'brain': '', 'gui': '', 'rtf': ''} + + self.server = None + self.client = None + self.host = sys.argv[1] + + # Initialize the GUI, HAL and Console behind the scenes + self.hal = HAL() + self.turtlebot = Turtlebot() + self.gui = GUI(self.host, self.turtlebot) + + # Function to parse the code + # A few assumptions: + # 1. The user always passes sequential and iterative codes + # 2. Only a single infinite loop + def parse_code(self, source_code): + sequential_code, iterative_code = self.seperate_seq_iter(source_code) + return iterative_code, sequential_code + + # Function to separate the iterative and sequential code + def seperate_seq_iter(self, source_code): + if source_code == "": + return "", "" + + # Search for an instance of while True + infinite_loop = re.search(r'[^ ]while\s*\(\s*True\s*\)\s*:|[^ ]while\s*True\s*:|[^ ]while\s*1\s*:|[^ ]while\s*\(\s*1\s*\)\s*:', source_code) + + # Separate the content inside while True and the other + # (Separating the sequential and iterative part!) + try: + start_index = infinite_loop.start() + iterative_code = source_code[start_index:] + sequential_code = source_code[:start_index] + + # Remove while True: syntax from the code + # And remove the the 4 spaces indentation before each command + iterative_code = re.sub(r'[^ ]while\s*\(\s*True\s*\)\s*:|[^ ]while\s*True\s*:|[^ ]while\s*1\s*:|[^ ]while\s*\(\s*1\s*\)\s*:', '', iterative_code) + # Add newlines to match line on bug report + extra_lines = sequential_code.count('\n') + while (extra_lines >= 0): + iterative_code = '\n' + iterative_code + extra_lines -= 1 + iterative_code = re.sub(r'^[ ]{4}', '', iterative_code, flags=re.M) + + except: + sequential_code = source_code + iterative_code = "" + + return sequential_code, iterative_code + + # The process function + def process_code(self, source_code): + # Redirect the information to console + start_console() + + iterative_code, sequential_code = self.parse_code(source_code) + + # print(sequential_code) + # print(iterative_code) + + # The Python exec function + # Run the sequential part + gui_module, hal_module = self.generate_modules() + reference_environment = {"GUI": gui_module, "HAL": hal_module} + while (self.stop_brain == True): + if (self.reload == True): + return + time.sleep(0.1) + exec(sequential_code, reference_environment) + + # Run the iterative part inside template + # and keep the check for flag + while self.reload == False: + while (self.stop_brain == True): + if (self.reload == True): + return + time.sleep(0.1) + + start_time = datetime.now() + + # Execute the iterative portion + exec(iterative_code, reference_environment) + + # Template specifics to run! + finish_time = datetime.now() + dt = finish_time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + + # Keep updating the iteration counter + if (iterative_code == ""): + self.iteration_counter = 0 + else: + self.iteration_counter = self.iteration_counter + 1 + + # The code should be run for atleast the target time step + # If it's less put to sleep + if (ms < self.ideal_cycle): + time.sleep((self.ideal_cycle - ms) / 1000.0) + + close_console() + print("Current Thread Joined!") + + # Function to generate the modules for use in ACE Editor + def generate_modules(self): + # Define HAL module + hal_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) + hal_module.HAL = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) + # hal_module.drone = imp.new_module("drone") + # motors# hal_module.HAL.motors = imp.new_module("motors") + + # Add HAL functions + hal_module.HAL.get_frontal_image = self.hal.get_frontal_image + hal_module.HAL.get_ventral_image = self.hal.get_ventral_image + hal_module.HAL.get_position = self.hal.get_position + hal_module.HAL.get_velocity = self.hal.get_velocity + hal_module.HAL.get_yaw_rate = self.hal.get_yaw_rate + hal_module.HAL.get_orientation = self.hal.get_orientation + hal_module.HAL.get_roll = self.hal.get_roll + hal_module.HAL.get_pitch = self.hal.get_pitch + hal_module.HAL.get_yaw = self.hal.get_yaw + hal_module.HAL.get_landed_state = self.hal.get_landed_state + hal_module.HAL.set_cmd_pos = self.hal.set_cmd_pos + hal_module.HAL.set_cmd_vel = self.hal.set_cmd_vel + hal_module.HAL.set_cmd_mix = self.hal.set_cmd_mix + hal_module.HAL.takeoff = self.hal.takeoff + hal_module.HAL.land = self.hal.land + + # Define GUI module + gui_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) + gui_module.GUI = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) + + # Add GUI functions + gui_module.GUI.showImage = self.gui.showImage + gui_module.GUI.showLeftImage = self.gui.showLeftImage + + # Adding modules to system + # Protip: The names should be different from + # other modules, otherwise some errors + sys.modules["HAL"] = hal_module + sys.modules["GUI"] = gui_module + + return gui_module, hal_module + + # Function to measure the frequency of iterations + def measure_frequency(self): + previous_time = datetime.now() + # An infinite loop + while True: + # Sleep for 2 seconds + time.sleep(2) + + # Measure the current time and subtract from the previous time to get real time interval + current_time = datetime.now() + dt = current_time - previous_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + previous_time = current_time + + # Get the time period + try: + # Division by zero + self.measured_cycle = ms / self.iteration_counter + except: + self.measured_cycle = 0 + + # Reset the counter + self.iteration_counter = 0 + + # Send to client + self.send_frequency_message() + + # Function to generate and send frequency messages + def send_frequency_message(self): + # This function generates and sends frequency measures of the brain and gui + brain_frequency = 0; gui_frequency = 0 + try: + brain_frequency = round(1000 / self.measured_cycle, 1) + except: + brain_frequency = 0 + + try: + gui_frequency = round(1000 / self.thread_gui.measured_cicle, 1) + except: + gui_frequency = 0 + + self.frequency_message["brain"] = brain_frequency + self.frequency_message["gui"] = gui_frequency + self.frequency_message["rtf"] = self.real_time_factor + + message = "#freq" + json.dumps(self.frequency_message) + self.server.send_message(self.client, message) + + def send_ping_message(self): + self.server.send_message(self.client, "#ping") + + # Function to notify the front end that the code was received and sent to execution + def send_code_message(self): + self.server.send_message(self.client, "#exec") + + # Function to track the real time factor from Gazebo statistics + # https://stackoverflow.com/a/17698359 + # (For reference, Python3 solution specified in the same answer) + def track_stats(self): + args = ["gz", "stats", "-p"] + # Prints gz statistics. "-p": Output comma-separated values containing- + # real-time factor (percent), simtime (sec), realtime (sec), paused (T or F) + stats_process = subprocess.Popen(args, stdout=subprocess.PIPE, bufsize=1, universal_newlines=True) + # bufsize=1 enables line-bufferred mode (the input buffer is flushed + # automatically on newlines if you would write to process.stdin ) + with stats_process.stdout: + for line in iter(stats_process.stdout.readline, ''): + stats_list = [x.strip() for x in line.split(',')] + self.real_time_factor = stats_list[0] + + # Function to maintain thread execution + def execute_thread(self, source_code): + # Keep checking until the thread is alive + # The thread will die when the coming iteration reads the flag + if self.thread is not None: + while self.thread.is_alive(): + time.sleep(0.2) + + # Turn the flag down, the iteration has successfully stopped! + self.reload = False + # New thread execution + self.thread = threading.Thread(target=self.process_code, args=[source_code]) + self.thread.start() + self.send_code_message() + print("New Thread Started!") + + # Function to read and set frequency from incoming message + def read_frequency_message(self, message): + frequency_message = json.loads(message) + + # Set brain frequency + frequency = float(frequency_message["brain"]) + self.ideal_cycle = 1000.0 / frequency + + # Set gui frequency + frequency = float(frequency_message["gui"]) + self.thread_gui.ideal_cicle = 1000.0 / frequency + + return + + # The websocket function + # Gets called when there is an incoming message from the client + def handle(self, client, server, message): + if message[:5] == "#freq": + frequency_message = message[5:] + self.read_frequency_message(frequency_message) + time.sleep(1) + return + + elif(message[:5] == "#ping"): + time.sleep(1) + self.send_ping_message() + return + + elif (message[:5] == "#code"): + try: + # Once received turn the reload flag up and send it to execute_thread function + self.user_code = message[6:] + # print(repr(code)) + self.reload = True + self.execute_thread(self.user_code) + except: + pass + + elif (message[:5] == "#rest"): + try: + self.reload = True + self.stop_brain = True + self.execute_thread(self.user_code) + except: + pass + + elif (message[:5] == "#stop"): + self.stop_brain = True + + elif (message[:5] == "#play"): + self.stop_brain = False + + # Function that gets called when the server is connected + def connected(self, client, server): + self.client = client + # Start the GUI update thread + self.thread_gui = ThreadGUI(self.gui) + self.thread_gui.start() + + # Start the real time factor tracker thread + self.stats_thread = threading.Thread(target=self.track_stats) + self.stats_thread.start() + + # Start measure frequency + self.measure_thread = threading.Thread(target=self.measure_frequency) + self.measure_thread.start() + + print(client, 'connected') + + # Function that gets called when the connected closes + def handle_close(self, client, server): + print(client, 'closed') + + def run_server(self): + self.server = WebsocketServer(port=1905, host=self.host) + self.server.set_fn_new_client(self.connected) + self.server.set_fn_client_left(self.handle_close) + self.server.set_fn_message_received(self.handle) + + logged = False + while not logged: + try: + f = open("/ws_code.log", "w") + f.write("websocket_code=ready") + f.close() + logged = True + except: + time.sleep(0.1) + + self.server.run_forever() + + +# Execute! +if __name__ == "__main__": + server = Template() + server.run_server() \ No newline at end of file diff --git a/exercises/static/exercises/follow_turtlebot/web-template/follow_turtlebot.world b/exercises/static/exercises/follow_turtlebot/web-template/follow_turtlebot.world new file mode 100644 index 000000000..cc03bc876 --- /dev/null +++ b/exercises/static/exercises/follow_turtlebot/web-template/follow_turtlebot.world @@ -0,0 +1,83 @@ + + + + + + + + + model://sun + + + + + model://ground_plane + + + + + logo + model://logoJdeRobot + 0 -4 0 0 0 0 + + + + + + 0 + 0.4 0.4 0.4 1.0 + 0.7 0.7 0.7 1 + + + 12 + + + + + + + + + + + EARTH_WGS84 + 47.3667 + 8.5500 + 500.0 + 0 + + + + + + + + quick + 1000 + 1.3 + + + 0 + 0.2 + 100 + 0.001 + + + 0.004 + 1 + 250 + 6.0e-06 2.3e-05 -4.2e-05 + 0 0 -9.8 + + + + + false + + + + + + + diff --git a/exercises/static/exercises/follow_turtlebot/web-template/gui.py b/exercises/static/exercises/follow_turtlebot/web-template/gui.py new file mode 100644 index 000000000..cd2219c0b --- /dev/null +++ b/exercises/static/exercises/follow_turtlebot/web-template/gui.py @@ -0,0 +1,257 @@ +import json +import cv2 +import base64 +import threading +import time +from datetime import datetime +from websocket_server import WebsocketServer +import logging + + +# Graphical User Interface Class +class GUI: + # Initialization function + # The actual initialization + def __init__(self, host, turtlebot): + t = threading.Thread(target=self.run_server) + + self.payload = {'image': ''} + self.left_payload = {'image': ''} + self.server = None + self.client = None + + self.host = host + + # Image variables + self.image_to_be_shown = None + self.image_to_be_shown_updated = False + self.image_show_lock = threading.Lock() + + self.left_image_to_be_shown = None + self.left_image_to_be_shown_updated = False + self.left_image_show_lock = threading.Lock() + + self.acknowledge = False + self.acknowledge_lock = threading.Lock() + + # Take the console object to set the same websocket and client + self.turtlebot = turtlebot + t.start() + + # Explicit initialization function + # Class method, so user can call it without instantiation + @classmethod + def initGUI(cls, host): + # self.payload = {'image': '', 'shape': []} + new_instance = cls(host) + return new_instance + + # Function to prepare image payload + # Encodes the image as a JSON string and sends through the WS + def payloadImage(self): + self.image_show_lock.acquire() + image_to_be_shown_updated = self.image_to_be_shown_updated + image_to_be_shown = self.image_to_be_shown + self.image_show_lock.release() + + image = image_to_be_shown + payload = {'image': '', 'shape': ''} + + if not image_to_be_shown_updated: + return payload + + shape = image.shape + frame = cv2.imencode('.JPEG', image)[1] + encoded_image = base64.b64encode(frame) + + payload['image'] = encoded_image.decode('utf-8') + payload['shape'] = shape + + self.image_show_lock.acquire() + self.image_to_be_shown_updated = False + self.image_show_lock.release() + + return payload + + # Function to prepare image payload + # Encodes the image as a JSON string and sends through the WS + def payloadLeftImage(self): + self.left_image_show_lock.acquire() + left_image_to_be_shown_updated = self.left_image_to_be_shown_updated + left_image_to_be_shown = self.left_image_to_be_shown + self.left_image_show_lock.release() + + image = left_image_to_be_shown + payload = {'image': '', 'shape': ''} + + if not left_image_to_be_shown_updated: + return payload + + shape = image.shape + frame = cv2.imencode('.JPEG', image)[1] + encoded_image = base64.b64encode(frame) + + payload['image'] = encoded_image.decode('utf-8') + payload['shape'] = shape + + self.left_image_show_lock.acquire() + self.left_image_to_be_shown_updated = False + self.left_image_show_lock.release() + + return payload + + # Function for student to call + def showImage(self, image): + self.image_show_lock.acquire() + self.image_to_be_shown = image + self.image_to_be_shown_updated = True + self.image_show_lock.release() + + # Function for student to call + def showLeftImage(self, image): + self.left_image_show_lock.acquire() + self.left_image_to_be_shown = image + self.left_image_to_be_shown_updated = True + self.left_image_show_lock.release() + + # Function to get the client + # Called when a new client is received + def get_client(self, client, server): + self.client = client + + # Function to get value of Acknowledge + def get_acknowledge(self): + self.acknowledge_lock.acquire() + acknowledge = self.acknowledge + self.acknowledge_lock.release() + + return acknowledge + + # Function to get value of Acknowledge + def set_acknowledge(self, value): + self.acknowledge_lock.acquire() + self.acknowledge = value + self.acknowledge_lock.release() + + # Update the gui + def update_gui(self): + # Payload Image Message + payload = self.payloadImage() + self.payload["image"] = json.dumps(payload) + + message = "#gui" + json.dumps(self.payload) + self.server.send_message(self.client, message) + + # Payload Left Image Message + left_payload = self.payloadLeftImage() + self.left_payload["image"] = json.dumps(left_payload) + + message = "#gul" + json.dumps(self.left_payload) + self.server.send_message(self.client, message) + + # Function to read the message from websocket + # Gets called when there is an incoming message from the client + def get_message(self, client, server, message): + # Acknowledge Message for GUI Thread + if message[:4] == "#ack": + self.set_acknowledge(True) + elif message[:4] == "#tur": + self.turtlebot.start_turtlebot() + elif message[:4] == "#stp": + self.turtlebot.stop_turtlebot() + elif message[:4] == "#rst": + self.turtlebot.reset_turtlebot() + + + # Activate the server + def run_server(self): + self.server = WebsocketServer(port=2303, host=self.host) + self.server.set_fn_new_client(self.get_client) + self.server.set_fn_message_received(self.get_message) + + logged = False + while not logged: + try: + f = open("/ws_gui.log", "w") + f.write("websocket_gui=ready") + f.close() + logged = True + except: + time.sleep(0.1) + + self.server.run_forever() + + # Function to reset + def reset_gui(self): + pass + + +# This class decouples the user thread +# and the GUI update thread +class ThreadGUI: + def __init__(self, gui): + self.gui = gui + + # Time variables + self.ideal_cycle = 80 + self.measured_cycle = 80 + self.iteration_counter = 0 + + # Function to start the execution of threads + def start(self): + self.measure_thread = threading.Thread(target=self.measure_thread) + self.thread = threading.Thread(target=self.run) + + self.measure_thread.start() + self.thread.start() + + print("GUI Thread Started!") + + # The measuring thread to measure frequency + def measure_thread(self): + while self.gui.client is None: + pass + + previous_time = datetime.now() + while True: + # Sleep for 2 seconds + time.sleep(2) + + # Measure the current time and subtract from previous time to get real time interval + current_time = datetime.now() + dt = current_time - previous_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + previous_time = current_time + + # Get the time period + try: + # Division by zero + self.measured_cycle = ms / self.iteration_counter + except: + self.measured_cycle = 0 + + # Reset the counter + self.iteration_counter = 0 + + # The main thread of execution + def run(self): + while self.gui.client is None: + pass + + while True: + start_time = datetime.now() + self.gui.update_gui() + acknowledge_message = self.gui.get_acknowledge() + + while not acknowledge_message: + acknowledge_message = self.gui.get_acknowledge() + + self.gui.set_acknowledge(False) + + finish_time = datetime.now() + self.iteration_counter = self.iteration_counter + 1 + + dt = finish_time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + if ms < self.ideal_cycle: + time.sleep((self.ideal_cycle-ms) / 1000.0) \ No newline at end of file diff --git a/exercises/static/exercises/follow_turtlebot/web-template/hal.py b/exercises/static/exercises/follow_turtlebot/web-template/hal.py new file mode 100644 index 000000000..b21d65e43 --- /dev/null +++ b/exercises/static/exercises/follow_turtlebot/web-template/hal.py @@ -0,0 +1,86 @@ + + +import rospy +import cv2 +import threading +import time +from datetime import datetime + +from drone_wrapper import DroneWrapper + + +# Hardware Abstraction Layer +class HAL: + IMG_WIDTH = 320 + IMG_HEIGHT = 240 + + def __init__(self): + rospy.init_node("HAL") + + self.image = None + self.drone = DroneWrapper(name="rqt") + + # Explicit initialization functions + # Class method, so user can call it without instantiation + @classmethod + def initRobot(cls): + new_instance = cls() + return new_instance + + # Get Image from ROS Driver Camera + def get_frontal_image(self): + image = self.drone.get_frontal_image() + image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) + return image_rgb + + def get_ventral_image(self): + image = self.drone.get_ventral_image() + image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) + return image_rgb + + def get_position(self): + pos = self.drone.get_position() + return pos + + def get_velocity(self): + vel = self.drone.get_velocity() + return vel + + def get_yaw_rate(self): + yaw_rate = self.drone.get_yaw_rate() + return yaw_rate + + def get_orientation(self): + orientation = self.drone.get_orientation() + return orientation + + def get_roll(self): + roll = self.drone.get_roll() + return roll + + def get_pitch(self): + pitch = self.drone.get_pitch() + return pitch + + def get_yaw(self): + yaw = self.drone.get_yaw() + return yaw + + def get_landed_state(self): + state = self.drone.get_landed_state() + return state + + def set_cmd_pos(self, x, y, z, az): + self.drone.set_cmd_pos(x, y, z, az) + + def set_cmd_vel(self, vx, vy, vz, az): + self.drone.set_cmd_vel(vx, vy, vz, az) + + def set_cmd_mix(self, vx, vy, z, az): + self.drone.set_cmd_mix(vx, vy, z, az) + + def takeoff(self, h=5): + self.drone.takeoff(h) + + def land(self): + self.drone.land() \ No newline at end of file diff --git a/exercises/static/exercises/follow_turtlebot/web-template/interfaces/__init__.py b/exercises/static/exercises/follow_turtlebot/web-template/interfaces/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/exercises/static/exercises/follow_turtlebot/web-template/interfaces/camera.py b/exercises/static/exercises/follow_turtlebot/web-template/interfaces/camera.py new file mode 100644 index 000000000..5a021a13e --- /dev/null +++ b/exercises/static/exercises/follow_turtlebot/web-template/interfaces/camera.py @@ -0,0 +1,89 @@ +import rospy +from sensor_msgs.msg import Image as ImageROS +import threading +from math import pi as PI +import cv2 +from cv_bridge import CvBridge, CvBridgeError + + +MAXRANGE = 8 # max length received from imageD +MINRANGE = 0 + + +def imageMsg2Image(img, bridge): + + image = Image() + + image.width = img.width + image.height = img.height + image.format = "BGR8" + image.timeStamp = img.header.stamp.secs + (img.header.stamp.nsecs * 1e-9) + cv_image = 0 + if img.encoding[-2:] == "C1": + gray_img_buff = bridge.imgmsg_to_cv2(img, img.encoding) + cv_image = depthToRGB8(gray_img_buff, img.encoding) + else: + cv_image = bridge.imgmsg_to_cv2(img, "bgr8") + image.data = cv_image + return image + + +import numpy as np + + +class Image: + + def __init__(self): + + self.height = 3 # Image height [pixels] + self.width = 3 # Image width [pixels] + self.timeStamp = 0 # Time stamp [s] */ + self.format = "" # Image format string (RGB8, BGR,...) + self.data = np.zeros((self.height, self.width, 3), np.uint8) # The image data itself + self.data.shape = self.height, self.width, 3 + + def __str__(self): + s = "Image: {\n height: " + str(self.height) + "\n width: " + str(self.width) + s = s + "\n format: " + self.format + "\n timeStamp: " + str(self.timeStamp) + s = s + "\n data: " + str(self.data) + "\n}" + return s + + +class ListenerCamera: + + def __init__(self, topic): + + self.topic = topic + self.data = Image() + self.sub = None + self.lock = threading.Lock() + + self.bridge = CvBridge() + self.start() + + def __callback(self, img): + + image = imageMsg2Image(img, self.bridge) + + self.lock.acquire() + self.data = image + self.lock.release() + + def stop(self): + + self.sub.unregister() + + def start(self): + self.sub = rospy.Subscriber(self.topic, ImageROS, self.__callback) + + def getImage(self): + + self.lock.acquire() + image = self.data + self.lock.release() + + return image + + def hasproxy(self): + + return hasattr(self, "sub") and self.sub diff --git a/exercises/static/exercises/follow_turtlebot/web-template/interfaces/motors.py b/exercises/static/exercises/follow_turtlebot/web-template/interfaces/motors.py new file mode 100644 index 000000000..70dca8a46 --- /dev/null +++ b/exercises/static/exercises/follow_turtlebot/web-template/interfaces/motors.py @@ -0,0 +1,123 @@ +import rospy +from geometry_msgs.msg import Twist +import threading +from math import pi as PI +from .threadPublisher import ThreadPublisher + + + +def cmdvel2Twist(vel): + + tw = Twist() + tw.linear.x = vel.vx + tw.linear.y = vel.vy + tw.linear.z = vel.vz + tw.angular.x = vel.ax + tw.angular.y = vel.ay + tw.angular.z = vel.az + + return tw + + +class CMDVel (): + + def __init__(self): + + self.vx = 0 # vel in x[m/s] (use this for V in wheeled robots) + self.vy = 0 # vel in y[m/s] + self.vz = 0 # vel in z[m/s] + self.ax = 0 # angular vel in X axis [rad/s] + self.ay = 0 # angular vel in X axis [rad/s] + self.az = 0 # angular vel in Z axis [rad/s] (use this for W in wheeled robots) + self.timeStamp = 0 # Time stamp [s] + + + def __str__(self): + s = "CMDVel: {\n vx: " + str(self.vx) + "\n vy: " + str(self.vy) + s = s + "\n vz: " + str(self.vz) + "\n ax: " + str(self.ax) + s = s + "\n ay: " + str(self.ay) + "\n az: " + str(self.az) + s = s + "\n timeStamp: " + str(self.timeStamp) + "\n}" + return s + +class PublisherMotors: + + def __init__(self, topic, maxV, maxW): + + self.maxW = maxW + self.maxV = maxV + + self.topic = topic + self.data = CMDVel() + self.pub = rospy.Publisher(self.topic, Twist, queue_size=1) + + self.lock = threading.Lock() + + self.kill_event = threading.Event() + self.thread = ThreadPublisher(self, self.kill_event) + + self.thread.daemon = True + self.start() + + def publish (self): + + self.lock.acquire() + tw = cmdvel2Twist(self.data) + self.lock.release() + self.pub.publish(tw) + + def stop(self): + + self.kill_event.set() + self.pub.unregister() + + def start (self): + + self.kill_event.clear() + self.thread.start() + + + + def getMaxW(self): + return self.maxW + + def getMaxV(self): + return self.maxV + + + def sendVelocities(self, vel): + + self.lock.acquire() + self.data = vel + self.lock.release() + + def sendV(self, v): + + self.sendVX(v) + + def sendL(self, l): + + self.sendVY(l) + + def sendW(self, w): + + self.sendAZ(w) + + def sendVX(self, vx): + + self.lock.acquire() + self.data.vx = vx + self.lock.release() + + def sendVY(self, vy): + + self.lock.acquire() + self.data.vy = vy + self.lock.release() + + def sendAZ(self, az): + + self.lock.acquire() + self.data.az = az + self.lock.release() + + diff --git a/exercises/static/exercises/follow_turtlebot/web-template/interfaces/pose3d.py b/exercises/static/exercises/follow_turtlebot/web-template/interfaces/pose3d.py new file mode 100644 index 000000000..fd0bfc37a --- /dev/null +++ b/exercises/static/exercises/follow_turtlebot/web-template/interfaces/pose3d.py @@ -0,0 +1,176 @@ +import rospy +import threading +from math import asin, atan2, pi +from nav_msgs.msg import Odometry + +def quat2Yaw(qw, qx, qy, qz): + ''' + Translates from Quaternion to Yaw. + + @param qw,qx,qy,qz: Quaternion values + + @type qw,qx,qy,qz: float + + @return Yaw value translated from Quaternion + + ''' + rotateZa0=2.0*(qx*qy + qw*qz) + rotateZa1=qw*qw + qx*qx - qy*qy - qz*qz + rotateZ=0.0 + if(rotateZa0 != 0.0 and rotateZa1 != 0.0): + rotateZ=atan2(rotateZa0,rotateZa1) + return rotateZ + +def quat2Pitch(qw, qx, qy, qz): + ''' + Translates from Quaternion to Pitch. + + @param qw,qx,qy,qz: Quaternion values + + @type qw,qx,qy,qz: float + + @return Pitch value translated from Quaternion + + ''' + + rotateYa0=-2.0*(qx*qz - qw*qy) + rotateY=0.0 + if(rotateYa0 >= 1.0): + rotateY = pi/2.0 + elif(rotateYa0 <= -1.0): + rotateY = -pi/2.0 + else: + rotateY = asin(rotateYa0) + + return rotateY + +def quat2Roll (qw, qx, qy, qz): + ''' + Translates from Quaternion to Roll. + + @param qw,qx,qy,qz: Quaternion values + + @type qw,qx,qy,qz: float + + @return Roll value translated from Quaternion + + ''' + rotateXa0=2.0*(qy*qz + qw*qx) + rotateXa1=qw*qw - qx*qx - qy*qy + qz*qz + rotateX=0.0 + + if(rotateXa0 != 0.0 and rotateXa1 != 0.0): + rotateX=atan2(rotateXa0, rotateXa1) + return rotateX + + +def odometry2Pose3D(odom): + ''' + Translates from ROS Odometry to JderobotTypes Pose3d. + + @param odom: ROS Odometry to translate + + @type odom: Odometry + + @return a Pose3d translated from odom + + ''' + pose = Pose3d() + ori = odom.pose.pose.orientation + + pose.x = odom.pose.pose.position.x + pose.y = odom.pose.pose.position.y + pose.z = odom.pose.pose.position.z + #pose.h = odom.pose.pose.position.h + pose.yaw = quat2Yaw(ori.w, ori.x, ori.y, ori.z) + pose.pitch = quat2Pitch(ori.w, ori.x, ori.y, ori.z) + pose.roll = quat2Roll(ori.w, ori.x, ori.y, ori.z) + pose.q = [ori.w, ori.x, ori.y, ori.z] + pose.timeStamp = odom.header.stamp.secs + (odom.header.stamp.nsecs *1e-9) + + return pose + +class Pose3d (): + + def __init__(self): + + self.x = 0 # X coord [meters] + self.y = 0 # Y coord [meters] + self.z = 0 # Z coord [meters] + self.h = 1 # H param + self.yaw = 0 #Yaw angle[rads] + self.pitch = 0 # Pitch angle[rads] + self.roll = 0 # Roll angle[rads] + self.q = [0,0,0,0] # Quaternion + self.timeStamp = 0 # Time stamp [s] + + + def __str__(self): + s = "Pose3D: {\n x: " + str(self.x) + "\n Y: " + str(self.y) + s = s + "\n Z: " + str(self.z) + "\n H: " + str(self.h) + s = s + "\n Yaw: " + str(self.yaw) + "\n Pitch: " + str(self.pitch) + "\n Roll: " + str(self.roll) + s = s + "\n quaternion: " + str(self.q) + "\n timeStamp: " + str(self.timeStamp) + "\n}" + return s + + +class ListenerPose3d: + ''' + ROS Pose3D Subscriber. Pose3D Client to Receive pose3d from ROS nodes. + ''' + def __init__(self, topic): + ''' + ListenerPose3d Constructor. + + @param topic: ROS topic to subscribe + + @type topic: String + + ''' + self.topic = topic + self.data = Pose3d() + self.sub = None + self.lock = threading.Lock() + self.start() + + def __callback (self, odom): + ''' + Callback function to receive and save Pose3d. + + @param odom: ROS Odometry received + + @type odom: Odometry + + ''' + pose = odometry2Pose3D(odom) + + self.lock.acquire() + self.data = pose + self.lock.release() + + def stop(self): + ''' + Stops (Unregisters) the client. + + ''' + self.sub.unregister() + + def start (self): + ''' + Starts (Subscribes) the client. + + ''' + self.sub = rospy.Subscriber(self.topic, Odometry, self.__callback) + + def getPose3d(self): + ''' + Returns last Pose3d. + + @return last JdeRobotTypes Pose3d saved + + ''' + self.lock.acquire() + pose = self.data + self.lock.release() + + return pose + diff --git a/exercises/static/exercises/follow_turtlebot/web-template/interfaces/threadPublisher.py b/exercises/static/exercises/follow_turtlebot/web-template/interfaces/threadPublisher.py new file mode 100644 index 000000000..69aa0ad48 --- /dev/null +++ b/exercises/static/exercises/follow_turtlebot/web-template/interfaces/threadPublisher.py @@ -0,0 +1,46 @@ +# +# Copyright (C) 1997-2016 JDE Developers Team +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. +# Authors : +# Alberto Martin Florido +# Aitor Martinez Fernandez +# +import threading +import time +from datetime import datetime + +time_cycle = 80 + + +class ThreadPublisher(threading.Thread): + + def __init__(self, pub, kill_event): + self.pub = pub + self.kill_event = kill_event + threading.Thread.__init__(self, args=kill_event) + + def run(self): + while (not self.kill_event.is_set()): + start_time = datetime.now() + + self.pub.publish() + + finish_Time = datetime.now() + + dt = finish_Time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + #print (ms) + if (ms < time_cycle): + time.sleep((time_cycle - ms) / 1000.0) \ No newline at end of file diff --git a/exercises/static/exercises/follow_turtlebot/web-template/interfaces/threadStoppable.py b/exercises/static/exercises/follow_turtlebot/web-template/interfaces/threadStoppable.py new file mode 100644 index 000000000..b631d180f --- /dev/null +++ b/exercises/static/exercises/follow_turtlebot/web-template/interfaces/threadStoppable.py @@ -0,0 +1,36 @@ +import threading +import time +from datetime import datetime + +time_cycle = 80 + + +class StoppableThread(threading.Thread): + """Thread class with a stop() method. The thread itself has to check + regularly for the stopped() condition.""" + + def __init__(self, target, kill_event=threading.Event(), *args, **kwargs): + super(StoppableThread, self).__init__(*args, **kwargs) + self._target = target + self._target_args = kwargs["args"] + self._kill_event = kill_event + + def run(self): + while not self.stopped(): + start_time = datetime.now() + + self._target(*self._target_args) + + finish_time = datetime.now() + + dt = finish_time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + # print (ms) + if ms < time_cycle: + time.sleep((time_cycle - ms) / 1000.0) + + def stop(self): + self._kill_event.set() + + def stopped(self): + return self._kill_event.is_set() diff --git a/exercises/static/exercises/follow_turtlebot/web-template/launch/follow_turtlebot.launch b/exercises/static/exercises/follow_turtlebot/web-template/launch/follow_turtlebot.launch new file mode 100644 index 000000000..62e0d36e3 --- /dev/null +++ b/exercises/static/exercises/follow_turtlebot/web-template/launch/follow_turtlebot.launch @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/exercises/static/exercises/follow_turtlebot/web-template/turtlebot.py b/exercises/static/exercises/follow_turtlebot/web-template/turtlebot.py new file mode 100644 index 000000000..a3c16925d --- /dev/null +++ b/exercises/static/exercises/follow_turtlebot/web-template/turtlebot.py @@ -0,0 +1,81 @@ +import sys +import rospy +from threading import Event + +from gazebo_msgs.msg import ModelState +from gazebo_msgs.srv import GetModelState +from gazebo_msgs.srv import SetModelState +from interfaces.threadStoppable import StoppableThread + +class Turtlebot(): + def __init__(self): + self.set_state = rospy.ServiceProxy('/gazebo/set_model_state', SetModelState) + self.get_state = rospy.ServiceProxy('/gazebo/get_model_state', GetModelState) + self.play_event = Event() + rospy.sleep(2) + self.stop_turtlebot() + + # Explicit initialization functions + # Class method, so user can call it without instantiation + @classmethod + def initRobot(cls): + new_instance = cls() + return new_instance + + def start_turtlebot(self): + try: + self.play_event.set() + rospy.sleep(0.5) + self.thread.join() + except: + print("Thread not yet started") + + def stop_turtlebot(self): + self.play_event.clear() + self.thread = StoppableThread(target=self.__stop__, args=[]) + self.thread.start() + + def __stop__(self): + rec = self.get_state("turtlebot3", "base_link") + req = ModelState() + req.model_name = "turtlebot3" + req.twist.linear.x = 0.0 + req.twist.linear.y = 0.0 + req.twist.linear.z = 0.0 + req.twist.angular.x = 0.0 + req.twist.angular.y = 0.0 + req.twist.angular.z = 0.0 + req.pose.position.x = rec.pose.position.x + 3.0 + req.pose.position.y = rec.pose.position.y + 1.0 + req.pose.position.z = 0.0 + req.pose.orientation.x = rec.pose.orientation.x + req.pose.orientation.y = rec.pose.orientation.y + req.pose.orientation.z = rec.pose.orientation.z + req.pose.orientation.w = 1.0 + while True: + if self.play_event.is_set(): + sys.exit() # kill stop_turtlebot thread + else: + self.set_state(req) + + def reset_turtlebot(self): + self.start_turtlebot() + req = ModelState() + req.model_name = "turtlebot3" + req.twist.linear.x = 0.0 + req.twist.linear.y = 0.0 + req.twist.linear.z = 0.0 + req.twist.angular.x = 0.0 + req.twist.angular.y = 0.0 + req.twist.angular.z = 0.0 + req.pose.position.x = 0.0 + req.pose.position.y = 0.0 + req.pose.position.z = 0.0 + req.pose.orientation.x = 0.0 + req.pose.orientation.y = 0.0 + req.pose.orientation.z = 0.0 + req.pose.orientation.w = 0.0 + self.set_state(req) + self.stop_turtlebot() + + diff --git a/exercises/static/exercises/labyrinth_escape/README.md b/exercises/static/exercises/labyrinth_escape/README.md new file mode 100644 index 000000000..78767cb95 --- /dev/null +++ b/exercises/static/exercises/labyrinth_escape/README.md @@ -0,0 +1 @@ +[Exercise Documentation Website](https://jderobot.github.io/RoboticsAcademy/exercises/Drones/labyrinth_escape) diff --git a/exercises/static/exercises/labyrinth_escape/arrows/down.png b/exercises/static/exercises/labyrinth_escape/arrows/down.png new file mode 100644 index 0000000000000000000000000000000000000000..1929fff2f42d898fe3962352635672fe7ffd8a63 GIT binary patch literal 2008 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 U9pRSfr~m)}07*qoM6N<$f{`(#TmS$7 literal 0 HcmV?d00001 diff --git a/exercises/static/exercises/labyrinth_escape/arrows/right.png b/exercises/static/exercises/labyrinth_escape/arrows/right.png new file mode 100644 index 0000000000000000000000000000000000000000..59dec6ca2cd8ae88e450e59276f9f3d849a149ca GIT binary patch literal 1974 zcmV;n2TAyeP)CjFRI0YA_bd(t+f(N5o;A{Tdj@3B&|(MTAOXw zjY*m`f4h6eiyBGWWY5fIe-1hGgxrKPGjGnb=giEz=ga^?sQkYA>MuegM@>z;(zyj8 zTqsgjCRB?!mbGiu%P0vkjB%=}a@AXLD8Zn586_b_MFRwbtJGU@D7Ce>;&NS4Dx)Ao zuzB-Ua&vu(VL6xT>NNy|{Yqt|h2-Q6^8E8Fm4k9JUayz!+XWDnE+Z+#@aXwR*&E)A-BNju6ao+vF`oJT8Qu!LWh*I;6#`&l z^4i7Mc;(_N#6&FH;Q$U70w55)9eSIsy<3?wr?MT67Bb66k&k%nx5pTZk7YFuEo2Ud zMh@}7*$4PD`e)W+&_ZtD*w``ZyX(0yeIYCDYa#P+X5tKWU3GL#c4fS6Eo1@CPoJl* ztBw=nCok4@(nwWK6$i==P@YpRT}I!s(nM{3Ek`SlD#-vXBrP=h8#!EYn3Z1j75KD}6ynCs z&ec2lu;fE>TscYAq5F^&;dA-;p!fqC{0&ms?uD=v;x^p)J^q{iE)p-o^)IrsRN4B( zKR;5(E|9hm2*%BEsXAmR^cK=w+AKYiN`z(lT?m26^(WQDl*-WvZZ5kJvf@lkDIxR4 zJcg$^b1y88>crt=_e$kyIk|C`*A{S1#`tzfVI!L)zA()IuaF@)q%F z>8EVS+mP19LA4MGs&cCMtn4!?aw^i^mhM9=sLQY8o62u8A_KILglt*4g+t|s2zUb- zYf}rEhuNIep5i_D+?m}kMfV{$;B)!dQ@n>K3!co%%HCSY916XK>@VG)_K{Q~w2*&D zbxtZ-bRWXCoYb+ZV_BC0S_qB)M!u~0GUYj`WO1wz1B0Ebce1x+FUwrZvK`L9IC6J{ zNHjv*Xd6!yJYl;%QXDD7&N%j#WmmKio1ul+3@yZFXdyO33$Ynmh|SPKY=#zMGqez! zVLbO-ySV*!Shvp0-FFw^_x~^3F*yby1eY%laq{FKot<$82Cm!Seun(~5cT!Ly!28Y zH8mx+OS~-=VzC%Uj-2Pc_a^!F+W-?2h0OV`@a8R~J)RNq_~R!-Te4tmGtq_6T^6XIq$qP zW~&Xf?9R;03{6dLz$;xwX{O%Rwt}Id5!JXH%jwe>>FiV+3@Hg28@r7kei%?{z{!05 z^;Lw(Q!1mF%kuE^&*@u;eyP#Xp0i0av`O#lD@07*qo IM6N<$f|=pOpa1{> literal 0 HcmV?d00001 diff --git a/exercises/static/exercises/labyrinth_escape/arrows/up.png b/exercises/static/exercises/labyrinth_escape/arrows/up.png new file mode 100644 index 0000000000000000000000000000000000000000..7bc514087235216499a47b62dc8dd9074a387729 GIT binary patch literal 2026 zcmVbqHmLzi6E2p9wiH@gws+F&w7?d3 zx4Yf>d;kn++nw3YMIo~(^b!N`__RJ0dA~mm_JFk;K8M}7XtNPmlL=LDV zCx_HFu`P4vsBfcENCJx%In_t8Df#(M0As4!s1o9K_hb3;Y*hj5%o}gyz~kvx)kc+& zx8CZ`1I3fz!+1Vm|bCvp{&de#=?r*C=^0?=bc-4 z@4a~`rei= zk7oc!k4Cwd5vMpP`Tv$F`2BNm4_&x_!R(YKrM9+{ zmMyCzAeLN+!*QP$FTPGc{ZvoUXw>Z7lOoG9?dje_xPpIQ*)`eJ9Bwu{wMmIYqICFh z9liEi12GnQN(fuAqMm#{pr8OKES!NQOJ0P_<iv9oJ}MVh zVwP)`Ntr1l7K`E9wQig{_ct0EfVMUUM#SlrFbOB56WHFm9b*%eCJHkO@vHw=xE*eT zGn*Q!i;Fx^eyd;x6rZq+d}?XZlHj?oURZCs0vScmGi9)}0z z{&K9!Sfzbp8e<6|1MvYAHWlJ#71J^<#asyd`T3 zSD%7%C4`9ZnQtQeM%3~9qIVZ{~K0ktkyl2Qn(R9L^w5k3fo$@Ax?3_;^w+? zQMs@Zez)JSn3KVckc#^i*wC^8VOjZt0IkS(=i|?Xee=&VHY}z$sFg}QPZ@r7UaGe?9C&URSj`)tCB&)=f;jk$}MiL`f*Sro@ zBUMI+R)ojl!SDXxO&Jawe#(l zBg3YIS=C{~gb)$F>i!A=DPUB{Df75G9F>!QT&xv_2q7YT*ZUp5@BQA0aGG*HbRMPc zrI1PgQVw8{ke>&C#_sOjmRfA^BmpUay*+yk84l|{$~rTA1|J4LG=0J~RTvnK`;Mc; zTcU3qy@g0|2{DRk(pS-~A~Z%CQ))ZIF#}f%t|G&kk#hWLY#O{SeeWPk)3tX%(;aZJ z)R(w2Fyy#$@Lbw+n&qPhrq{9rPa}kIh7iITLI`IFA)H|wA&fCNEU>%$XQ&}OE%%uS zTM4lOmJ(uAfzM1VB_shp*W+r5rG(f4A%ru85Y7-nI70~G4BH4{kA8T^RzgJSM4%cW zgflE9#Of3?YOwgb>aULO4SR;SAddVPJ4D{&$!E3~h!G!Wotl!s~Zj zX^EwTSoIDpHCRfB9S}k|LkQsvA%ru85YDiT5C?OJ&(wKCtU3{>#!^DW#E>&Y=|o^S zLI`IFA)FzEaE1`V8MYC^z$|f}dfL}tH(=g8LVkWa3JPW;J>6q=PP)Nc>4botlS4BJ+R0ALkfmh(vO6;J{L>S<{Br);)m&~9A3*orl4ycioB|C;e%6c5ZtN4+?7XgKA>?8*;6 zj3|}?s5n9Z!0T;AFqn~Rc%xkcY;B+E$mBI3Ppu~0A82_V>!cPhX4Qo07*qo IM6N<$f?hMBN&o-= literal 0 HcmV?d00001 diff --git a/exercises/static/exercises/labyrinth_escape/labyrinth.world b/exercises/static/exercises/labyrinth_escape/labyrinth.world new file mode 100644 index 000000000..c50d7faf2 --- /dev/null +++ b/exercises/static/exercises/labyrinth_escape/labyrinth.world @@ -0,0 +1,89 @@ + + + + + + model://arrow + arrow_1 + -4 -6 0 0 0 0 + + + model://arrow + arrow_2 + 2.5 -6 0 0 0 0 + + + model://arrow + arrow_3 + 9 -6 0 0 0 1.57 + + + model://arrow + arrow_4 + 9 0 0 0 0 3.14 + + + model://arrow + arrow_5 + 2 0 0 0 0 3.14 + + + model://arrow + arrow_6 + -6 0 0 0 0 1.57 + + + model://arrow + arrow_7 + -6 5 0 0 0 0 + + + model://arrow + arrow_8 + 1 5 0 0 0 1.57 + + + model://arrow + arrow_9 + 1 9 0 0 0 3.14 + + + model://arrow + arrow_10 + -6 9 0 0 0 3.14 + + + + + model://sun + + + + model://simple_labyrinth + + + + + 0 0 -9.8066 + + + quick + 10 + 1.3 + 0 + + + 0 + 0.2 + 100 + 0.001 + + + 0.004 + 1 + 250 + 6.0e-6 2.3e-5 -4.2e-5 + + + + diff --git a/exercises/static/exercises/labyrinth_escape/labyrinth_escape.launch b/exercises/static/exercises/labyrinth_escape/labyrinth_escape.launch new file mode 100644 index 000000000..c4e591b50 --- /dev/null +++ b/exercises/static/exercises/labyrinth_escape/labyrinth_escape.launch @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/exercises/static/exercises/labyrinth_escape/my_solution.py b/exercises/static/exercises/labyrinth_escape/my_solution.py new file mode 100755 index 000000000..05d74d9d1 --- /dev/null +++ b/exercises/static/exercises/labyrinth_escape/my_solution.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python + +import rospy +import numpy as np +import cv2 +from drone_wrapper import DroneWrapper +from std_msgs.msg import Bool, Float64 +from sensor_msgs.msg import Image +from geometry_msgs.msg import Twist, Pose + +code_live_flag = False + +def gui_play_stop_cb(msg): + global code_live_flag, code_live_timer + if msg.data == True: + if not code_live_flag: + code_live_flag = True + code_live_timer = rospy.Timer(rospy.Duration(nsecs=50000000), execute) + else: + if code_live_flag: + code_live_flag = False + code_live_timer.shutdown() + +def set_image_filtered(img): + gui_filtered_img_pub.publish(drone.bridge.cv2_to_imgmsg(img)) + +def set_image_threshed(img): + gui_threshed_img_pub.publish(drone.bridge.cv2_to_imgmsg(img)) + +def execute(event): + global drone + img_frontal = drone.get_frontal_image() + img_ventral = drone.get_ventral_image() + # Both the above images are cv2 images + ################# Insert your code here ################################# + + set_image_filtered(img_frontal) + set_image_threshed(img_ventral) + + ######################################################################### + +if __name__ == "__main__": + drone = DroneWrapper() + rospy.Subscriber('gui/play_stop', Bool, gui_play_stop_cb) + gui_filtered_img_pub = rospy.Publisher('interface/filtered_img', Image, queue_size = 1) + gui_threshed_img_pub = rospy.Publisher('interface/threshed_img', Image, queue_size = 1) + code_live_flag = False + code_live_timer = rospy.Timer(rospy.Duration(nsecs=50000000), execute) + code_live_timer.shutdown() + while not rospy.is_shutdown(): + rospy.spin() diff --git a/exercises/static/exercises/labyrinth_escape/web-template/RADI-launch b/exercises/static/exercises/labyrinth_escape/web-template/RADI-launch new file mode 100755 index 000000000..afde9c081 --- /dev/null +++ b/exercises/static/exercises/labyrinth_escape/web-template/RADI-launch @@ -0,0 +1,2 @@ +#!/bin/sh +docker run -it --rm -p 8000:8000 -p 2303:2303 -p 1905:1905 -p 8765:8765 -p 6080:6080 -p 1108:1108 jderobot/robotics-academy:3.1.2 ./start-3.1.sh diff --git a/exercises/static/exercises/labyrinth_escape/web-template/README.md b/exercises/static/exercises/labyrinth_escape/web-template/README.md new file mode 100644 index 000000000..78767cb95 --- /dev/null +++ b/exercises/static/exercises/labyrinth_escape/web-template/README.md @@ -0,0 +1 @@ +[Exercise Documentation Website](https://jderobot.github.io/RoboticsAcademy/exercises/Drones/labyrinth_escape) diff --git a/exercises/static/exercises/labyrinth_escape/web-template/code/academy.py b/exercises/static/exercises/labyrinth_escape/web-template/code/academy.py new file mode 100644 index 000000000..7a59ce7f5 --- /dev/null +++ b/exercises/static/exercises/labyrinth_escape/web-template/code/academy.py @@ -0,0 +1,8 @@ +# Enter sequential code! +from GUI import GUI +from HAL import HAL + +while True: + # Enter iterative code! + img = HAL.get_ventral_image() + GUI.showImage(img) \ No newline at end of file diff --git a/exercises/static/exercises/labyrinth_escape/web-template/console.py b/exercises/static/exercises/labyrinth_escape/web-template/console.py new file mode 100644 index 000000000..7b72a0913 --- /dev/null +++ b/exercises/static/exercises/labyrinth_escape/web-template/console.py @@ -0,0 +1,20 @@ +# Functions to start and close console +import os +import sys + + +def start_console(): + # Get all the file descriptors and choose the latest one + fds = os.listdir("/dev/pts/") + fds.sort() + console_fd = fds[-2] + + sys.stderr = open('/dev/pts/' + console_fd, 'w') + sys.stdout = open('/dev/pts/' + console_fd, 'w') + sys.stdin = open('/dev/pts/' + console_fd, 'w') + + +def close_console(): + sys.stderr.close() + sys.stdout.close() + sys.stdin.close() diff --git a/exercises/static/exercises/labyrinth_escape/web-template/exercise.py b/exercises/static/exercises/labyrinth_escape/web-template/exercise.py new file mode 100644 index 000000000..a896ab64f --- /dev/null +++ b/exercises/static/exercises/labyrinth_escape/web-template/exercise.py @@ -0,0 +1,362 @@ +#!/usr/bin/env python + +from __future__ import print_function + +from websocket_server import WebsocketServer +import time +import threading +import subprocess +import sys +from datetime import datetime +import re +import json +import importlib + +import rospy +from std_srvs.srv import Empty + +from gui import GUI, ThreadGUI +from hal import HAL +from console import start_console, close_console + + +class Template: + # Initialize class variables + # self.ideal_cycle to run an execution for at least 1 second + # self.process for the current running process + def __init__(self): + self.measure_thread = None + self.thread = None + self.reload = False + self.stop_brain = True + self.user_code = "" + + # Time variables + self.ideal_cycle = 80 + self.measured_cycle = 80 + self.iteration_counter = 0 + self.real_time_factor = 0 + self.frequency_message = {'brain': '', 'gui': '', 'rtf': ''} + + self.server = None + self.client = None + self.host = sys.argv[1] + + # Initialize the GUI, HAL and Console behind the scenes + self.hal = HAL() + self.gui = GUI(self.host) + + # Function to parse the code + # A few assumptions: + # 1. The user always passes sequential and iterative codes + # 2. Only a single infinite loop + def parse_code(self, source_code): + sequential_code, iterative_code = self.seperate_seq_iter(source_code) + return iterative_code, sequential_code + + # Function to separate the iterative and sequential code + def seperate_seq_iter(self, source_code): + if source_code == "": + return "", "" + + # Search for an instance of while True + infinite_loop = re.search(r'[^ ]while\s*\(\s*True\s*\)\s*:|[^ ]while\s*True\s*:|[^ ]while\s*1\s*:|[^ ]while\s*\(\s*1\s*\)\s*:', source_code) + + # Separate the content inside while True and the other + # (Separating the sequential and iterative part!) + try: + start_index = infinite_loop.start() + iterative_code = source_code[start_index:] + sequential_code = source_code[:start_index] + + # Remove while True: syntax from the code + # And remove the the 4 spaces indentation before each command + iterative_code = re.sub(r'[^ ]while\s*\(\s*True\s*\)\s*:|[^ ]while\s*True\s*:|[^ ]while\s*1\s*:|[^ ]while\s*\(\s*1\s*\)\s*:', '', iterative_code) + # Add newlines to match line on bug report + extra_lines = sequential_code.count('\n') + while (extra_lines >= 0): + iterative_code = '\n' + iterative_code + extra_lines -= 1 + iterative_code = re.sub(r'^[ ]{4}', '', iterative_code, flags=re.M) + + except: + sequential_code = source_code + iterative_code = "" + + return sequential_code, iterative_code + + # The process function + def process_code(self, source_code): + # Redirect the information to console + start_console() + + iterative_code, sequential_code = self.parse_code(source_code) + + # print(sequential_code) + # print(iterative_code) + + # The Python exec function + # Run the sequential part + gui_module, hal_module = self.generate_modules() + reference_environment = {"GUI": gui_module, "HAL": hal_module} + while (self.stop_brain == True): + if (self.reload == True): + return + time.sleep(0.1) + exec(sequential_code, reference_environment) + + # Run the iterative part inside template + # and keep the check for flag + while self.reload == False: + while (self.stop_brain == True): + if (self.reload == True): + return + time.sleep(0.1) + + start_time = datetime.now() + + # Execute the iterative portion + exec(iterative_code, reference_environment) + + # Template specifics to run! + finish_time = datetime.now() + dt = finish_time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + + # Keep updating the iteration counter + if (iterative_code == ""): + self.iteration_counter = 0 + else: + self.iteration_counter = self.iteration_counter + 1 + + # The code should be run for atleast the target time step + # If it's less put to sleep + if (ms < self.ideal_cycle): + time.sleep((self.ideal_cycle - ms) / 1000.0) + + close_console() + print("Current Thread Joined!") + + # Function to generate the modules for use in ACE Editor + def generate_modules(self): + # Define HAL module + hal_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) + hal_module.HAL = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) + # hal_module.drone = imp.new_module("drone") + # motors# hal_module.HAL.motors = imp.new_module("motors") + + # Add HAL functions + hal_module.HAL.get_frontal_image = self.hal.get_frontal_image + hal_module.HAL.get_ventral_image = self.hal.get_ventral_image + hal_module.HAL.get_position = self.hal.get_position + hal_module.HAL.get_velocity = self.hal.get_velocity + hal_module.HAL.get_yaw_rate = self.hal.get_yaw_rate + hal_module.HAL.get_orientation = self.hal.get_orientation + hal_module.HAL.get_roll = self.hal.get_roll + hal_module.HAL.get_pitch = self.hal.get_pitch + hal_module.HAL.get_yaw = self.hal.get_yaw + hal_module.HAL.get_landed_state = self.hal.get_landed_state + hal_module.HAL.set_cmd_pos = self.hal.set_cmd_pos + hal_module.HAL.set_cmd_vel = self.hal.set_cmd_vel + hal_module.HAL.set_cmd_mix = self.hal.set_cmd_mix + hal_module.HAL.takeoff = self.hal.takeoff + hal_module.HAL.land = self.hal.land + + # Define GUI module + gui_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) + gui_module.GUI = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) + + # Add GUI functions + gui_module.GUI.showImage = self.gui.showImage + gui_module.GUI.showLeftImage = self.gui.showLeftImage + + # Adding modules to system + # Protip: The names should be different from + # other modules, otherwise some errors + sys.modules["HAL"] = hal_module + sys.modules["GUI"] = gui_module + + return gui_module, hal_module + + # Function to measure the frequency of iterations + def measure_frequency(self): + previous_time = datetime.now() + # An infinite loop + while True: + # Sleep for 2 seconds + time.sleep(2) + + # Measure the current time and subtract from the previous time to get real time interval + current_time = datetime.now() + dt = current_time - previous_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + previous_time = current_time + + # Get the time period + try: + # Division by zero + self.measured_cycle = ms / self.iteration_counter + except: + self.measured_cycle = 0 + + # Reset the counter + self.iteration_counter = 0 + + # Send to client + self.send_frequency_message() + + # Function to generate and send frequency messages + def send_frequency_message(self): + # This function generates and sends frequency measures of the brain and gui + brain_frequency = 0; gui_frequency = 0 + try: + brain_frequency = round(1000 / self.measured_cycle, 1) + except ZeroDivisionError: + brain_frequency = 0 + + try: + gui_frequency = round(1000 / self.thread_gui.measured_cycle, 1) + except ZeroDivisionError: + gui_frequency = 0 + + self.frequency_message["brain"] = brain_frequency + self.frequency_message["gui"] = gui_frequency + self.frequency_message["rtf"] = self.real_time_factor + + message = "#freq" + json.dumps(self.frequency_message) + self.server.send_message(self.client, message) + + def send_ping_message(self): + self.server.send_message(self.client, "#ping") + + # Function to notify the front end that the code was received and sent to execution + def send_code_message(self): + self.server.send_message(self.client, "#exec") + + # Function to track the real time factor from Gazebo statistics + # https://stackoverflow.com/a/17698359 + # (For reference, Python3 solution specified in the same answer) + def track_stats(self): + args = ["gz", "stats", "-p"] + # Prints gz statistics. "-p": Output comma-separated values containing- + # real-time factor (percent), simtime (sec), realtime (sec), paused (T or F) + stats_process = subprocess.Popen(args, stdout=subprocess.PIPE, bufsize=1, universal_newlines=True) + # bufsize=1 enables line-bufferred mode (the input buffer is flushed + # automatically on newlines if you would write to process.stdin ) + with stats_process.stdout: + for line in iter(stats_process.stdout.readline, ''): + stats_list = [x.strip() for x in line.split(',')] + self.real_time_factor = stats_list[0] + + # Function to maintain thread execution + def execute_thread(self, source_code): + # Keep checking until the thread is alive + # The thread will die when the coming iteration reads the flag + if self.thread is not None: + while self.thread.is_alive(): + time.sleep(0.2) + + # Turn the flag down, the iteration has successfully stopped! + self.reload = False + # New thread execution + self.thread = threading.Thread(target=self.process_code, args=[source_code]) + self.thread.start() + self.send_code_message() + print("New Thread Started!") + + # Function to read and set frequency from incoming message + def read_frequency_message(self, message): + frequency_message = json.loads(message) + + # Set brain frequency + frequency = float(frequency_message["brain"]) + self.ideal_cycle = 1000.0 / frequency + + # Set gui frequency + frequency = float(frequency_message["gui"]) + self.thread_gui.ideal_cycle = 1000.0 / frequency + + return + + # The websocket function + # Gets called when there is an incoming message from the client + def handle(self, client, server, message): + if message[:5] == "#freq": + frequency_message = message[5:] + self.read_frequency_message(frequency_message) + time.sleep(1) + return + + elif(message[:5] == "#ping"): + time.sleep(1) + self.send_ping_message() + return + + elif (message[:5] == "#code"): + try: + # Once received turn the reload flag up and send it to execute_thread function + self.user_code = message[6:] + # print(repr(code)) + self.reload = True + self.execute_thread(self.user_code) + except: + pass + + elif (message[:5] == "#rest"): + try: + self.reload = True + self.stop_brain = True + self.execute_thread(self.user_code) + except: + pass + + elif (message[:5] == "#stop"): + self.stop_brain = True + + elif (message[:5] == "#play"): + self.stop_brain = False + + # Function that gets called when the server is connected + def connected(self, client, server): + self.client = client + # Start the GUI update thread + self.thread_gui = ThreadGUI(self.gui) + self.thread_gui.start() + + # Start the real time factor tracker thread + self.stats_thread = threading.Thread(target=self.track_stats) + self.stats_thread.start() + + # Start measure frequency + self.measure_thread = threading.Thread(target=self.measure_frequency) + self.measure_thread.start() + + print(client, 'connected') + + # Function that gets called when the connected closes + def handle_close(self, client, server): + print(client, 'closed') + + def run_server(self): + self.server = WebsocketServer(port=1905, host=self.host) + self.server.set_fn_new_client(self.connected) + self.server.set_fn_client_left(self.handle_close) + self.server.set_fn_message_received(self.handle) + + logged = False + while not logged: + try: + f = open("/ws_code.log", "w") + f.write("websocket_code=ready") + f.close() + logged = True + except: + time.sleep(0.1) + + self.server.run_forever() + + +# Execute! +if __name__ == "__main__": + server = Template() + server.run_server() \ No newline at end of file diff --git a/exercises/static/exercises/labyrinth_escape/web-template/gui.py b/exercises/static/exercises/labyrinth_escape/web-template/gui.py new file mode 100644 index 000000000..51d327f1a --- /dev/null +++ b/exercises/static/exercises/labyrinth_escape/web-template/gui.py @@ -0,0 +1,248 @@ +import json +import cv2 +import base64 +import threading +import time +from datetime import datetime +from websocket_server import WebsocketServer + + +# Graphical User Interface Class +class GUI: + # Initialization function + # The actual initialization + def __init__(self, host): + t = threading.Thread(target=self.run_server) + + self.payload = {'image': ''} + self.left_payload = {'image': ''} + self.server = None + self.client = None + + self.host = host + + # Image variables + self.image_to_be_shown = None + self.image_to_be_shown_updated = False + self.image_show_lock = threading.Lock() + + self.left_image_to_be_shown = None + self.left_image_to_be_shown_updated = False + self.left_image_show_lock = threading.Lock() + + self.acknowledge = False + self.acknowledge_lock = threading.Lock() + + # Take the console object to set the same websocket and client + t.start() + + # Explicit initialization function + # Class method, so user can call it without instantiation + @classmethod + def initGUI(cls, host): + # self.payload = {'image': '', 'shape': []} + new_instance = cls(host) + return new_instance + + # Function to prepare image payload + # Encodes the image as a JSON string and sends through the WS + def payloadImage(self): + self.image_show_lock.acquire() + image_to_be_shown_updated = self.image_to_be_shown_updated + image_to_be_shown = self.image_to_be_shown + self.image_show_lock.release() + + image = image_to_be_shown + payload = {'image': '', 'shape': ''} + + if not image_to_be_shown_updated: + return payload + + shape = image.shape + frame = cv2.imencode('.JPEG', image)[1] + encoded_image = base64.b64encode(frame) + + payload['image'] = encoded_image.decode('utf-8') + payload['shape'] = shape + + self.image_show_lock.acquire() + self.image_to_be_shown_updated = False + self.image_show_lock.release() + + return payload + + # Function to prepare image payload + # Encodes the image as a JSON string and sends through the WS + def payloadLeftImage(self): + self.left_image_show_lock.acquire() + left_image_to_be_shown_updated = self.left_image_to_be_shown_updated + left_image_to_be_shown = self.left_image_to_be_shown + self.left_image_show_lock.release() + + image = left_image_to_be_shown + payload = {'image': '', 'shape': ''} + + if not left_image_to_be_shown_updated: + return payload + + shape = image.shape + frame = cv2.imencode('.JPEG', image)[1] + encoded_image = base64.b64encode(frame) + + payload['image'] = encoded_image.decode('utf-8') + payload['shape'] = shape + + self.left_image_show_lock.acquire() + self.left_image_to_be_shown_updated = False + self.left_image_show_lock.release() + + return payload + + # Function for student to call + def showImage(self, image): + self.image_show_lock.acquire() + self.image_to_be_shown = image + self.image_to_be_shown_updated = True + self.image_show_lock.release() + + # Function for student to call + def showLeftImage(self, image): + self.left_image_show_lock.acquire() + self.left_image_to_be_shown = image + self.left_image_to_be_shown_updated = True + self.left_image_show_lock.release() + + # Function to get the client + # Called when a new client is received + def get_client(self, client, server): + self.client = client + + # Function to get value of Acknowledge + def get_acknowledge(self): + self.acknowledge_lock.acquire() + acknowledge = self.acknowledge + self.acknowledge_lock.release() + + return acknowledge + + # Function to get value of Acknowledge + def set_acknowledge(self, value): + self.acknowledge_lock.acquire() + self.acknowledge = value + self.acknowledge_lock.release() + + # Update the gui + def update_gui(self): + # Payload Image Message + payload = self.payloadImage() + self.payload["image"] = json.dumps(payload) + + message = "#gui" + json.dumps(self.payload) + self.server.send_message(self.client, message) + + # Payload Left Image Message + left_payload = self.payloadLeftImage() + self.left_payload["image"] = json.dumps(left_payload) + + message = "#gul" + json.dumps(self.left_payload) + self.server.send_message(self.client, message) + + # Function to read the message from websocket + # Gets called when there is an incoming message from the client + def get_message(self, client, server, message): + # Acknowledge Message for GUI Thread + if message[:4] == "#ack": + self.set_acknowledge(True) + + # Activate the server + def run_server(self): + self.server = WebsocketServer(port=2303, host=self.host) + self.server.set_fn_new_client(self.get_client) + self.server.set_fn_message_received(self.get_message) + + logged = False + while not logged: + try: + f = open("/ws_gui.log", "w") + f.write("websocket_gui=ready") + f.close() + logged = True + except: + time.sleep(0.1) + + self.server.run_forever() + + # Function to reset + def reset_gui(self): + pass + + +# This class decouples the user thread +# and the GUI update thread +class ThreadGUI: + def __init__(self, gui): + self.gui = gui + + # Time variables + self.ideal_cycle = 80 + self.measured_cycle = 80 + self.iteration_counter = 0 + + # Function to start the execution of threads + def start(self): + self.measure_thread = threading.Thread(target=self.measure_thread) + self.thread = threading.Thread(target=self.run) + + self.measure_thread.start() + self.thread.start() + + print("GUI Thread Started!") + + # The measuring thread to measure frequency + def measure_thread(self): + while self.gui.client is None: + pass + + previous_time = datetime.now() + while True: + # Sleep for 2 seconds + time.sleep(2) + + # Measure the current time and subtract from previous time to get real time interval + current_time = datetime.now() + dt = current_time - previous_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + previous_time = current_time + + # Get the time period + try: + # Division by zero + self.measured_cycle = ms / self.iteration_counter + except: + self.measured_cycle = 0 + + # Reset the counter + self.iteration_counter = 0 + + # The main thread of execution + def run(self): + while self.gui.client is None: + pass + + while True: + start_time = datetime.now() + self.gui.update_gui() + acknowledge_message = self.gui.get_acknowledge() + + while not acknowledge_message: + acknowledge_message = self.gui.get_acknowledge() + + self.gui.set_acknowledge(False) + + finish_time = datetime.now() + self.iteration_counter = self.iteration_counter + 1 + + dt = finish_time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + if ms < self.ideal_cycle: + time.sleep((self.ideal_cycle-ms) / 1000.0) diff --git a/exercises/static/exercises/labyrinth_escape/web-template/hal.py b/exercises/static/exercises/labyrinth_escape/web-template/hal.py new file mode 100644 index 000000000..25635a119 --- /dev/null +++ b/exercises/static/exercises/labyrinth_escape/web-template/hal.py @@ -0,0 +1,82 @@ +import numpy as np +import rospy +import cv2 + +from drone_wrapper import DroneWrapper + + +# Hardware Abstraction Layer +class HAL: + IMG_WIDTH = 320 + IMG_HEIGHT = 240 + + def __init__(self): + rospy.init_node("HAL") + + self.image = None + self.drone = DroneWrapper(name="rqt") + + # Explicit initialization functions + # Class method, so user can call it without instantiation + @classmethod + def initRobot(cls): + new_instance = cls() + return new_instance + + # Get Image from ROS Driver Camera + def get_frontal_image(self): + image = self.drone.get_frontal_image() + image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) + return image_rgb + + def get_ventral_image(self): + image = self.drone.get_ventral_image() + image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) + return image_rgb + + def get_position(self): + pos = self.drone.get_position() + return pos + + def get_velocity(self): + vel = self.drone.get_velocity() + return vel + + def get_yaw_rate(self): + yaw_rate = self.drone.get_yaw_rate() + return yaw_rate + + def get_orientation(self): + orientation = self.drone.get_orientation() + return orientation + + def get_roll(self): + roll = self.drone.get_roll() + return roll + + def get_pitch(self): + pitch = self.drone.get_pitch() + return pitch + + def get_yaw(self): + yaw = self.drone.get_yaw() + return yaw + + def get_landed_state(self): + state = self.drone.get_landed_state() + return state + + def set_cmd_pos(self, x, y, z, az): + self.drone.set_cmd_pos(x, y, z, az) + + def set_cmd_vel(self, vx, vy, vz, az): + self.drone.set_cmd_vel(vx, vy, vz, az) + + def set_cmd_mix(self, vx, vy, z, az): + self.drone.set_cmd_mix(vx, vy, z, az) + + def takeoff(self, h=3): + self.drone.takeoff(h) + + def land(self): + self.drone.land() diff --git a/exercises/static/exercises/labyrinth_escape/web-template/interfaces/__init__.py b/exercises/static/exercises/labyrinth_escape/web-template/interfaces/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/exercises/static/exercises/labyrinth_escape/web-template/interfaces/camera.py b/exercises/static/exercises/labyrinth_escape/web-template/interfaces/camera.py new file mode 100644 index 000000000..5a021a13e --- /dev/null +++ b/exercises/static/exercises/labyrinth_escape/web-template/interfaces/camera.py @@ -0,0 +1,89 @@ +import rospy +from sensor_msgs.msg import Image as ImageROS +import threading +from math import pi as PI +import cv2 +from cv_bridge import CvBridge, CvBridgeError + + +MAXRANGE = 8 # max length received from imageD +MINRANGE = 0 + + +def imageMsg2Image(img, bridge): + + image = Image() + + image.width = img.width + image.height = img.height + image.format = "BGR8" + image.timeStamp = img.header.stamp.secs + (img.header.stamp.nsecs * 1e-9) + cv_image = 0 + if img.encoding[-2:] == "C1": + gray_img_buff = bridge.imgmsg_to_cv2(img, img.encoding) + cv_image = depthToRGB8(gray_img_buff, img.encoding) + else: + cv_image = bridge.imgmsg_to_cv2(img, "bgr8") + image.data = cv_image + return image + + +import numpy as np + + +class Image: + + def __init__(self): + + self.height = 3 # Image height [pixels] + self.width = 3 # Image width [pixels] + self.timeStamp = 0 # Time stamp [s] */ + self.format = "" # Image format string (RGB8, BGR,...) + self.data = np.zeros((self.height, self.width, 3), np.uint8) # The image data itself + self.data.shape = self.height, self.width, 3 + + def __str__(self): + s = "Image: {\n height: " + str(self.height) + "\n width: " + str(self.width) + s = s + "\n format: " + self.format + "\n timeStamp: " + str(self.timeStamp) + s = s + "\n data: " + str(self.data) + "\n}" + return s + + +class ListenerCamera: + + def __init__(self, topic): + + self.topic = topic + self.data = Image() + self.sub = None + self.lock = threading.Lock() + + self.bridge = CvBridge() + self.start() + + def __callback(self, img): + + image = imageMsg2Image(img, self.bridge) + + self.lock.acquire() + self.data = image + self.lock.release() + + def stop(self): + + self.sub.unregister() + + def start(self): + self.sub = rospy.Subscriber(self.topic, ImageROS, self.__callback) + + def getImage(self): + + self.lock.acquire() + image = self.data + self.lock.release() + + return image + + def hasproxy(self): + + return hasattr(self, "sub") and self.sub diff --git a/exercises/static/exercises/labyrinth_escape/web-template/interfaces/motors.py b/exercises/static/exercises/labyrinth_escape/web-template/interfaces/motors.py new file mode 100644 index 000000000..70dca8a46 --- /dev/null +++ b/exercises/static/exercises/labyrinth_escape/web-template/interfaces/motors.py @@ -0,0 +1,123 @@ +import rospy +from geometry_msgs.msg import Twist +import threading +from math import pi as PI +from .threadPublisher import ThreadPublisher + + + +def cmdvel2Twist(vel): + + tw = Twist() + tw.linear.x = vel.vx + tw.linear.y = vel.vy + tw.linear.z = vel.vz + tw.angular.x = vel.ax + tw.angular.y = vel.ay + tw.angular.z = vel.az + + return tw + + +class CMDVel (): + + def __init__(self): + + self.vx = 0 # vel in x[m/s] (use this for V in wheeled robots) + self.vy = 0 # vel in y[m/s] + self.vz = 0 # vel in z[m/s] + self.ax = 0 # angular vel in X axis [rad/s] + self.ay = 0 # angular vel in X axis [rad/s] + self.az = 0 # angular vel in Z axis [rad/s] (use this for W in wheeled robots) + self.timeStamp = 0 # Time stamp [s] + + + def __str__(self): + s = "CMDVel: {\n vx: " + str(self.vx) + "\n vy: " + str(self.vy) + s = s + "\n vz: " + str(self.vz) + "\n ax: " + str(self.ax) + s = s + "\n ay: " + str(self.ay) + "\n az: " + str(self.az) + s = s + "\n timeStamp: " + str(self.timeStamp) + "\n}" + return s + +class PublisherMotors: + + def __init__(self, topic, maxV, maxW): + + self.maxW = maxW + self.maxV = maxV + + self.topic = topic + self.data = CMDVel() + self.pub = rospy.Publisher(self.topic, Twist, queue_size=1) + + self.lock = threading.Lock() + + self.kill_event = threading.Event() + self.thread = ThreadPublisher(self, self.kill_event) + + self.thread.daemon = True + self.start() + + def publish (self): + + self.lock.acquire() + tw = cmdvel2Twist(self.data) + self.lock.release() + self.pub.publish(tw) + + def stop(self): + + self.kill_event.set() + self.pub.unregister() + + def start (self): + + self.kill_event.clear() + self.thread.start() + + + + def getMaxW(self): + return self.maxW + + def getMaxV(self): + return self.maxV + + + def sendVelocities(self, vel): + + self.lock.acquire() + self.data = vel + self.lock.release() + + def sendV(self, v): + + self.sendVX(v) + + def sendL(self, l): + + self.sendVY(l) + + def sendW(self, w): + + self.sendAZ(w) + + def sendVX(self, vx): + + self.lock.acquire() + self.data.vx = vx + self.lock.release() + + def sendVY(self, vy): + + self.lock.acquire() + self.data.vy = vy + self.lock.release() + + def sendAZ(self, az): + + self.lock.acquire() + self.data.az = az + self.lock.release() + + diff --git a/exercises/static/exercises/labyrinth_escape/web-template/interfaces/pose3d.py b/exercises/static/exercises/labyrinth_escape/web-template/interfaces/pose3d.py new file mode 100644 index 000000000..fd0bfc37a --- /dev/null +++ b/exercises/static/exercises/labyrinth_escape/web-template/interfaces/pose3d.py @@ -0,0 +1,176 @@ +import rospy +import threading +from math import asin, atan2, pi +from nav_msgs.msg import Odometry + +def quat2Yaw(qw, qx, qy, qz): + ''' + Translates from Quaternion to Yaw. + + @param qw,qx,qy,qz: Quaternion values + + @type qw,qx,qy,qz: float + + @return Yaw value translated from Quaternion + + ''' + rotateZa0=2.0*(qx*qy + qw*qz) + rotateZa1=qw*qw + qx*qx - qy*qy - qz*qz + rotateZ=0.0 + if(rotateZa0 != 0.0 and rotateZa1 != 0.0): + rotateZ=atan2(rotateZa0,rotateZa1) + return rotateZ + +def quat2Pitch(qw, qx, qy, qz): + ''' + Translates from Quaternion to Pitch. + + @param qw,qx,qy,qz: Quaternion values + + @type qw,qx,qy,qz: float + + @return Pitch value translated from Quaternion + + ''' + + rotateYa0=-2.0*(qx*qz - qw*qy) + rotateY=0.0 + if(rotateYa0 >= 1.0): + rotateY = pi/2.0 + elif(rotateYa0 <= -1.0): + rotateY = -pi/2.0 + else: + rotateY = asin(rotateYa0) + + return rotateY + +def quat2Roll (qw, qx, qy, qz): + ''' + Translates from Quaternion to Roll. + + @param qw,qx,qy,qz: Quaternion values + + @type qw,qx,qy,qz: float + + @return Roll value translated from Quaternion + + ''' + rotateXa0=2.0*(qy*qz + qw*qx) + rotateXa1=qw*qw - qx*qx - qy*qy + qz*qz + rotateX=0.0 + + if(rotateXa0 != 0.0 and rotateXa1 != 0.0): + rotateX=atan2(rotateXa0, rotateXa1) + return rotateX + + +def odometry2Pose3D(odom): + ''' + Translates from ROS Odometry to JderobotTypes Pose3d. + + @param odom: ROS Odometry to translate + + @type odom: Odometry + + @return a Pose3d translated from odom + + ''' + pose = Pose3d() + ori = odom.pose.pose.orientation + + pose.x = odom.pose.pose.position.x + pose.y = odom.pose.pose.position.y + pose.z = odom.pose.pose.position.z + #pose.h = odom.pose.pose.position.h + pose.yaw = quat2Yaw(ori.w, ori.x, ori.y, ori.z) + pose.pitch = quat2Pitch(ori.w, ori.x, ori.y, ori.z) + pose.roll = quat2Roll(ori.w, ori.x, ori.y, ori.z) + pose.q = [ori.w, ori.x, ori.y, ori.z] + pose.timeStamp = odom.header.stamp.secs + (odom.header.stamp.nsecs *1e-9) + + return pose + +class Pose3d (): + + def __init__(self): + + self.x = 0 # X coord [meters] + self.y = 0 # Y coord [meters] + self.z = 0 # Z coord [meters] + self.h = 1 # H param + self.yaw = 0 #Yaw angle[rads] + self.pitch = 0 # Pitch angle[rads] + self.roll = 0 # Roll angle[rads] + self.q = [0,0,0,0] # Quaternion + self.timeStamp = 0 # Time stamp [s] + + + def __str__(self): + s = "Pose3D: {\n x: " + str(self.x) + "\n Y: " + str(self.y) + s = s + "\n Z: " + str(self.z) + "\n H: " + str(self.h) + s = s + "\n Yaw: " + str(self.yaw) + "\n Pitch: " + str(self.pitch) + "\n Roll: " + str(self.roll) + s = s + "\n quaternion: " + str(self.q) + "\n timeStamp: " + str(self.timeStamp) + "\n}" + return s + + +class ListenerPose3d: + ''' + ROS Pose3D Subscriber. Pose3D Client to Receive pose3d from ROS nodes. + ''' + def __init__(self, topic): + ''' + ListenerPose3d Constructor. + + @param topic: ROS topic to subscribe + + @type topic: String + + ''' + self.topic = topic + self.data = Pose3d() + self.sub = None + self.lock = threading.Lock() + self.start() + + def __callback (self, odom): + ''' + Callback function to receive and save Pose3d. + + @param odom: ROS Odometry received + + @type odom: Odometry + + ''' + pose = odometry2Pose3D(odom) + + self.lock.acquire() + self.data = pose + self.lock.release() + + def stop(self): + ''' + Stops (Unregisters) the client. + + ''' + self.sub.unregister() + + def start (self): + ''' + Starts (Subscribes) the client. + + ''' + self.sub = rospy.Subscriber(self.topic, Odometry, self.__callback) + + def getPose3d(self): + ''' + Returns last Pose3d. + + @return last JdeRobotTypes Pose3d saved + + ''' + self.lock.acquire() + pose = self.data + self.lock.release() + + return pose + diff --git a/exercises/static/exercises/labyrinth_escape/web-template/interfaces/threadPublisher.py b/exercises/static/exercises/labyrinth_escape/web-template/interfaces/threadPublisher.py new file mode 100644 index 000000000..69aa0ad48 --- /dev/null +++ b/exercises/static/exercises/labyrinth_escape/web-template/interfaces/threadPublisher.py @@ -0,0 +1,46 @@ +# +# Copyright (C) 1997-2016 JDE Developers Team +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. +# Authors : +# Alberto Martin Florido +# Aitor Martinez Fernandez +# +import threading +import time +from datetime import datetime + +time_cycle = 80 + + +class ThreadPublisher(threading.Thread): + + def __init__(self, pub, kill_event): + self.pub = pub + self.kill_event = kill_event + threading.Thread.__init__(self, args=kill_event) + + def run(self): + while (not self.kill_event.is_set()): + start_time = datetime.now() + + self.pub.publish() + + finish_Time = datetime.now() + + dt = finish_Time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + #print (ms) + if (ms < time_cycle): + time.sleep((time_cycle - ms) / 1000.0) \ No newline at end of file diff --git a/exercises/static/exercises/labyrinth_escape/web-template/interfaces/threadStoppable.py b/exercises/static/exercises/labyrinth_escape/web-template/interfaces/threadStoppable.py new file mode 100644 index 000000000..b631d180f --- /dev/null +++ b/exercises/static/exercises/labyrinth_escape/web-template/interfaces/threadStoppable.py @@ -0,0 +1,36 @@ +import threading +import time +from datetime import datetime + +time_cycle = 80 + + +class StoppableThread(threading.Thread): + """Thread class with a stop() method. The thread itself has to check + regularly for the stopped() condition.""" + + def __init__(self, target, kill_event=threading.Event(), *args, **kwargs): + super(StoppableThread, self).__init__(*args, **kwargs) + self._target = target + self._target_args = kwargs["args"] + self._kill_event = kill_event + + def run(self): + while not self.stopped(): + start_time = datetime.now() + + self._target(*self._target_args) + + finish_time = datetime.now() + + dt = finish_time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + # print (ms) + if ms < time_cycle: + time.sleep((time_cycle - ms) / 1000.0) + + def stop(self): + self._kill_event.set() + + def stopped(self): + return self._kill_event.is_set() diff --git a/exercises/static/exercises/labyrinth_escape/web-template/labyrinth_escape.world b/exercises/static/exercises/labyrinth_escape/web-template/labyrinth_escape.world new file mode 100644 index 000000000..c78d5586d --- /dev/null +++ b/exercises/static/exercises/labyrinth_escape/web-template/labyrinth_escape.world @@ -0,0 +1,114 @@ + + + + + + + + model://arrow + arrow_1 + -18 -8.5 0.01 0 0 0 + + + model://arrow + arrow_2 + -5.5 -8.5 0.01 0 0 0 + + + model://arrow + arrow_3 + 7 -8.5 0.01 0 0 1.57 + + + model://arrow + arrow_4 + 7 3 0.01 0 0 1.57 + + + model://arrow + arrow_5 + 7 14.5 0.01 0 0 3.14 + + + model://arrow + arrow_6 + 2.5 14.5 0.01 0 0 3.14 + + + model://arrow + arrow_7 + 2.5 7.5.01 0 0 3.14 + + + model://arrow + arrow_8 + -3 7.5 0.01 0 0 1.57 + + + model://arrow + arrow_9 + -3 20.5 0.01 0 0 3.14 + + + model://arrow + arrow_10 + -13 20.5 0.01 0 0 3.14 + + + + model://simple_labyrinth_green + + + + + model://grass_plane + + + + + logo + model://logoJdeRobot + -26.0 0.0 0.0 0.0 0.0 0.0 + + + + 0 + 0.4 0.4 0.4 1.0 + 0.7 0.7 0.7 1 + + + 12 + + + + + + + model://sun + + + + + 0 0 -9.8066 + + + quick + 10 + 1.3 + 0 + + + 0 + 0.2 + 100 + 0.001 + + + 0.004 + 1 + 250 + 6.0e-6 2.3e-5 -4.2e-5 + + + + \ No newline at end of file diff --git a/exercises/static/exercises/labyrinth_escape/web-template/launch/gazebo.launch b/exercises/static/exercises/labyrinth_escape/web-template/launch/gazebo.launch new file mode 100644 index 000000000..021500295 --- /dev/null +++ b/exercises/static/exercises/labyrinth_escape/web-template/launch/gazebo.launch @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/exercises/static/exercises/labyrinth_escape/web-template/launch/launch.py b/exercises/static/exercises/labyrinth_escape/web-template/launch/launch.py new file mode 100644 index 000000000..2abb93395 --- /dev/null +++ b/exercises/static/exercises/labyrinth_escape/web-template/launch/launch.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python3 + +import stat +import rospy +from os import lstat +from subprocess import Popen, PIPE + + +DRI_PATH = "/dev/dri/card0" +EXERCISE = "labyrinth_escape" +TIMEOUT = 30 +MAX_ATTEMPT = 2 + + +# Check if acceleration can be enabled +def check_device(device_path): + try: + return stat.S_ISCHR(lstat(device_path)[stat.ST_MODE]) + except: + return False + + +# Spawn new process +def spawn_process(args, insert_vglrun=False): + if insert_vglrun: + args.insert(0, "vglrun") + process = Popen(args, stdout=PIPE, bufsize=1, universal_newlines=True) + return process + + +class Test(): + def gazebo(self): + rospy.logwarn("[GAZEBO] Launching") + try: + rospy.wait_for_service("/gazebo/get_model_properties", TIMEOUT) + return True + except rospy.ROSException: + return False + + def px4(self): + rospy.logwarn("[PX4-SITL] Launching") + start_time = rospy.get_time() + args = ["./PX4-Autopilot/build/px4_sitl_default/bin/px4-commander","--instance", "0", "check"] + while rospy.get_time() - start_time < TIMEOUT: + process = spawn_process(args, insert_vglrun=False) + with process.stdout: + for line in iter(process.stdout.readline, ''): + if ("Prearm check: OK" in line): + return True + rospy.sleep(2) + return False + + def mavros(self, ns=""): + rospy.logwarn("[MAVROS] Launching") + try: + rospy.wait_for_service(ns + "/mavros/cmd/arming", TIMEOUT) + return True + except rospy.ROSException: + return False + + +class Launch(): + def __init__(self): + self.test = Test() + self.acceleration_enabled = check_device(DRI_PATH) + + # Start roscore + args = ["/opt/ros/noetic/bin/roscore"] + spawn_process(args, insert_vglrun=False) + + rospy.init_node("launch", anonymous=True) + + def start(self): + ######## LAUNCH GAZEBO ######## + args = ["/opt/ros/noetic/bin/roslaunch", + "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/gazebo.launch", + "--wait", + "--log" + ] + + attempt = 1 + while True: + spawn_process(args, insert_vglrun=self.acceleration_enabled) + if self.test.gazebo() == True: + break + if attempt == MAX_ATTEMPT: + rospy.logerr("[GAZEBO] Launch Failed") + return + attempt = attempt + 1 + + + ######## LAUNCH PX4 ######## + args = ["/opt/ros/noetic/bin/roslaunch", + "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/px4.launch", + "--log" + ] + + attempt = 1 + while True: + spawn_process(args, insert_vglrun=self.acceleration_enabled) + if self.test.px4() == True: + break + if attempt == MAX_ATTEMPT: + rospy.logerr("[PX4] Launch Failed") + return + attempt = attempt + 1 + + + ######## LAUNCH MAVROS ######## + args = ["/opt/ros/noetic/bin/roslaunch", + "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/mavros.launch", + "--log" + ] + + attempt = 1 + while True: + spawn_process(args, insert_vglrun=self.acceleration_enabled) + if self.test.mavros() == True: + break + if attempt == MAX_ATTEMPT: + rospy.logerr("[MAVROS] Launch Failed") + return + attempt = attempt + 1 + + +if __name__ == "__main__": + launch = Launch() + launch.start() + + with open("/drones_launch.log", "w") as f: + f.write("success") diff --git a/exercises/static/exercises/labyrinth_escape/web-template/launch/mavros.launch b/exercises/static/exercises/labyrinth_escape/web-template/launch/mavros.launch new file mode 100644 index 000000000..b899c0ec1 --- /dev/null +++ b/exercises/static/exercises/labyrinth_escape/web-template/launch/mavros.launch @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/exercises/static/exercises/labyrinth_escape/web-template/launch/px4.launch b/exercises/static/exercises/labyrinth_escape/web-template/launch/px4.launch new file mode 100644 index 000000000..43ed14611 --- /dev/null +++ b/exercises/static/exercises/labyrinth_escape/web-template/launch/px4.launch @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/exercises/static/exercises/package_delivery/README.md b/exercises/static/exercises/package_delivery/README.md new file mode 100755 index 000000000..b883075de --- /dev/null +++ b/exercises/static/exercises/package_delivery/README.md @@ -0,0 +1 @@ +[Exercise Documentation Website](https://jderobot.github.io/RoboticsAcademy/exercises/Drones/package_delivery) diff --git a/exercises/static/exercises/package_delivery/magnet.py b/exercises/static/exercises/package_delivery/magnet.py new file mode 100644 index 000000000..75a7673ac --- /dev/null +++ b/exercises/static/exercises/package_delivery/magnet.py @@ -0,0 +1,73 @@ +import rospy +from math import sqrt +from gazebo_msgs.msg import ModelState +from gazebo_msgs.srv import GetModelState +from gazebo_msgs.srv import SetModelState +from threading import Event, Thread + + +MIN_DIST = 2.0 # minimum distance required by drone to pick package + + +class Magnet: + def __init__(self): + # rospy.init_node("magnet") + self.magnetize = Event() + self.get_state = rospy.ServiceProxy('/gazebo/get_model_state', GetModelState) + self.set_state = rospy.ServiceProxy('/gazebo/set_model_state', SetModelState) + self.set_pkg_state = False + + + def start_magnet(self): + req = ModelState() + req.model_name = "package_box" + + while self.magnetize.is_set(): + drone_pos = self.get_state("typhoon_h480_dual_cam", "") + req.pose.position.x = drone_pos.pose.position.x + req.pose.position.y = drone_pos.pose.position.y + req.pose.position.z = drone_pos.pose.position.z - 0.215 + req.twist.linear.x = drone_pos.twist.linear.x + req.twist.linear.y = drone_pos.twist.linear.y + req.twist.linear.z = drone_pos.twist.linear.z + req.twist.angular.x = drone_pos.twist.angular.x + req.twist.angular.y = drone_pos.twist.angular.y + req.twist.angular.z = drone_pos.twist.angular.z + req.pose.orientation.x = drone_pos.pose.orientation.x + req.pose.orientation.y = drone_pos.pose.orientation.y + req.pose.orientation.z = drone_pos.pose.orientation.z + req.pose.orientation.w = drone_pos.pose.orientation.w + self.set_state(req) + + + def stop_magnet(self): + self.magnetize.clear() + + + def set_cmd_pick(self): + try: + pkg_pos = self.get_state("package_box", "") + drone_pos = self.get_state("typhoon_h480_dual_cam", "") + dist = sqrt(pow(pkg_pos.pose.position.x - drone_pos.pose.position.x, 2) + \ + pow(pkg_pos.pose.position.y - drone_pos.pose.position.y, 2) + \ + pow(pkg_pos.pose.position.z - drone_pos.pose.position.z, 2)) + + if dist < MIN_DIST: + self.magnetize.set() + self.set_cmd_pick_thread = Thread(target=self.start_magnet) + self.set_cmd_pick_thread.start() + self.set_pkg_state = True + except: + pass + + + def set_cmd_drop(self): + try: + self.stop_magnet() + self.set_cmd_pick_thread.join() + self.set_pkg_state = False + except: + pass + + def get_pkg_state(self): + return self.set_pkg_state diff --git a/exercises/static/exercises/package_delivery/my_solution.py b/exercises/static/exercises/package_delivery/my_solution.py new file mode 100644 index 000000000..87736399b --- /dev/null +++ b/exercises/static/exercises/package_delivery/my_solution.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python + +import rospy +import numpy as np +import cv2 +from drone_wrapper import DroneWrapper +from std_msgs.msg import Bool, Float64 +from sensor_msgs.msg import Image +from geometry_msgs.msg import Twist, Pose + +code_live_flag = False + +def gui_play_stop_cb(msg): + global code_live_flag, code_live_timer + if msg.data == True: + if not code_live_flag: + code_live_flag = True + code_live_timer = rospy.Timer(rospy.Duration(nsecs=50000000), execute) + else: + if code_live_flag: + code_live_flag = False + code_live_timer.shutdown() + + +def set_image_filtered(img): + gui_filtered_img_pub.publish(HAL.bridge.cv2_to_imgmsg(img)) + + +def set_image_threshed(img): + gui_threshed_img_pub.publish(HAL.bridge.cv2_to_imgmsg(img)) + + +def execute(event): + global HAL + img_frontal = HAL.get_frontal_image() + img_ventral = HAL.get_ventral_image() + # Both the above images are cv2 images + ################# Insert your code here ################################# + + set_image_filtered(img_frontal) + set_image_threshed(img_ventral) + + ######################################################################### + + +if __name__ == "__main__": + HAL = DroneWrapper() # Hardware Abstraction Layer aka the drone + rospy.Subscriber('gui/play_stop', Bool, gui_play_stop_cb) + gui_filtered_img_pub = rospy.Publisher('interface/filtered_img', Image, queue_size = 1) + gui_threshed_img_pub = rospy.Publisher('interface/threshed_img', Image, queue_size = 1) + code_live_flag = False + code_live_timer = rospy.Timer(rospy.Duration(nsecs=50000000), execute) + code_live_timer.shutdown() + while not rospy.is_shutdown(): + rospy.spin() diff --git a/exercises/static/exercises/package_delivery/package_delivery.launch b/exercises/static/exercises/package_delivery/package_delivery.launch new file mode 100644 index 000000000..1aea56a4b --- /dev/null +++ b/exercises/static/exercises/package_delivery/package_delivery.launch @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/exercises/static/exercises/package_delivery/package_delivery.world b/exercises/static/exercises/package_delivery/package_delivery.world new file mode 100644 index 000000000..d64fc2d8d --- /dev/null +++ b/exercises/static/exercises/package_delivery/package_delivery.world @@ -0,0 +1,238 @@ + + + + + + + -4.70385 10.895 16.2659 -0 0.921795 -1.12701 + orbit + perspective + + + + + + model://sun + + + + + model://asphalt_plane + 0 0 -0.045 0 0 + + + + + model://logoJdeRobot + 30 -5 0 0 0 0 + + + + + model://package_box + -1 -1 0.061 0 0 0 + + + + + model://logoPadRed + -1 -1 0.05 0 0 + + + + + model://logoPadGreen + -1 -4 0.05 0 0 + + + + + model://house_3 + 20 20 0 0 0 0 + + + + + model://logoBeacon + 20 15 0 0 0 0 + + + + + model://aws_robomaker_warehouse_ShelfF_01 + + -5.795143 -0.956635 0 0 0 0 + + + + model://aws_robomaker_warehouse_WallB_01 + + 0.0 0.0 0 0 0 0 + + + + model://aws_robomaker_warehouse_ShelfE_01 + + 4.73156 0.57943 0 0 0 0 + + + + model://aws_robomaker_warehouse_ShelfE_01 + + 4.73156 -4.827049 0 0 0 0 + + + + model://aws_robomaker_warehouse_ShelfE_01 + + 4.73156 -8.6651 0 0 0 0 + + + + model://aws_robomaker_warehouse_ShelfD_01 + + 4.73156 -1.242668 0 0 0 0 + + + + model://aws_robomaker_warehouse_ShelfD_01 + + 4.73156 -3.038551 0 0 0 0 + + + + model://aws_robomaker_warehouse_ShelfD_01 + + 4.73156 -6.750542 0 0 0 0 + + + + model://aws_robomaker_warehouse_GroundB_01 + + 0.0 0.0 -0.090092 0 0 0 + + + + model://aws_robomaker_warehouse_Bucket_01 + + 0.433449 9.631706 0 0 0 -1.563161 + + + + model://aws_robomaker_warehouse_Bucket_01 + + -1.8321 -6.3752 0 0 0 -1.563161 + + + + model://aws_robomaker_warehouse_Bucket_01 + + 0.433449 8.59 0 0 0 -1.563161 + + + + model://aws_robomaker_warehouse_ClutteringA_01 + + 5.708138 8.616844 -0.017477 0 0 0 + + + + model://aws_robomaker_warehouse_ClutteringA_01 + + 3.408638 8.616844 -0.017477 0 0 0 + + + + model://aws_robomaker_warehouse_ClutteringA_01 + + -1.491287 5.222435 -0.017477 0 0 -1.583185 + + + + model://aws_robomaker_warehouse_ClutteringC_01 + + 3.324959 3.822449 -0.012064 0 0 1.563871 + + + + model://aws_robomaker_warehouse_ClutteringC_01 + + 5.54171 3.816475 -0.015663 0 0 -1.583191 + + + + model://aws_robomaker_warehouse_ClutteringC_01 + + 5.384239 6.137154 0 0 0 3.150000 + + + + model://aws_robomaker_warehouse_ClutteringC_01 + + 3.236 6.137154 0 0 0 3.150000 + + + + model://aws_robomaker_warehouse_ClutteringC_01 + + -1.573677 2.301994 -0.015663 0 0 -3.133191 + + + + model://aws_robomaker_warehouse_ClutteringC_01 + + -1.2196 9.407 -0.015663 0 0 1.563871 + + + + model://aws_robomaker_warehouse_ClutteringD_01 + + -1.634682 -7.811813 -0.319559 0 0 0 + + + + model://aws_robomaker_warehouse_TrashCanC_01 + + -1.592441 7.715420 0 0 0 0 + + + + model://aws_robomaker_warehouse_PalletJackB_01 + + -0.276098 -9.481944 0.023266 0 0 0 + + + + 0 + 0.4 0.4 0.4 1.0 + 0.7 0.7 0.7 1 + + + 12 + + + + + + 0 0 -9.8066 + + + quick + 10 + 1.3 + 0 + + + 0 + 0.2 + 100 + 0.001 + + + 0.004 + 1 + 250 + 6.0e-6 2.3e-5 -4.2e-5 + + + \ No newline at end of file diff --git a/exercises/static/exercises/package_delivery/package_delivery.yaml b/exercises/static/exercises/package_delivery/package_delivery.yaml new file mode 100644 index 000000000..e93828f54 --- /dev/null +++ b/exercises/static/exercises/package_delivery/package_delivery.yaml @@ -0,0 +1 @@ +drone_model: "typhoon_h480_dual_cam" \ No newline at end of file diff --git a/exercises/static/exercises/package_delivery/web-template/README.md b/exercises/static/exercises/package_delivery/web-template/README.md new file mode 100644 index 000000000..b883075de --- /dev/null +++ b/exercises/static/exercises/package_delivery/web-template/README.md @@ -0,0 +1 @@ +[Exercise Documentation Website](https://jderobot.github.io/RoboticsAcademy/exercises/Drones/package_delivery) diff --git a/exercises/static/exercises/package_delivery/web-template/code/academy.py b/exercises/static/exercises/package_delivery/web-template/code/academy.py new file mode 100644 index 000000000..7a59ce7f5 --- /dev/null +++ b/exercises/static/exercises/package_delivery/web-template/code/academy.py @@ -0,0 +1,8 @@ +# Enter sequential code! +from GUI import GUI +from HAL import HAL + +while True: + # Enter iterative code! + img = HAL.get_ventral_image() + GUI.showImage(img) \ No newline at end of file diff --git a/exercises/static/exercises/package_delivery/web-template/console.py b/exercises/static/exercises/package_delivery/web-template/console.py new file mode 100755 index 000000000..23d0efad3 --- /dev/null +++ b/exercises/static/exercises/package_delivery/web-template/console.py @@ -0,0 +1,18 @@ +# Functions to start and close console +import os +import sys + +def start_console(): + # Get all the file descriptors and choose the latest one + fds = os.listdir("/dev/pts/") + fds.sort() + console_fd = fds[-2] + + sys.stderr = open('/dev/pts/' + console_fd, 'w') + sys.stdout = open('/dev/pts/' + console_fd, 'w') + sys.stdin = open('/dev/pts/' + console_fd, 'w') + +def close_console(): + sys.stderr.close() + sys.stdout.close() + sys.stdin.close() \ No newline at end of file diff --git a/exercises/static/exercises/package_delivery/web-template/exercise.py b/exercises/static/exercises/package_delivery/web-template/exercise.py new file mode 100755 index 000000000..09f49aa73 --- /dev/null +++ b/exercises/static/exercises/package_delivery/web-template/exercise.py @@ -0,0 +1,357 @@ +#!/usr/bin/env python + +from __future__ import print_function + +from websocket_server import WebsocketServer +import time +import threading +import subprocess +import sys +from datetime import datetime +import re +import json +import importlib + +import rospy +from std_srvs.srv import Empty + +from gui import GUI, ThreadGUI +from hal import HAL +from console import start_console, close_console + + +class Template: + # Initialize class variables + # self.ideal_cycle to run an execution for at least 1 second + # self.process for the current running process + def __init__(self): + self.measure_thread = None + self.thread = None + self.reload = False + self.stop_brain = False + self.user_code = "" + + # Time variables + self.ideal_cycle = 80 + self.measured_cycle = 80 + self.iteration_counter = 0 + self.real_time_factor = 0 + self.frequency_message = {'brain': '', 'gui': '', 'rtf': ''} + + self.server = None + self.client = None + self.host = sys.argv[1] + + # Initialize the GUI, HAL and Console behind the scenes + self.hal = HAL() + self.gui = GUI(self.host) + + # Function to parse the code + # A few assumptions: + # 1. The user always passes sequential and iterative codes + # 2. Only a single infinite loop + def parse_code(self, source_code): + sequential_code, iterative_code = self.seperate_seq_iter(source_code) + return iterative_code, sequential_code + + # Function to separate the iterative and sequential code + def seperate_seq_iter(self, source_code): + if source_code == "": + return "", "" + + # Search for an instance of while True + infinite_loop = re.search(r'[^ ]while\s*\(\s*True\s*\)\s*:|[^ ]while\s*True\s*:|[^ ]while\s*1\s*:|[^ ]while\s*\(\s*1\s*\)\s*:', source_code) + + # Separate the content inside while True and the other + # (Separating the sequential and iterative part!) + try: + start_index = infinite_loop.start() + iterative_code = source_code[start_index:] + sequential_code = source_code[:start_index] + + # Remove while True: syntax from the code + # And remove the the 4 spaces indentation before each command + iterative_code = re.sub(r'[^ ]while\s*\(\s*True\s*\)\s*:|[^ ]while\s*True\s*:|[^ ]while\s*1\s*:|[^ ]while\s*\(\s*1\s*\)\s*:', '', iterative_code) + iterative_code = re.sub(r'^[ ]{4}', '', iterative_code, flags=re.M) + + except: + sequential_code = source_code + iterative_code = "" + + return sequential_code, iterative_code + + # The process function + def process_code(self, source_code): + # Redirect the information to console + start_console() + + iterative_code, sequential_code = self.parse_code(source_code) + + # print(sequential_code) + # print(iterative_code) + + # The Python exec function + # Run the sequential part + gui_module, hal_module = self.generate_modules() + reference_environment = {"GUI": gui_module, "HAL": hal_module} + exec(sequential_code, reference_environment) + + # Run the iterative part inside template + # and keep the check for flag + while self.reload == False: + while (self.stop_brain == True): + if (self.reload == True): + break + time.sleep(0.1) + + start_time = datetime.now() + + # Execute the iterative portion + exec(iterative_code, reference_environment) + + # Template specifics to run! + finish_time = datetime.now() + dt = finish_time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + + # Keep updating the iteration counter + if (iterative_code == ""): + self.iteration_counter = 0 + else: + self.iteration_counter = self.iteration_counter + 1 + + # The code should be run for atleast the target time step + # If it's less put to sleep + if (ms < self.ideal_cycle): + time.sleep((self.ideal_cycle - ms) / 1000.0) + + close_console() + print("Current Thread Joined!") + + # Function to generate the modules for use in ACE Editor + def generate_modules(self): + # Define HAL module + hal_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) + hal_module.HAL = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) + # hal_module.drone = imp.new_module("drone") + # motors# hal_module.HAL.motors = imp.new_module("motors") + + # Add HAL functions + hal_module.HAL.get_frontal_image = self.hal.get_frontal_image + hal_module.HAL.get_ventral_image = self.hal.get_ventral_image + hal_module.HAL.get_position = self.hal.get_position + hal_module.HAL.get_velocity = self.hal.get_velocity + hal_module.HAL.get_yaw_rate = self.hal.get_yaw_rate + hal_module.HAL.get_orientation = self.hal.get_orientation + hal_module.HAL.get_roll = self.hal.get_roll + hal_module.HAL.get_pitch = self.hal.get_pitch + hal_module.HAL.get_yaw = self.hal.get_yaw + hal_module.HAL.get_landed_state = self.hal.get_landed_state + hal_module.HAL.set_cmd_pos = self.hal.set_cmd_pos + hal_module.HAL.set_cmd_vel = self.hal.set_cmd_vel + hal_module.HAL.set_cmd_mix = self.hal.set_cmd_mix + hal_module.HAL.takeoff = self.hal.takeoff + hal_module.HAL.land = self.hal.land + hal_module.HAL.set_cmd_pick = self.hal.set_cmd_pick + hal_module.HAL.set_cmd_drop = self.hal.set_cmd_drop + hal_module.HAL.get_pkg_state = self.hal.get_pkg_state + + + # Define GUI module + gui_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) + gui_module.GUI = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) + + # Add GUI functions + gui_module.GUI.showImage = self.gui.showImage + gui_module.GUI.showLeftImage = self.gui.showLeftImage + + # Adding modules to system + # Protip: The names should be different from + # other modules, otherwise some errors + sys.modules["HAL"] = hal_module + sys.modules["GUI"] = gui_module + + return gui_module, hal_module + + # Function to measure the frequency of iterations + def measure_frequency(self): + previous_time = datetime.now() + # An infinite loop + while True: + # Sleep for 2 seconds + time.sleep(2) + + # Measure the current time and subtract from the previous time to get real time interval + current_time = datetime.now() + dt = current_time - previous_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + previous_time = current_time + + # Get the time period + try: + # Division by zero + self.measured_cycle = ms / self.iteration_counter + except: + self.measured_cycle = 0 + + # Reset the counter + self.iteration_counter = 0 + + # Send to client + self.send_frequency_message() + + # Function to generate and send frequency messages + def send_frequency_message(self): + # This function generates and sends frequency measures of the brain and gui + brain_frequency = 0; gui_frequency = 0 + try: + brain_frequency = round(1000 / self.measured_cycle, 1) + except ZeroDivisionError: + brain_frequency = 0 + + try: + gui_frequency = round(1000 / self.thread_gui.measured_cycle, 1) + except ZeroDivisionError: + gui_frequency = 0 + + self.frequency_message["brain"] = brain_frequency + self.frequency_message["gui"] = gui_frequency + self.frequency_message["rtf"] = self.real_time_factor + + message = "#freq" + json.dumps(self.frequency_message) + self.server.send_message(self.client, message) + + def send_ping_message(self): + self.server.send_message(self.client, "#ping") + + # Function to notify the front end that the code was received and sent to execution + def send_code_message(self): + self.server.send_message(self.client, "#exec") + + # Function to track the real time factor from Gazebo statistics + # https://stackoverflow.com/a/17698359 + # (For reference, Python3 solution specified in the same answer) + def track_stats(self): + args = ["gz", "stats", "-p"] + # Prints gz statistics. "-p": Output comma-separated values containing- + # real-time factor (percent), simtime (sec), realtime (sec), paused (T or F) + stats_process = subprocess.Popen(args, stdout=subprocess.PIPE, bufsize=1, universal_newlines=True) + # bufsize=1 enables line-bufferred mode (the input buffer is flushed + # automatically on newlines if you would write to process.stdin ) + with stats_process.stdout: + for line in iter(stats_process.stdout.readline, ''): + stats_list = [x.strip() for x in line.split(',')] + self.real_time_factor = stats_list[0] + + # Function to maintain thread execution + def execute_thread(self, source_code): + # Keep checking until the thread is alive + # The thread will die when the coming iteration reads the flag + if self.thread is not None: + while self.thread.is_alive(): + time.sleep(0.2) + + # Turn the flag down, the iteration has successfully stopped! + self.reload = False + # New thread execution + self.thread = threading.Thread(target=self.process_code, args=[source_code]) + self.thread.start() + self.send_code_message() + print("New Thread Started!") + + # Function to read and set frequency from incoming message + def read_frequency_message(self, message): + frequency_message = json.loads(message) + + # Set brain frequency + frequency = float(frequency_message["brain"]) + self.ideal_cycle = 1000.0 / frequency + + # Set gui frequency + frequency = float(frequency_message["gui"]) + self.thread_gui.ideal_cycle = 1000.0 / frequency + + return + + # The websocket function + # Gets called when there is an incoming message from the client + def handle(self, client, server, message): + if message[:5] == "#freq": + frequency_message = message[5:] + self.read_frequency_message(frequency_message) + time.sleep(1) + return + + elif(message[:5] == "#ping"): + time.sleep(1) + self.send_ping_message() + return + + elif (message[:5] == "#code"): + try: + # Once received turn the reload flag up and send it to execute_thread function + self.user_code = message + # print(repr(code)) + self.reload = True + self.execute_thread(self.user_code) + except: + pass + + elif (message[:5] == "#rest"): + try: + self.reload = True + self.stop_brain = True + self.execute_thread(self.user_code) + except: + pass + + elif (message[:5] == "#stop"): + self.stop_brain = True + + elif (message[:5] == "#play"): + self.stop_brain = False + + # Function that gets called when the server is connected + def connected(self, client, server): + self.client = client + # Start the GUI update thread + self.thread_gui = ThreadGUI(self.gui) + self.thread_gui.start() + + # Start the real time factor tracker thread + self.stats_thread = threading.Thread(target=self.track_stats) + self.stats_thread.start() + + # Start measure frequency + self.measure_thread = threading.Thread(target=self.measure_frequency) + self.measure_thread.start() + + print(client, 'connected') + + # Function that gets called when the connected closes + def handle_close(self, client, server): + print(client, 'closed') + + def run_server(self): + self.server = WebsocketServer(port=1905, host=self.host) + self.server.set_fn_new_client(self.connected) + self.server.set_fn_client_left(self.handle_close) + self.server.set_fn_message_received(self.handle) + + logged = False + while not logged: + try: + f = open("/ws_code.log", "w") + f.write("websocket_code=ready") + f.close() + logged = True + except: + time.sleep(0.1) + + self.server.run_forever() + + +# Execute! +if __name__ == "__main__": + server = Template() + server.run_server() \ No newline at end of file diff --git a/exercises/static/exercises/package_delivery/web-template/gui.py b/exercises/static/exercises/package_delivery/web-template/gui.py new file mode 100755 index 000000000..f4fd34044 --- /dev/null +++ b/exercises/static/exercises/package_delivery/web-template/gui.py @@ -0,0 +1,250 @@ +import json +import cv2 +import base64 +import threading +import time +from datetime import datetime +from websocket_server import WebsocketServer +import logging + + +# Graphical User Interface Class +class GUI: + # Initialization function + # The actual initialization + def __init__(self, host): + t = threading.Thread(target=self.run_server) + + self.payload = {'image': ''} + self.left_payload = {'image': ''} + self.server = None + self.client = None + + self.host = host + + # Image variables + self.image_to_be_shown = None + self.image_to_be_shown_updated = False + self.image_show_lock = threading.Lock() + + self.left_image_to_be_shown = None + self.left_image_to_be_shown_updated = False + self.left_image_show_lock = threading.Lock() + + self.acknowledge = False + self.acknowledge_lock = threading.Lock() + + # Take the console object to set the same websocket and client + t.start() + + # Explicit initialization function + # Class method, so user can call it without instantiation + @classmethod + def initGUI(cls, host): + # self.payload = {'image': '', 'shape': []} + new_instance = cls(host) + return new_instance + + # Function to prepare image payload + # Encodes the image as a JSON string and sends through the WS + def payloadImage(self): + self.image_show_lock.acquire() + image_to_be_shown_updated = self.image_to_be_shown_updated + image_to_be_shown = self.image_to_be_shown + self.image_show_lock.release() + + image = image_to_be_shown + payload = {'image': '', 'shape': ''} + + if not image_to_be_shown_updated: + return payload + + shape = image.shape + frame = cv2.imencode('.JPEG', image)[1] + encoded_image = base64.b64encode(frame) + + payload['image'] = encoded_image.decode('utf-8') + payload['shape'] = shape + + self.image_show_lock.acquire() + self.image_to_be_shown_updated = False + self.image_show_lock.release() + + return payload + + # Function to prepare image payload + # Encodes the image as a JSON string and sends through the WS + def payloadLeftImage(self): + self.left_image_show_lock.acquire() + left_image_to_be_shown_updated = self.left_image_to_be_shown_updated + left_image_to_be_shown = self.left_image_to_be_shown + self.left_image_show_lock.release() + + image = left_image_to_be_shown + payload = {'image': '', 'shape': ''} + + if not left_image_to_be_shown_updated: + return payload + + shape = image.shape + frame = cv2.imencode('.JPEG', image)[1] + encoded_image = base64.b64encode(frame) + + payload['image'] = encoded_image.decode('utf-8') + payload['shape'] = shape + + self.left_image_show_lock.acquire() + self.left_image_to_be_shown_updated = False + self.left_image_show_lock.release() + + return payload + + # Function for student to call + def showImage(self, image): + self.image_show_lock.acquire() + self.image_to_be_shown = image + self.image_to_be_shown_updated = True + self.image_show_lock.release() + + # Function for student to call + def showLeftImage(self, image): + self.left_image_show_lock.acquire() + self.left_image_to_be_shown = image + self.left_image_to_be_shown_updated = True + self.left_image_show_lock.release() + + # Function to get the client + # Called when a new client is received + def get_client(self, client, server): + self.client = client + + # Function to get value of Acknowledge + def get_acknowledge(self): + self.acknowledge_lock.acquire() + acknowledge = self.acknowledge + self.acknowledge_lock.release() + + return acknowledge + + # Function to get value of Acknowledge + def set_acknowledge(self, value): + self.acknowledge_lock.acquire() + self.acknowledge = value + self.acknowledge_lock.release() + + # Update the gui + def update_gui(self): + # Payload Image Message + payload = self.payloadImage() + self.payload["image"] = json.dumps(payload) + + message = "#gui" + json.dumps(self.payload) + self.server.send_message(self.client, message) + + # Payload Left Image Message + left_payload = self.payloadLeftImage() + self.left_payload["image"] = json.dumps(left_payload) + + message = "#gul" + json.dumps(self.left_payload) + self.server.send_message(self.client, message) + + # Function to read the message from websocket + # Gets called when there is an incoming message from the client + def get_message(self, client, server, message): + # Acknowledge Message for GUI Thread + if message[:4] == "#ack": + self.set_acknowledge(True) + + + # Activate the server + def run_server(self): + self.server = WebsocketServer(port=2303, host=self.host) + self.server.set_fn_new_client(self.get_client) + self.server.set_fn_message_received(self.get_message) + + logged = False + while not logged: + try: + f = open("/ws_gui.log", "w") + f.write("websocket_gui=ready") + f.close() + logged = True + except: + time.sleep(0.1) + + self.server.run_forever() + + # Function to reset + def reset_gui(self): + pass + + +# This class decouples the user thread +# and the GUI update thread +class ThreadGUI: + def __init__(self, gui): + self.gui = gui + + # Time variables + self.ideal_cycle = 80 + self.measured_cycle = 80 + self.iteration_counter = 0 + + # Function to start the execution of threads + def start(self): + self.measure_thread = threading.Thread(target=self.measure_thread) + self.thread = threading.Thread(target=self.run) + + self.measure_thread.start() + self.thread.start() + + print("GUI Thread Started!") + + # The measuring thread to measure frequency + def measure_thread(self): + while self.gui.client is None: + pass + + previous_time = datetime.now() + while True: + # Sleep for 2 seconds + time.sleep(2) + + # Measure the current time and subtract from previous time to get real time interval + current_time = datetime.now() + dt = current_time - previous_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + previous_time = current_time + + # Get the time period + try: + # Division by zero + self.measured_cycle = ms / self.iteration_counter + except: + self.measured_cycle = 0 + + # Reset the counter + self.iteration_counter = 0 + + # The main thread of execution + def run(self): + while self.gui.client is None: + pass + + while True: + start_time = datetime.now() + self.gui.update_gui() + acknowledge_message = self.gui.get_acknowledge() + + while not acknowledge_message: + acknowledge_message = self.gui.get_acknowledge() + + self.gui.set_acknowledge(False) + + finish_time = datetime.now() + self.iteration_counter = self.iteration_counter + 1 + + dt = finish_time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + if ms < self.ideal_cycle: + time.sleep((self.ideal_cycle-ms) / 1000.0) diff --git a/exercises/static/exercises/package_delivery/web-template/hal.py b/exercises/static/exercises/package_delivery/web-template/hal.py new file mode 100644 index 000000000..f6a0957d0 --- /dev/null +++ b/exercises/static/exercises/package_delivery/web-template/hal.py @@ -0,0 +1,95 @@ +import rospy +import cv2 +import threading +import time +from datetime import datetime + +from drone_wrapper import DroneWrapper +from magnet import Magnet + +# Hardware Abstraction Layer +class HAL: + IMG_WIDTH = 320 + IMG_HEIGHT = 240 + + def __init__(self): + rospy.init_node("HAL") + + self.image = None + self.drone = DroneWrapper(name="rqt") + self.magnet = Magnet() + + # Explicit initialization functions + # Class method, so user can call it without instantiation + @classmethod + def initRobot(cls): + new_instance = cls() + return new_instance + + # Get Image from ROS Driver Camera + def get_frontal_image(self): + image = self.drone.get_frontal_image() + image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) + return image_rgb + + def get_ventral_image(self): + image = self.drone.get_ventral_image() + image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) + return image_rgb + + def get_position(self): + pos = self.drone.get_position() + return pos + + def get_velocity(self): + vel = self.drone.get_velocity() + return vel + + def get_yaw_rate(self): + yaw_rate = self.drone.get_yaw_rate() + return yaw_rate + + def get_orientation(self): + orientation = self.drone.get_orientation() + return orientation + + def get_roll(self): + roll = self.drone.get_roll() + return roll + + def get_pitch(self): + pitch = self.drone.get_pitch() + return pitch + + def get_yaw(self): + yaw = self.drone.get_yaw() + return yaw + + def get_landed_state(self): + state = self.drone.get_landed_state() + return state + + def set_cmd_pos(self, x, y, z, az): + self.drone.set_cmd_pos(x, y, z, az) + + def set_cmd_vel(self, vx, vy, vz, az): + self.drone.set_cmd_vel(vx, vy, vz, az) + + def set_cmd_mix(self, vx, vy, z, az): + self.drone.set_cmd_mix(vx, vy, z, az) + + def takeoff(self, h=5): + self.drone.takeoff(h) + + def land(self): + self.drone.land() + + def set_cmd_pick(self): + self.magnet.set_cmd_pick() + + def set_cmd_drop(self): + self.magnet.set_cmd_drop() + + def get_pkg_state(self): + state = self.magnet.get_pkg_state() + return state diff --git a/exercises/static/exercises/package_delivery/web-template/interfaces/__init__.py b/exercises/static/exercises/package_delivery/web-template/interfaces/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/exercises/static/exercises/package_delivery/web-template/interfaces/camera.py b/exercises/static/exercises/package_delivery/web-template/interfaces/camera.py new file mode 100644 index 000000000..5a021a13e --- /dev/null +++ b/exercises/static/exercises/package_delivery/web-template/interfaces/camera.py @@ -0,0 +1,89 @@ +import rospy +from sensor_msgs.msg import Image as ImageROS +import threading +from math import pi as PI +import cv2 +from cv_bridge import CvBridge, CvBridgeError + + +MAXRANGE = 8 # max length received from imageD +MINRANGE = 0 + + +def imageMsg2Image(img, bridge): + + image = Image() + + image.width = img.width + image.height = img.height + image.format = "BGR8" + image.timeStamp = img.header.stamp.secs + (img.header.stamp.nsecs * 1e-9) + cv_image = 0 + if img.encoding[-2:] == "C1": + gray_img_buff = bridge.imgmsg_to_cv2(img, img.encoding) + cv_image = depthToRGB8(gray_img_buff, img.encoding) + else: + cv_image = bridge.imgmsg_to_cv2(img, "bgr8") + image.data = cv_image + return image + + +import numpy as np + + +class Image: + + def __init__(self): + + self.height = 3 # Image height [pixels] + self.width = 3 # Image width [pixels] + self.timeStamp = 0 # Time stamp [s] */ + self.format = "" # Image format string (RGB8, BGR,...) + self.data = np.zeros((self.height, self.width, 3), np.uint8) # The image data itself + self.data.shape = self.height, self.width, 3 + + def __str__(self): + s = "Image: {\n height: " + str(self.height) + "\n width: " + str(self.width) + s = s + "\n format: " + self.format + "\n timeStamp: " + str(self.timeStamp) + s = s + "\n data: " + str(self.data) + "\n}" + return s + + +class ListenerCamera: + + def __init__(self, topic): + + self.topic = topic + self.data = Image() + self.sub = None + self.lock = threading.Lock() + + self.bridge = CvBridge() + self.start() + + def __callback(self, img): + + image = imageMsg2Image(img, self.bridge) + + self.lock.acquire() + self.data = image + self.lock.release() + + def stop(self): + + self.sub.unregister() + + def start(self): + self.sub = rospy.Subscriber(self.topic, ImageROS, self.__callback) + + def getImage(self): + + self.lock.acquire() + image = self.data + self.lock.release() + + return image + + def hasproxy(self): + + return hasattr(self, "sub") and self.sub diff --git a/exercises/static/exercises/package_delivery/web-template/interfaces/motors.py b/exercises/static/exercises/package_delivery/web-template/interfaces/motors.py new file mode 100644 index 000000000..70dca8a46 --- /dev/null +++ b/exercises/static/exercises/package_delivery/web-template/interfaces/motors.py @@ -0,0 +1,123 @@ +import rospy +from geometry_msgs.msg import Twist +import threading +from math import pi as PI +from .threadPublisher import ThreadPublisher + + + +def cmdvel2Twist(vel): + + tw = Twist() + tw.linear.x = vel.vx + tw.linear.y = vel.vy + tw.linear.z = vel.vz + tw.angular.x = vel.ax + tw.angular.y = vel.ay + tw.angular.z = vel.az + + return tw + + +class CMDVel (): + + def __init__(self): + + self.vx = 0 # vel in x[m/s] (use this for V in wheeled robots) + self.vy = 0 # vel in y[m/s] + self.vz = 0 # vel in z[m/s] + self.ax = 0 # angular vel in X axis [rad/s] + self.ay = 0 # angular vel in X axis [rad/s] + self.az = 0 # angular vel in Z axis [rad/s] (use this for W in wheeled robots) + self.timeStamp = 0 # Time stamp [s] + + + def __str__(self): + s = "CMDVel: {\n vx: " + str(self.vx) + "\n vy: " + str(self.vy) + s = s + "\n vz: " + str(self.vz) + "\n ax: " + str(self.ax) + s = s + "\n ay: " + str(self.ay) + "\n az: " + str(self.az) + s = s + "\n timeStamp: " + str(self.timeStamp) + "\n}" + return s + +class PublisherMotors: + + def __init__(self, topic, maxV, maxW): + + self.maxW = maxW + self.maxV = maxV + + self.topic = topic + self.data = CMDVel() + self.pub = rospy.Publisher(self.topic, Twist, queue_size=1) + + self.lock = threading.Lock() + + self.kill_event = threading.Event() + self.thread = ThreadPublisher(self, self.kill_event) + + self.thread.daemon = True + self.start() + + def publish (self): + + self.lock.acquire() + tw = cmdvel2Twist(self.data) + self.lock.release() + self.pub.publish(tw) + + def stop(self): + + self.kill_event.set() + self.pub.unregister() + + def start (self): + + self.kill_event.clear() + self.thread.start() + + + + def getMaxW(self): + return self.maxW + + def getMaxV(self): + return self.maxV + + + def sendVelocities(self, vel): + + self.lock.acquire() + self.data = vel + self.lock.release() + + def sendV(self, v): + + self.sendVX(v) + + def sendL(self, l): + + self.sendVY(l) + + def sendW(self, w): + + self.sendAZ(w) + + def sendVX(self, vx): + + self.lock.acquire() + self.data.vx = vx + self.lock.release() + + def sendVY(self, vy): + + self.lock.acquire() + self.data.vy = vy + self.lock.release() + + def sendAZ(self, az): + + self.lock.acquire() + self.data.az = az + self.lock.release() + + diff --git a/exercises/static/exercises/package_delivery/web-template/interfaces/pose3d.py b/exercises/static/exercises/package_delivery/web-template/interfaces/pose3d.py new file mode 100644 index 000000000..fd0bfc37a --- /dev/null +++ b/exercises/static/exercises/package_delivery/web-template/interfaces/pose3d.py @@ -0,0 +1,176 @@ +import rospy +import threading +from math import asin, atan2, pi +from nav_msgs.msg import Odometry + +def quat2Yaw(qw, qx, qy, qz): + ''' + Translates from Quaternion to Yaw. + + @param qw,qx,qy,qz: Quaternion values + + @type qw,qx,qy,qz: float + + @return Yaw value translated from Quaternion + + ''' + rotateZa0=2.0*(qx*qy + qw*qz) + rotateZa1=qw*qw + qx*qx - qy*qy - qz*qz + rotateZ=0.0 + if(rotateZa0 != 0.0 and rotateZa1 != 0.0): + rotateZ=atan2(rotateZa0,rotateZa1) + return rotateZ + +def quat2Pitch(qw, qx, qy, qz): + ''' + Translates from Quaternion to Pitch. + + @param qw,qx,qy,qz: Quaternion values + + @type qw,qx,qy,qz: float + + @return Pitch value translated from Quaternion + + ''' + + rotateYa0=-2.0*(qx*qz - qw*qy) + rotateY=0.0 + if(rotateYa0 >= 1.0): + rotateY = pi/2.0 + elif(rotateYa0 <= -1.0): + rotateY = -pi/2.0 + else: + rotateY = asin(rotateYa0) + + return rotateY + +def quat2Roll (qw, qx, qy, qz): + ''' + Translates from Quaternion to Roll. + + @param qw,qx,qy,qz: Quaternion values + + @type qw,qx,qy,qz: float + + @return Roll value translated from Quaternion + + ''' + rotateXa0=2.0*(qy*qz + qw*qx) + rotateXa1=qw*qw - qx*qx - qy*qy + qz*qz + rotateX=0.0 + + if(rotateXa0 != 0.0 and rotateXa1 != 0.0): + rotateX=atan2(rotateXa0, rotateXa1) + return rotateX + + +def odometry2Pose3D(odom): + ''' + Translates from ROS Odometry to JderobotTypes Pose3d. + + @param odom: ROS Odometry to translate + + @type odom: Odometry + + @return a Pose3d translated from odom + + ''' + pose = Pose3d() + ori = odom.pose.pose.orientation + + pose.x = odom.pose.pose.position.x + pose.y = odom.pose.pose.position.y + pose.z = odom.pose.pose.position.z + #pose.h = odom.pose.pose.position.h + pose.yaw = quat2Yaw(ori.w, ori.x, ori.y, ori.z) + pose.pitch = quat2Pitch(ori.w, ori.x, ori.y, ori.z) + pose.roll = quat2Roll(ori.w, ori.x, ori.y, ori.z) + pose.q = [ori.w, ori.x, ori.y, ori.z] + pose.timeStamp = odom.header.stamp.secs + (odom.header.stamp.nsecs *1e-9) + + return pose + +class Pose3d (): + + def __init__(self): + + self.x = 0 # X coord [meters] + self.y = 0 # Y coord [meters] + self.z = 0 # Z coord [meters] + self.h = 1 # H param + self.yaw = 0 #Yaw angle[rads] + self.pitch = 0 # Pitch angle[rads] + self.roll = 0 # Roll angle[rads] + self.q = [0,0,0,0] # Quaternion + self.timeStamp = 0 # Time stamp [s] + + + def __str__(self): + s = "Pose3D: {\n x: " + str(self.x) + "\n Y: " + str(self.y) + s = s + "\n Z: " + str(self.z) + "\n H: " + str(self.h) + s = s + "\n Yaw: " + str(self.yaw) + "\n Pitch: " + str(self.pitch) + "\n Roll: " + str(self.roll) + s = s + "\n quaternion: " + str(self.q) + "\n timeStamp: " + str(self.timeStamp) + "\n}" + return s + + +class ListenerPose3d: + ''' + ROS Pose3D Subscriber. Pose3D Client to Receive pose3d from ROS nodes. + ''' + def __init__(self, topic): + ''' + ListenerPose3d Constructor. + + @param topic: ROS topic to subscribe + + @type topic: String + + ''' + self.topic = topic + self.data = Pose3d() + self.sub = None + self.lock = threading.Lock() + self.start() + + def __callback (self, odom): + ''' + Callback function to receive and save Pose3d. + + @param odom: ROS Odometry received + + @type odom: Odometry + + ''' + pose = odometry2Pose3D(odom) + + self.lock.acquire() + self.data = pose + self.lock.release() + + def stop(self): + ''' + Stops (Unregisters) the client. + + ''' + self.sub.unregister() + + def start (self): + ''' + Starts (Subscribes) the client. + + ''' + self.sub = rospy.Subscriber(self.topic, Odometry, self.__callback) + + def getPose3d(self): + ''' + Returns last Pose3d. + + @return last JdeRobotTypes Pose3d saved + + ''' + self.lock.acquire() + pose = self.data + self.lock.release() + + return pose + diff --git a/exercises/static/exercises/package_delivery/web-template/interfaces/threadPublisher.py b/exercises/static/exercises/package_delivery/web-template/interfaces/threadPublisher.py new file mode 100644 index 000000000..69aa0ad48 --- /dev/null +++ b/exercises/static/exercises/package_delivery/web-template/interfaces/threadPublisher.py @@ -0,0 +1,46 @@ +# +# Copyright (C) 1997-2016 JDE Developers Team +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. +# Authors : +# Alberto Martin Florido +# Aitor Martinez Fernandez +# +import threading +import time +from datetime import datetime + +time_cycle = 80 + + +class ThreadPublisher(threading.Thread): + + def __init__(self, pub, kill_event): + self.pub = pub + self.kill_event = kill_event + threading.Thread.__init__(self, args=kill_event) + + def run(self): + while (not self.kill_event.is_set()): + start_time = datetime.now() + + self.pub.publish() + + finish_Time = datetime.now() + + dt = finish_Time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + #print (ms) + if (ms < time_cycle): + time.sleep((time_cycle - ms) / 1000.0) \ No newline at end of file diff --git a/exercises/static/exercises/package_delivery/web-template/launch/gazebo.launch b/exercises/static/exercises/package_delivery/web-template/launch/gazebo.launch new file mode 100644 index 000000000..23249178c --- /dev/null +++ b/exercises/static/exercises/package_delivery/web-template/launch/gazebo.launch @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/exercises/static/exercises/package_delivery/web-template/launch/launch.py b/exercises/static/exercises/package_delivery/web-template/launch/launch.py new file mode 100644 index 000000000..0b2fc4e37 --- /dev/null +++ b/exercises/static/exercises/package_delivery/web-template/launch/launch.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python3 + +import stat +import rospy +from os import lstat +from subprocess import Popen, PIPE + + +DRI_PATH = "/dev/dri/card0" +EXERCISE = "package_delivery" +TIMEOUT = 30 +MAX_ATTEMPT = 2 + + +# Check if acceleration can be enabled +def check_device(device_path): + try: + return stat.S_ISCHR(lstat(device_path)[stat.ST_MODE]) + except: + return False + + +# Spawn new process +def spawn_process(args, insert_vglrun=False): + if insert_vglrun: + args.insert(0, "vglrun") + process = Popen(args, stdout=PIPE, bufsize=1, universal_newlines=True) + return process + + +class Test(): + def gazebo(self): + rospy.logwarn("[GAZEBO] Launching") + try: + rospy.wait_for_service("/gazebo/get_model_properties", TIMEOUT) + return True + except rospy.ROSException: + return False + + def px4(self): + rospy.logwarn("[PX4-SITL] Launching") + start_time = rospy.get_time() + args = ["./PX4-Autopilot/build/px4_sitl_default/bin/px4-commander","--instance", "0", "check"] + while rospy.get_time() - start_time < TIMEOUT: + process = spawn_process(args, insert_vglrun=False) + with process.stdout: + for line in iter(process.stdout.readline, ''): + if ("Prearm check: OK" in line): + return True + rospy.sleep(2) + return False + + def mavros(self, ns=""): + rospy.logwarn("[MAVROS] Launching") + try: + rospy.wait_for_service(ns + "/mavros/cmd/arming", TIMEOUT) + return True + except rospy.ROSException: + return False + + +class Launch(): + def __init__(self): + self.test = Test() + self.acceleration_enabled = check_device(DRI_PATH) + + # Start roscore + args = ["/opt/ros/noetic/bin/roscore"] + spawn_process(args, insert_vglrun=False) + + rospy.init_node("launch", anonymous=True) + + def start(self): + ######## LAUNCH GAZEBO ######## + args = ["/opt/ros/noetic/bin/roslaunch", + "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/gazebo.launch", + "--wait", + "--log" + ] + + attempt = 1 + while True: + spawn_process(args, insert_vglrun=self.acceleration_enabled) + if self.test.gazebo() == True: + break + if attempt == MAX_ATTEMPT: + rospy.logerr("[GAZEBO] Launch Failed") + return + attempt = attempt + 1 + + + ######## LAUNCH PX4 ######## + args = ["/opt/ros/noetic/bin/roslaunch", + "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/px4.launch", + "--log" + ] + + attempt = 1 + while True: + spawn_process(args, insert_vglrun=self.acceleration_enabled) + if self.test.px4() == True: + break + if attempt == MAX_ATTEMPT: + rospy.logerr("[PX4] Launch Failed") + return + attempt = attempt + 1 + + + ######## LAUNCH MAVROS ######## + args = ["/opt/ros/noetic/bin/roslaunch", + "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/mavros.launch", + "--log" + ] + + attempt = 1 + while True: + spawn_process(args, insert_vglrun=self.acceleration_enabled) + if self.test.mavros() == True: + break + if attempt == MAX_ATTEMPT: + rospy.logerr("[MAVROS] Launch Failed") + return + attempt = attempt + 1 + + +if __name__ == "__main__": + launch = Launch() + launch.start() + + with open("/drones_launch.log", "w") as f: + f.write("success") diff --git a/exercises/static/exercises/package_delivery/web-template/launch/mavros.launch b/exercises/static/exercises/package_delivery/web-template/launch/mavros.launch new file mode 100644 index 000000000..b899c0ec1 --- /dev/null +++ b/exercises/static/exercises/package_delivery/web-template/launch/mavros.launch @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/exercises/static/exercises/package_delivery/web-template/launch/px4.launch b/exercises/static/exercises/package_delivery/web-template/launch/px4.launch new file mode 100644 index 000000000..11f78d57b --- /dev/null +++ b/exercises/static/exercises/package_delivery/web-template/launch/px4.launch @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/exercises/static/exercises/package_delivery/web-template/magnet.py b/exercises/static/exercises/package_delivery/web-template/magnet.py new file mode 100644 index 000000000..75a7673ac --- /dev/null +++ b/exercises/static/exercises/package_delivery/web-template/magnet.py @@ -0,0 +1,73 @@ +import rospy +from math import sqrt +from gazebo_msgs.msg import ModelState +from gazebo_msgs.srv import GetModelState +from gazebo_msgs.srv import SetModelState +from threading import Event, Thread + + +MIN_DIST = 2.0 # minimum distance required by drone to pick package + + +class Magnet: + def __init__(self): + # rospy.init_node("magnet") + self.magnetize = Event() + self.get_state = rospy.ServiceProxy('/gazebo/get_model_state', GetModelState) + self.set_state = rospy.ServiceProxy('/gazebo/set_model_state', SetModelState) + self.set_pkg_state = False + + + def start_magnet(self): + req = ModelState() + req.model_name = "package_box" + + while self.magnetize.is_set(): + drone_pos = self.get_state("typhoon_h480_dual_cam", "") + req.pose.position.x = drone_pos.pose.position.x + req.pose.position.y = drone_pos.pose.position.y + req.pose.position.z = drone_pos.pose.position.z - 0.215 + req.twist.linear.x = drone_pos.twist.linear.x + req.twist.linear.y = drone_pos.twist.linear.y + req.twist.linear.z = drone_pos.twist.linear.z + req.twist.angular.x = drone_pos.twist.angular.x + req.twist.angular.y = drone_pos.twist.angular.y + req.twist.angular.z = drone_pos.twist.angular.z + req.pose.orientation.x = drone_pos.pose.orientation.x + req.pose.orientation.y = drone_pos.pose.orientation.y + req.pose.orientation.z = drone_pos.pose.orientation.z + req.pose.orientation.w = drone_pos.pose.orientation.w + self.set_state(req) + + + def stop_magnet(self): + self.magnetize.clear() + + + def set_cmd_pick(self): + try: + pkg_pos = self.get_state("package_box", "") + drone_pos = self.get_state("typhoon_h480_dual_cam", "") + dist = sqrt(pow(pkg_pos.pose.position.x - drone_pos.pose.position.x, 2) + \ + pow(pkg_pos.pose.position.y - drone_pos.pose.position.y, 2) + \ + pow(pkg_pos.pose.position.z - drone_pos.pose.position.z, 2)) + + if dist < MIN_DIST: + self.magnetize.set() + self.set_cmd_pick_thread = Thread(target=self.start_magnet) + self.set_cmd_pick_thread.start() + self.set_pkg_state = True + except: + pass + + + def set_cmd_drop(self): + try: + self.stop_magnet() + self.set_cmd_pick_thread.join() + self.set_pkg_state = False + except: + pass + + def get_pkg_state(self): + return self.set_pkg_state diff --git a/exercises/static/exercises/package_delivery/web-template/package_delivery.world b/exercises/static/exercises/package_delivery/web-template/package_delivery.world new file mode 100644 index 000000000..6ec6bb43c --- /dev/null +++ b/exercises/static/exercises/package_delivery/web-template/package_delivery.world @@ -0,0 +1,238 @@ + + + + + + + -4.70385 10.895 16.2659 -0 0.921795 -1.12701 + orbit + perspective + + + + + + model://sun + + + + + model://asphalt_plane + 0 0 -0.045 0 0 + + + + + model://logoJdeRobot + 30 -5 0 0 0 0 + + + + + model://package_box + -1 -1 0.061 0 0 0 + + + + + model://logoPadRed + -1 -1 0.05 0 0 + + + + + model://logoPadGreen + -1 -4 0.05 0 0 + + + + + model://house_3 + 20 20 0 0 0 0 + + + + + model://logoBeacon + 20 15 0 0 0 0 + + + + + model://aws_robomaker_warehouse_ShelfF_01 + + -5.795143 -0.956635 0 0 0 0 + + + + model://aws_robomaker_warehouse_WallB_01 + + 0.0 0.0 0 0 0 0 + + + + model://aws_robomaker_warehouse_ShelfE_01 + + 4.73156 0.57943 0 0 0 0 + + + + model://aws_robomaker_warehouse_ShelfE_01 + + 4.73156 -4.827049 0 0 0 0 + + + + model://aws_robomaker_warehouse_ShelfE_01 + + 4.73156 -8.6651 0 0 0 0 + + + + model://aws_robomaker_warehouse_ShelfD_01 + + 4.73156 -1.242668 0 0 0 0 + + + + model://aws_robomaker_warehouse_ShelfD_01 + + 4.73156 -3.038551 0 0 0 0 + + + + model://aws_robomaker_warehouse_ShelfD_01 + + 4.73156 -6.750542 0 0 0 0 + + + + model://aws_robomaker_warehouse_GroundB_01 + + 0.0 0.0 -0.090092 0 0 0 + + + + model://aws_robomaker_warehouse_Bucket_01 + + 0.433449 9.631706 0 0 0 -1.563161 + + + + model://aws_robomaker_warehouse_Bucket_01 + + -1.8321 -6.3752 0 0 0 -1.563161 + + + + model://aws_robomaker_warehouse_Bucket_01 + + 0.433449 8.59 0 0 0 -1.563161 + + + + model://aws_robomaker_warehouse_ClutteringA_01 + + 5.708138 8.616844 -0.017477 0 0 0 + + + + model://aws_robomaker_warehouse_ClutteringA_01 + + 3.408638 8.616844 -0.017477 0 0 0 + + + + model://aws_robomaker_warehouse_ClutteringA_01 + + -1.491287 5.222435 -0.017477 0 0 -1.583185 + + + + model://aws_robomaker_warehouse_ClutteringC_01 + + 3.324959 3.822449 -0.012064 0 0 1.563871 + + + + model://aws_robomaker_warehouse_ClutteringC_01 + + 5.54171 3.816475 -0.015663 0 0 -1.583191 + + + + model://aws_robomaker_warehouse_ClutteringC_01 + + 5.384239 6.137154 0 0 0 3.150000 + + + + model://aws_robomaker_warehouse_ClutteringC_01 + + 3.236 6.137154 0 0 0 3.150000 + + + + model://aws_robomaker_warehouse_ClutteringC_01 + + -1.573677 2.301994 -0.015663 0 0 -3.133191 + + + + model://aws_robomaker_warehouse_ClutteringC_01 + + -1.2196 9.407 -0.015663 0 0 1.563871 + + + + model://aws_robomaker_warehouse_ClutteringD_01 + + -1.634682 -7.811813 -0.319559 0 0 0 + + + + model://aws_robomaker_warehouse_TrashCanC_01 + + -1.592441 7.715420 0 0 0 0 + + + + model://aws_robomaker_warehouse_PalletJackB_01 + + -0.276098 -9.481944 0.023266 0 0 0 + + + + 0 + 0.4 0.4 0.4 1.0 + 0.7 0.7 0.7 1 + + + 12 + + + + + + 0 0 -9.8066 + + + quick + 10 + 1.3 + 0 + + + 0 + 0.2 + 100 + 0.001 + + + 0.004 + 1 + 250 + 6.0e-6 2.3e-5 -4.2e-5 + + + \ No newline at end of file diff --git a/exercises/static/exercises/package_delivery/web-template/package_delivery.yaml b/exercises/static/exercises/package_delivery/web-template/package_delivery.yaml new file mode 100644 index 000000000..e93828f54 --- /dev/null +++ b/exercises/static/exercises/package_delivery/web-template/package_delivery.yaml @@ -0,0 +1 @@ +drone_model: "typhoon_h480_dual_cam" \ No newline at end of file diff --git a/exercises/static/exercises/position_control/Beacon.py b/exercises/static/exercises/position_control/Beacon.py new file mode 100644 index 000000000..e2d0bd59b --- /dev/null +++ b/exercises/static/exercises/position_control/Beacon.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python + + +class Beacon: + def __init__(self, id, pose, active=False, reached=False): + self.id = id + self.pose = pose + self.active = active + self.reached = reached + + def get_pose(self): + return self.pose + + def get_id(self): + return self.id + + def is_reached(self): + return self.reached + + def set_reached(self, value): + self.reached = value + + def is_active(self): + return self.active + + def set_active(self, value): + self.active = value diff --git a/exercises/static/exercises/position_control/README.md b/exercises/static/exercises/position_control/README.md new file mode 100644 index 000000000..463f1ee76 --- /dev/null +++ b/exercises/static/exercises/position_control/README.md @@ -0,0 +1 @@ +[Exercise Documentation Website](https://jderobot.github.io/RoboticsAcademy/exercises/Drones/position_control) diff --git a/exercises/static/exercises/position_control/my_solution.py b/exercises/static/exercises/position_control/my_solution.py new file mode 100644 index 000000000..024fef3a2 --- /dev/null +++ b/exercises/static/exercises/position_control/my_solution.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python + +import rospy +import numpy as np +import cv2 +from drone_wrapper import DroneWrapper +from std_msgs.msg import Bool, Float64 +from sensor_msgs.msg import Image +from geometry_msgs.msg import Twist, Pose + +from Beacon import Beacon + +code_live_flag = False +beacons = [] +min_error = 0.01 + +def init_beacons(): + global beacons + beacons.append(Beacon('beacon1', np.array([0, 5, 0]), False, False)) + beacons.append(Beacon('beacon2', np.array([5, 0, 0]), False, False)) + beacons.append(Beacon('beacon3', np.array([0, -5, 0]), False, False)) + beacons.append(Beacon('beacon4', np.array([-5, 0, 0]), False, False)) + beacons.append(Beacon('beacon5', np.array([10, 0, 0]), False, False)) + beacons.append(Beacon('initial', np.array([0, 0, 0]), False, False)) + +def get_next_beacon(): + for beacon in beacons: + if beacon.is_reached() == False: + return beacon + return None + +def gui_play_stop_cb(msg): + global code_live_flag, code_live_timer + if msg.data == True: + if not code_live_flag: + code_live_flag = True + code_live_timer = rospy.Timer(rospy.Duration(nsecs=50000000), execute) + else: + if code_live_flag: + code_live_flag = False + code_live_timer.shutdown() + +def set_image_filtered(img): + gui_filtered_img_pub.publish(drone.bridge.cv2_to_imgmsg(img)) + +def set_image_threshed(img): + gui_threshed_img_pub.publish(drone.bridge.cv2_to_imgmsg(img)) + +def execute(event): + global drone + img_frontal = drone.get_frontal_image() + img_ventral = drone.get_ventral_image() + # Both the above images are cv2 images + ################# Insert your code here ################################# + + set_image_filtered(img_frontal) + set_image_threshed(img_ventral) + + ######################################################################### + +if __name__ == "__main__": + drone = DroneWrapper() + rospy.Subscriber('gui/play_stop', Bool, gui_play_stop_cb) + gui_filtered_img_pub = rospy.Publisher('interface/filtered_img', Image, queue_size = 1) + gui_threshed_img_pub = rospy.Publisher('interface/threshed_img', Image, queue_size = 1) + code_live_flag = False + code_live_timer = rospy.Timer(rospy.Duration(nsecs=50000000), execute) + code_live_timer.shutdown() + while not rospy.is_shutdown(): + rospy.spin() diff --git a/exercises/static/exercises/position_control/position_control.launch b/exercises/static/exercises/position_control/position_control.launch new file mode 100644 index 000000000..908c389a1 --- /dev/null +++ b/exercises/static/exercises/position_control/position_control.launch @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/exercises/static/exercises/position_control/position_control.world b/exercises/static/exercises/position_control/position_control.world new file mode 100644 index 000000000..cdb9df5c3 --- /dev/null +++ b/exercises/static/exercises/position_control/position_control.world @@ -0,0 +1,65 @@ + + + + + + beacon1 + model://construction_cone + 0 5 0 0 0 0 + + + beacon2 + model://construction_cone + 5 0 0 0 0 0 + + + beacon3 + model://construction_cone + 0 -5 0 0 0 0 + + + beacon4 + model://construction_cone + -5 0 0 0 0 0 + + + beacon5 + model://construction_cone + 10 0 0 0 0 0 + + + + + model://sun + + + + + model://ground_plane + + + + + 0 0 -9.8066 + + + quick + 10 + 1.3 + 0 + + + 0 + 0.2 + 100 + 0.001 + + + 0.004 + 1 + 250 + 6.0e-6 2.3e-5 -4.2e-5 + + + + \ No newline at end of file diff --git a/exercises/static/exercises/position_control/web-template/Beacon.py b/exercises/static/exercises/position_control/web-template/Beacon.py new file mode 100644 index 000000000..e2d0bd59b --- /dev/null +++ b/exercises/static/exercises/position_control/web-template/Beacon.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python + + +class Beacon: + def __init__(self, id, pose, active=False, reached=False): + self.id = id + self.pose = pose + self.active = active + self.reached = reached + + def get_pose(self): + return self.pose + + def get_id(self): + return self.id + + def is_reached(self): + return self.reached + + def set_reached(self, value): + self.reached = value + + def is_active(self): + return self.active + + def set_active(self, value): + self.active = value diff --git a/exercises/static/exercises/position_control/web-template/RADI-launch b/exercises/static/exercises/position_control/web-template/RADI-launch new file mode 100755 index 000000000..afde9c081 --- /dev/null +++ b/exercises/static/exercises/position_control/web-template/RADI-launch @@ -0,0 +1,2 @@ +#!/bin/sh +docker run -it --rm -p 8000:8000 -p 2303:2303 -p 1905:1905 -p 8765:8765 -p 6080:6080 -p 1108:1108 jderobot/robotics-academy:3.1.2 ./start-3.1.sh diff --git a/exercises/static/exercises/position_control/web-template/README.md b/exercises/static/exercises/position_control/web-template/README.md new file mode 100644 index 000000000..463f1ee76 --- /dev/null +++ b/exercises/static/exercises/position_control/web-template/README.md @@ -0,0 +1 @@ +[Exercise Documentation Website](https://jderobot.github.io/RoboticsAcademy/exercises/Drones/position_control) diff --git a/exercises/static/exercises/position_control/web-template/code/academy.py b/exercises/static/exercises/position_control/web-template/code/academy.py new file mode 100644 index 000000000..7a59ce7f5 --- /dev/null +++ b/exercises/static/exercises/position_control/web-template/code/academy.py @@ -0,0 +1,8 @@ +# Enter sequential code! +from GUI import GUI +from HAL import HAL + +while True: + # Enter iterative code! + img = HAL.get_ventral_image() + GUI.showImage(img) \ No newline at end of file diff --git a/exercises/static/exercises/position_control/web-template/console.py b/exercises/static/exercises/position_control/web-template/console.py new file mode 100644 index 000000000..7b72a0913 --- /dev/null +++ b/exercises/static/exercises/position_control/web-template/console.py @@ -0,0 +1,20 @@ +# Functions to start and close console +import os +import sys + + +def start_console(): + # Get all the file descriptors and choose the latest one + fds = os.listdir("/dev/pts/") + fds.sort() + console_fd = fds[-2] + + sys.stderr = open('/dev/pts/' + console_fd, 'w') + sys.stdout = open('/dev/pts/' + console_fd, 'w') + sys.stdin = open('/dev/pts/' + console_fd, 'w') + + +def close_console(): + sys.stderr.close() + sys.stdout.close() + sys.stdin.close() diff --git a/exercises/static/exercises/position_control/web-template/exercise.py b/exercises/static/exercises/position_control/web-template/exercise.py new file mode 100644 index 000000000..42c559a6a --- /dev/null +++ b/exercises/static/exercises/position_control/web-template/exercise.py @@ -0,0 +1,364 @@ +#!/usr/bin/env python + +from __future__ import print_function + +from websocket_server import WebsocketServer +import time +import threading +import subprocess +import sys +from datetime import datetime +import re +import json +import importlib + +import rospy +from std_srvs.srv import Empty + +from gui import GUI, ThreadGUI +from hal import HAL +from console import start_console, close_console + + +class Template: + # Initialize class variables + # self.ideal_cycle to run an execution for at least 1 second + # self.process for the current running process + def __init__(self): + self.measure_thread = None + self.thread = None + self.reload = False + self.stop_brain = True + self.user_code = "" + + # Time variables + self.ideal_cycle = 80 + self.measured_cycle = 80 + self.iteration_counter = 0 + self.real_time_factor = 0 + self.frequency_message = {'brain': '', 'gui': '', 'rtf': ''} + + self.server = None + self.client = None + self.host = sys.argv[1] + + # Initialize the GUI, HAL and Console behind the scenes + self.hal = HAL() + self.gui = GUI(self.host) + + # Function to parse the code + # A few assumptions: + # 1. The user always passes sequential and iterative codes + # 2. Only a single infinite loop + def parse_code(self, source_code): + sequential_code, iterative_code = self.seperate_seq_iter(source_code) + return iterative_code, sequential_code + + # Function to separate the iterative and sequential code + def seperate_seq_iter(self, source_code): + if source_code == "": + return "", "" + + # Search for an instance of while True + infinite_loop = re.search(r'[^ ]while\s*\(\s*True\s*\)\s*:|[^ ]while\s*True\s*:|[^ ]while\s*1\s*:|[^ ]while\s*\(\s*1\s*\)\s*:', source_code) + + # Separate the content inside while True and the other + # (Separating the sequential and iterative part!) + try: + start_index = infinite_loop.start() + iterative_code = source_code[start_index:] + sequential_code = source_code[:start_index] + + # Remove while True: syntax from the code + # And remove the the 4 spaces indentation before each command + iterative_code = re.sub(r'[^ ]while\s*\(\s*True\s*\)\s*:|[^ ]while\s*True\s*:|[^ ]while\s*1\s*:|[^ ]while\s*\(\s*1\s*\)\s*:', '', iterative_code) + # Add newlines to match line on bug report + extra_lines = sequential_code.count('\n') + while (extra_lines >= 0): + iterative_code = '\n' + iterative_code + extra_lines -= 1 + iterative_code = re.sub(r'^[ ]{4}', '', iterative_code, flags=re.M) + + except: + sequential_code = source_code + iterative_code = "" + + return sequential_code, iterative_code + + # The process function + def process_code(self, source_code): + # Redirect the information to console + start_console() + + iterative_code, sequential_code = self.parse_code(source_code) + + # print(sequential_code) + # print(iterative_code) + + # The Python exec function + # Run the sequential part + gui_module, hal_module = self.generate_modules() + reference_environment = {"GUI": gui_module, "HAL": hal_module} + while (self.stop_brain == True): + if (self.reload == True): + return + time.sleep(0.1) + exec(sequential_code, reference_environment) + + # Run the iterative part inside template + # and keep the check for flag + while self.reload == False: + while (self.stop_brain == True): + if (self.reload == True): + return + time.sleep(0.1) + + start_time = datetime.now() + + # Execute the iterative portion + exec(iterative_code, reference_environment) + + # Template specifics to run! + finish_time = datetime.now() + dt = finish_time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + + # Keep updating the iteration counter + if (iterative_code == ""): + self.iteration_counter = 0 + else: + self.iteration_counter = self.iteration_counter + 1 + + # The code should be run for atleast the target time step + # If it's less put to sleep + if (ms < self.ideal_cycle): + time.sleep((self.ideal_cycle - ms) / 1000.0) + + close_console() + print("Current Thread Joined!") + + # Function to generate the modules for use in ACE Editor + def generate_modules(self): + # Define HAL module + hal_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) + hal_module.HAL = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) + # hal_module.drone = imp.new_module("drone") + # motors# hal_module.HAL.motors = imp.new_module("motors") + + # Add HAL functions + hal_module.HAL.get_frontal_image = self.hal.get_frontal_image + hal_module.HAL.get_ventral_image = self.hal.get_ventral_image + hal_module.HAL.get_position = self.hal.get_position + hal_module.HAL.get_velocity = self.hal.get_velocity + hal_module.HAL.get_yaw_rate = self.hal.get_yaw_rate + hal_module.HAL.get_orientation = self.hal.get_orientation + hal_module.HAL.get_roll = self.hal.get_roll + hal_module.HAL.get_pitch = self.hal.get_pitch + hal_module.HAL.get_yaw = self.hal.get_yaw + hal_module.HAL.get_landed_state = self.hal.get_landed_state + hal_module.HAL.set_cmd_pos = self.hal.set_cmd_pos + hal_module.HAL.set_cmd_vel = self.hal.set_cmd_vel + hal_module.HAL.set_cmd_mix = self.hal.set_cmd_mix + hal_module.HAL.takeoff = self.hal.takeoff + hal_module.HAL.land = self.hal.land + hal_module.HAL.init_beacons = self.hal.init_beacons + hal_module.HAL.get_next_beacon = self.hal.get_next_beacon + + # Define GUI module + gui_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) + gui_module.GUI = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) + + # Add GUI functions + gui_module.GUI.showImage = self.gui.showImage + gui_module.GUI.showLeftImage = self.gui.showLeftImage + + # Adding modules to system + # Protip: The names should be different from + # other modules, otherwise some errors + sys.modules["HAL"] = hal_module + sys.modules["GUI"] = gui_module + + return gui_module, hal_module + + # Function to measure the frequency of iterations + def measure_frequency(self): + previous_time = datetime.now() + # An infinite loop + while True: + # Sleep for 2 seconds + time.sleep(2) + + # Measure the current time and subtract from the previous time to get real time interval + current_time = datetime.now() + dt = current_time - previous_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + previous_time = current_time + + # Get the time period + try: + # Division by zero + self.measured_cycle = ms / self.iteration_counter + except: + self.measured_cycle = 0 + + # Reset the counter + self.iteration_counter = 0 + + # Send to client + self.send_frequency_message() + + # Function to generate and send frequency messages + def send_frequency_message(self): + # This function generates and sends frequency measures of the brain and gui + brain_frequency = 0; gui_frequency = 0 + try: + brain_frequency = round(1000 / self.measured_cycle, 1) + except ZeroDivisionError: + brain_frequency = 0 + + try: + gui_frequency = round(1000 / self.thread_gui.measured_cycle, 1) + except ZeroDivisionError: + gui_frequency = 0 + + self.frequency_message["brain"] = brain_frequency + self.frequency_message["gui"] = gui_frequency + self.frequency_message["rtf"] = self.real_time_factor + + message = "#freq" + json.dumps(self.frequency_message) + self.server.send_message(self.client, message) + + def send_ping_message(self): + self.server.send_message(self.client, "#ping") + + # Function to notify the front end that the code was received and sent to execution + def send_code_message(self): + self.server.send_message(self.client, "#exec") + + # Function to track the real time factor from Gazebo statistics + # https://stackoverflow.com/a/17698359 + # (For reference, Python3 solution specified in the same answer) + def track_stats(self): + args = ["gz", "stats", "-p"] + # Prints gz statistics. "-p": Output comma-separated values containing- + # real-time factor (percent), simtime (sec), realtime (sec), paused (T or F) + stats_process = subprocess.Popen(args, stdout=subprocess.PIPE, bufsize=1, universal_newlines=True) + # bufsize=1 enables line-bufferred mode (the input buffer is flushed + # automatically on newlines if you would write to process.stdin ) + with stats_process.stdout: + for line in iter(stats_process.stdout.readline, ''): + stats_list = [x.strip() for x in line.split(',')] + self.real_time_factor = stats_list[0] + + # Function to maintain thread execution + def execute_thread(self, source_code): + # Keep checking until the thread is alive + # The thread will die when the coming iteration reads the flag + if self.thread is not None: + while self.thread.is_alive(): + time.sleep(0.2) + + # Turn the flag down, the iteration has successfully stopped! + self.reload = False + # New thread execution + self.thread = threading.Thread(target=self.process_code, args=[source_code]) + self.thread.start() + self.send_code_message() + print("New Thread Started!") + + # Function to read and set frequency from incoming message + def read_frequency_message(self, message): + frequency_message = json.loads(message) + + # Set brain frequency + frequency = float(frequency_message["brain"]) + self.ideal_cycle = 1000.0 / frequency + + # Set gui frequency + frequency = float(frequency_message["gui"]) + self.thread_gui.ideal_cycle = 1000.0 / frequency + + return + + # The websocket function + # Gets called when there is an incoming message from the client + def handle(self, client, server, message): + if message[:5] == "#freq": + frequency_message = message[5:] + self.read_frequency_message(frequency_message) + time.sleep(1) + return + + elif(message[:5] == "#ping"): + time.sleep(1) + self.send_ping_message() + return + + elif (message[:5] == "#code"): + try: + # Once received turn the reload flag up and send it to execute_thread function + self.user_code = message[6:] + # print(repr(code)) + self.reload = True + self.execute_thread(self.user_code) + except: + pass + + elif (message[:5] == "#rest"): + try: + self.reload = True + self.stop_brain = True + self.execute_thread(self.user_code) + except: + pass + + elif (message[:5] == "#stop"): + self.stop_brain = True + + elif (message[:5] == "#play"): + self.stop_brain = False + + # Function that gets called when the server is connected + def connected(self, client, server): + self.client = client + # Start the GUI update thread + self.thread_gui = ThreadGUI(self.gui) + self.thread_gui.start() + + # Start the real time factor tracker thread + self.stats_thread = threading.Thread(target=self.track_stats) + self.stats_thread.start() + + # Start measure frequency + self.measure_thread = threading.Thread(target=self.measure_frequency) + self.measure_thread.start() + + print(client, 'connected') + + # Function that gets called when the connected closes + def handle_close(self, client, server): + print(client, 'closed') + + def run_server(self): + self.server = WebsocketServer(port=1905, host=self.host) + self.server.set_fn_new_client(self.connected) + self.server.set_fn_client_left(self.handle_close) + self.server.set_fn_message_received(self.handle) + + logged = False + while not logged: + try: + f = open("/ws_code.log", "w") + f.write("websocket_code=ready") + f.close() + logged = True + except: + time.sleep(0.1) + + self.server.run_forever() + + +# Execute! +if __name__ == "__main__": + server = Template() + server.run_server() \ No newline at end of file diff --git a/exercises/static/exercises/position_control/web-template/gui.py b/exercises/static/exercises/position_control/web-template/gui.py new file mode 100644 index 000000000..51d327f1a --- /dev/null +++ b/exercises/static/exercises/position_control/web-template/gui.py @@ -0,0 +1,248 @@ +import json +import cv2 +import base64 +import threading +import time +from datetime import datetime +from websocket_server import WebsocketServer + + +# Graphical User Interface Class +class GUI: + # Initialization function + # The actual initialization + def __init__(self, host): + t = threading.Thread(target=self.run_server) + + self.payload = {'image': ''} + self.left_payload = {'image': ''} + self.server = None + self.client = None + + self.host = host + + # Image variables + self.image_to_be_shown = None + self.image_to_be_shown_updated = False + self.image_show_lock = threading.Lock() + + self.left_image_to_be_shown = None + self.left_image_to_be_shown_updated = False + self.left_image_show_lock = threading.Lock() + + self.acknowledge = False + self.acknowledge_lock = threading.Lock() + + # Take the console object to set the same websocket and client + t.start() + + # Explicit initialization function + # Class method, so user can call it without instantiation + @classmethod + def initGUI(cls, host): + # self.payload = {'image': '', 'shape': []} + new_instance = cls(host) + return new_instance + + # Function to prepare image payload + # Encodes the image as a JSON string and sends through the WS + def payloadImage(self): + self.image_show_lock.acquire() + image_to_be_shown_updated = self.image_to_be_shown_updated + image_to_be_shown = self.image_to_be_shown + self.image_show_lock.release() + + image = image_to_be_shown + payload = {'image': '', 'shape': ''} + + if not image_to_be_shown_updated: + return payload + + shape = image.shape + frame = cv2.imencode('.JPEG', image)[1] + encoded_image = base64.b64encode(frame) + + payload['image'] = encoded_image.decode('utf-8') + payload['shape'] = shape + + self.image_show_lock.acquire() + self.image_to_be_shown_updated = False + self.image_show_lock.release() + + return payload + + # Function to prepare image payload + # Encodes the image as a JSON string and sends through the WS + def payloadLeftImage(self): + self.left_image_show_lock.acquire() + left_image_to_be_shown_updated = self.left_image_to_be_shown_updated + left_image_to_be_shown = self.left_image_to_be_shown + self.left_image_show_lock.release() + + image = left_image_to_be_shown + payload = {'image': '', 'shape': ''} + + if not left_image_to_be_shown_updated: + return payload + + shape = image.shape + frame = cv2.imencode('.JPEG', image)[1] + encoded_image = base64.b64encode(frame) + + payload['image'] = encoded_image.decode('utf-8') + payload['shape'] = shape + + self.left_image_show_lock.acquire() + self.left_image_to_be_shown_updated = False + self.left_image_show_lock.release() + + return payload + + # Function for student to call + def showImage(self, image): + self.image_show_lock.acquire() + self.image_to_be_shown = image + self.image_to_be_shown_updated = True + self.image_show_lock.release() + + # Function for student to call + def showLeftImage(self, image): + self.left_image_show_lock.acquire() + self.left_image_to_be_shown = image + self.left_image_to_be_shown_updated = True + self.left_image_show_lock.release() + + # Function to get the client + # Called when a new client is received + def get_client(self, client, server): + self.client = client + + # Function to get value of Acknowledge + def get_acknowledge(self): + self.acknowledge_lock.acquire() + acknowledge = self.acknowledge + self.acknowledge_lock.release() + + return acknowledge + + # Function to get value of Acknowledge + def set_acknowledge(self, value): + self.acknowledge_lock.acquire() + self.acknowledge = value + self.acknowledge_lock.release() + + # Update the gui + def update_gui(self): + # Payload Image Message + payload = self.payloadImage() + self.payload["image"] = json.dumps(payload) + + message = "#gui" + json.dumps(self.payload) + self.server.send_message(self.client, message) + + # Payload Left Image Message + left_payload = self.payloadLeftImage() + self.left_payload["image"] = json.dumps(left_payload) + + message = "#gul" + json.dumps(self.left_payload) + self.server.send_message(self.client, message) + + # Function to read the message from websocket + # Gets called when there is an incoming message from the client + def get_message(self, client, server, message): + # Acknowledge Message for GUI Thread + if message[:4] == "#ack": + self.set_acknowledge(True) + + # Activate the server + def run_server(self): + self.server = WebsocketServer(port=2303, host=self.host) + self.server.set_fn_new_client(self.get_client) + self.server.set_fn_message_received(self.get_message) + + logged = False + while not logged: + try: + f = open("/ws_gui.log", "w") + f.write("websocket_gui=ready") + f.close() + logged = True + except: + time.sleep(0.1) + + self.server.run_forever() + + # Function to reset + def reset_gui(self): + pass + + +# This class decouples the user thread +# and the GUI update thread +class ThreadGUI: + def __init__(self, gui): + self.gui = gui + + # Time variables + self.ideal_cycle = 80 + self.measured_cycle = 80 + self.iteration_counter = 0 + + # Function to start the execution of threads + def start(self): + self.measure_thread = threading.Thread(target=self.measure_thread) + self.thread = threading.Thread(target=self.run) + + self.measure_thread.start() + self.thread.start() + + print("GUI Thread Started!") + + # The measuring thread to measure frequency + def measure_thread(self): + while self.gui.client is None: + pass + + previous_time = datetime.now() + while True: + # Sleep for 2 seconds + time.sleep(2) + + # Measure the current time and subtract from previous time to get real time interval + current_time = datetime.now() + dt = current_time - previous_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + previous_time = current_time + + # Get the time period + try: + # Division by zero + self.measured_cycle = ms / self.iteration_counter + except: + self.measured_cycle = 0 + + # Reset the counter + self.iteration_counter = 0 + + # The main thread of execution + def run(self): + while self.gui.client is None: + pass + + while True: + start_time = datetime.now() + self.gui.update_gui() + acknowledge_message = self.gui.get_acknowledge() + + while not acknowledge_message: + acknowledge_message = self.gui.get_acknowledge() + + self.gui.set_acknowledge(False) + + finish_time = datetime.now() + self.iteration_counter = self.iteration_counter + 1 + + dt = finish_time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + if ms < self.ideal_cycle: + time.sleep((self.ideal_cycle-ms) / 1000.0) diff --git a/exercises/static/exercises/position_control/web-template/hal.py b/exercises/static/exercises/position_control/web-template/hal.py new file mode 100644 index 000000000..4f11a1fbc --- /dev/null +++ b/exercises/static/exercises/position_control/web-template/hal.py @@ -0,0 +1,98 @@ +import numpy as np +import rospy +import cv2 + +from drone_wrapper import DroneWrapper +from Beacon import Beacon + + +# Hardware Abstraction Layer +class HAL: + IMG_WIDTH = 320 + IMG_HEIGHT = 240 + + def __init__(self): + rospy.init_node("HAL") + + self.image = None + self.drone = DroneWrapper(name="rqt") + + # Explicit initialization functions + # Class method, so user can call it without instantiation + @classmethod + def initRobot(cls): + new_instance = cls() + return new_instance + + # Get Image from ROS Driver Camera + def get_frontal_image(self): + image = self.drone.get_frontal_image() + image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) + return image_rgb + + def get_ventral_image(self): + image = self.drone.get_ventral_image() + image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) + return image_rgb + + def get_position(self): + pos = self.drone.get_position() + return pos + + def get_velocity(self): + vel = self.drone.get_velocity() + return vel + + def get_yaw_rate(self): + yaw_rate = self.drone.get_yaw_rate() + return yaw_rate + + def get_orientation(self): + orientation = self.drone.get_orientation() + return orientation + + def get_roll(self): + roll = self.drone.get_roll() + return roll + + def get_pitch(self): + pitch = self.drone.get_pitch() + return pitch + + def get_yaw(self): + yaw = self.drone.get_yaw() + return yaw + + def get_landed_state(self): + state = self.drone.get_landed_state() + return state + + def set_cmd_pos(self, x, y, z, az): + self.drone.set_cmd_pos(x, y, z, az) + + def set_cmd_vel(self, vx, vy, vz, az): + self.drone.set_cmd_vel(vx, vy, vz, az) + + def set_cmd_mix(self, vx, vy, z, az): + self.drone.set_cmd_mix(vx, vy, z, az) + + def takeoff(self, h=3): + self.drone.takeoff(h) + + def land(self): + self.drone.land() + + def init_beacons(self): + self.beacons = [] + self.beacons.append(Beacon('beacon1', np.array([0, 5, 0]), False, False)) + self.beacons.append(Beacon('beacon2', np.array([5, 0, 0]), False, False)) + self.beacons.append(Beacon('beacon3', np.array([0, -5, 0]), False, False)) + self.beacons.append(Beacon('beacon4', np.array([-5, 0, 0]), False, False)) + self.beacons.append(Beacon('beacon5', np.array([10, 0, 0]), False, False)) + self.beacons.append(Beacon('initial', np.array([0, 0, 0]), False, False)) + + def get_next_beacon(self): + for beacon in self.beacons: + if beacon.is_reached() == False: + return beacon + return None \ No newline at end of file diff --git a/exercises/static/exercises/position_control/web-template/interfaces/__init__.py b/exercises/static/exercises/position_control/web-template/interfaces/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/exercises/static/exercises/position_control/web-template/interfaces/camera.py b/exercises/static/exercises/position_control/web-template/interfaces/camera.py new file mode 100644 index 000000000..5a021a13e --- /dev/null +++ b/exercises/static/exercises/position_control/web-template/interfaces/camera.py @@ -0,0 +1,89 @@ +import rospy +from sensor_msgs.msg import Image as ImageROS +import threading +from math import pi as PI +import cv2 +from cv_bridge import CvBridge, CvBridgeError + + +MAXRANGE = 8 # max length received from imageD +MINRANGE = 0 + + +def imageMsg2Image(img, bridge): + + image = Image() + + image.width = img.width + image.height = img.height + image.format = "BGR8" + image.timeStamp = img.header.stamp.secs + (img.header.stamp.nsecs * 1e-9) + cv_image = 0 + if img.encoding[-2:] == "C1": + gray_img_buff = bridge.imgmsg_to_cv2(img, img.encoding) + cv_image = depthToRGB8(gray_img_buff, img.encoding) + else: + cv_image = bridge.imgmsg_to_cv2(img, "bgr8") + image.data = cv_image + return image + + +import numpy as np + + +class Image: + + def __init__(self): + + self.height = 3 # Image height [pixels] + self.width = 3 # Image width [pixels] + self.timeStamp = 0 # Time stamp [s] */ + self.format = "" # Image format string (RGB8, BGR,...) + self.data = np.zeros((self.height, self.width, 3), np.uint8) # The image data itself + self.data.shape = self.height, self.width, 3 + + def __str__(self): + s = "Image: {\n height: " + str(self.height) + "\n width: " + str(self.width) + s = s + "\n format: " + self.format + "\n timeStamp: " + str(self.timeStamp) + s = s + "\n data: " + str(self.data) + "\n}" + return s + + +class ListenerCamera: + + def __init__(self, topic): + + self.topic = topic + self.data = Image() + self.sub = None + self.lock = threading.Lock() + + self.bridge = CvBridge() + self.start() + + def __callback(self, img): + + image = imageMsg2Image(img, self.bridge) + + self.lock.acquire() + self.data = image + self.lock.release() + + def stop(self): + + self.sub.unregister() + + def start(self): + self.sub = rospy.Subscriber(self.topic, ImageROS, self.__callback) + + def getImage(self): + + self.lock.acquire() + image = self.data + self.lock.release() + + return image + + def hasproxy(self): + + return hasattr(self, "sub") and self.sub diff --git a/exercises/static/exercises/position_control/web-template/interfaces/motors.py b/exercises/static/exercises/position_control/web-template/interfaces/motors.py new file mode 100644 index 000000000..70dca8a46 --- /dev/null +++ b/exercises/static/exercises/position_control/web-template/interfaces/motors.py @@ -0,0 +1,123 @@ +import rospy +from geometry_msgs.msg import Twist +import threading +from math import pi as PI +from .threadPublisher import ThreadPublisher + + + +def cmdvel2Twist(vel): + + tw = Twist() + tw.linear.x = vel.vx + tw.linear.y = vel.vy + tw.linear.z = vel.vz + tw.angular.x = vel.ax + tw.angular.y = vel.ay + tw.angular.z = vel.az + + return tw + + +class CMDVel (): + + def __init__(self): + + self.vx = 0 # vel in x[m/s] (use this for V in wheeled robots) + self.vy = 0 # vel in y[m/s] + self.vz = 0 # vel in z[m/s] + self.ax = 0 # angular vel in X axis [rad/s] + self.ay = 0 # angular vel in X axis [rad/s] + self.az = 0 # angular vel in Z axis [rad/s] (use this for W in wheeled robots) + self.timeStamp = 0 # Time stamp [s] + + + def __str__(self): + s = "CMDVel: {\n vx: " + str(self.vx) + "\n vy: " + str(self.vy) + s = s + "\n vz: " + str(self.vz) + "\n ax: " + str(self.ax) + s = s + "\n ay: " + str(self.ay) + "\n az: " + str(self.az) + s = s + "\n timeStamp: " + str(self.timeStamp) + "\n}" + return s + +class PublisherMotors: + + def __init__(self, topic, maxV, maxW): + + self.maxW = maxW + self.maxV = maxV + + self.topic = topic + self.data = CMDVel() + self.pub = rospy.Publisher(self.topic, Twist, queue_size=1) + + self.lock = threading.Lock() + + self.kill_event = threading.Event() + self.thread = ThreadPublisher(self, self.kill_event) + + self.thread.daemon = True + self.start() + + def publish (self): + + self.lock.acquire() + tw = cmdvel2Twist(self.data) + self.lock.release() + self.pub.publish(tw) + + def stop(self): + + self.kill_event.set() + self.pub.unregister() + + def start (self): + + self.kill_event.clear() + self.thread.start() + + + + def getMaxW(self): + return self.maxW + + def getMaxV(self): + return self.maxV + + + def sendVelocities(self, vel): + + self.lock.acquire() + self.data = vel + self.lock.release() + + def sendV(self, v): + + self.sendVX(v) + + def sendL(self, l): + + self.sendVY(l) + + def sendW(self, w): + + self.sendAZ(w) + + def sendVX(self, vx): + + self.lock.acquire() + self.data.vx = vx + self.lock.release() + + def sendVY(self, vy): + + self.lock.acquire() + self.data.vy = vy + self.lock.release() + + def sendAZ(self, az): + + self.lock.acquire() + self.data.az = az + self.lock.release() + + diff --git a/exercises/static/exercises/position_control/web-template/interfaces/pose3d.py b/exercises/static/exercises/position_control/web-template/interfaces/pose3d.py new file mode 100644 index 000000000..fd0bfc37a --- /dev/null +++ b/exercises/static/exercises/position_control/web-template/interfaces/pose3d.py @@ -0,0 +1,176 @@ +import rospy +import threading +from math import asin, atan2, pi +from nav_msgs.msg import Odometry + +def quat2Yaw(qw, qx, qy, qz): + ''' + Translates from Quaternion to Yaw. + + @param qw,qx,qy,qz: Quaternion values + + @type qw,qx,qy,qz: float + + @return Yaw value translated from Quaternion + + ''' + rotateZa0=2.0*(qx*qy + qw*qz) + rotateZa1=qw*qw + qx*qx - qy*qy - qz*qz + rotateZ=0.0 + if(rotateZa0 != 0.0 and rotateZa1 != 0.0): + rotateZ=atan2(rotateZa0,rotateZa1) + return rotateZ + +def quat2Pitch(qw, qx, qy, qz): + ''' + Translates from Quaternion to Pitch. + + @param qw,qx,qy,qz: Quaternion values + + @type qw,qx,qy,qz: float + + @return Pitch value translated from Quaternion + + ''' + + rotateYa0=-2.0*(qx*qz - qw*qy) + rotateY=0.0 + if(rotateYa0 >= 1.0): + rotateY = pi/2.0 + elif(rotateYa0 <= -1.0): + rotateY = -pi/2.0 + else: + rotateY = asin(rotateYa0) + + return rotateY + +def quat2Roll (qw, qx, qy, qz): + ''' + Translates from Quaternion to Roll. + + @param qw,qx,qy,qz: Quaternion values + + @type qw,qx,qy,qz: float + + @return Roll value translated from Quaternion + + ''' + rotateXa0=2.0*(qy*qz + qw*qx) + rotateXa1=qw*qw - qx*qx - qy*qy + qz*qz + rotateX=0.0 + + if(rotateXa0 != 0.0 and rotateXa1 != 0.0): + rotateX=atan2(rotateXa0, rotateXa1) + return rotateX + + +def odometry2Pose3D(odom): + ''' + Translates from ROS Odometry to JderobotTypes Pose3d. + + @param odom: ROS Odometry to translate + + @type odom: Odometry + + @return a Pose3d translated from odom + + ''' + pose = Pose3d() + ori = odom.pose.pose.orientation + + pose.x = odom.pose.pose.position.x + pose.y = odom.pose.pose.position.y + pose.z = odom.pose.pose.position.z + #pose.h = odom.pose.pose.position.h + pose.yaw = quat2Yaw(ori.w, ori.x, ori.y, ori.z) + pose.pitch = quat2Pitch(ori.w, ori.x, ori.y, ori.z) + pose.roll = quat2Roll(ori.w, ori.x, ori.y, ori.z) + pose.q = [ori.w, ori.x, ori.y, ori.z] + pose.timeStamp = odom.header.stamp.secs + (odom.header.stamp.nsecs *1e-9) + + return pose + +class Pose3d (): + + def __init__(self): + + self.x = 0 # X coord [meters] + self.y = 0 # Y coord [meters] + self.z = 0 # Z coord [meters] + self.h = 1 # H param + self.yaw = 0 #Yaw angle[rads] + self.pitch = 0 # Pitch angle[rads] + self.roll = 0 # Roll angle[rads] + self.q = [0,0,0,0] # Quaternion + self.timeStamp = 0 # Time stamp [s] + + + def __str__(self): + s = "Pose3D: {\n x: " + str(self.x) + "\n Y: " + str(self.y) + s = s + "\n Z: " + str(self.z) + "\n H: " + str(self.h) + s = s + "\n Yaw: " + str(self.yaw) + "\n Pitch: " + str(self.pitch) + "\n Roll: " + str(self.roll) + s = s + "\n quaternion: " + str(self.q) + "\n timeStamp: " + str(self.timeStamp) + "\n}" + return s + + +class ListenerPose3d: + ''' + ROS Pose3D Subscriber. Pose3D Client to Receive pose3d from ROS nodes. + ''' + def __init__(self, topic): + ''' + ListenerPose3d Constructor. + + @param topic: ROS topic to subscribe + + @type topic: String + + ''' + self.topic = topic + self.data = Pose3d() + self.sub = None + self.lock = threading.Lock() + self.start() + + def __callback (self, odom): + ''' + Callback function to receive and save Pose3d. + + @param odom: ROS Odometry received + + @type odom: Odometry + + ''' + pose = odometry2Pose3D(odom) + + self.lock.acquire() + self.data = pose + self.lock.release() + + def stop(self): + ''' + Stops (Unregisters) the client. + + ''' + self.sub.unregister() + + def start (self): + ''' + Starts (Subscribes) the client. + + ''' + self.sub = rospy.Subscriber(self.topic, Odometry, self.__callback) + + def getPose3d(self): + ''' + Returns last Pose3d. + + @return last JdeRobotTypes Pose3d saved + + ''' + self.lock.acquire() + pose = self.data + self.lock.release() + + return pose + diff --git a/exercises/static/exercises/position_control/web-template/interfaces/threadPublisher.py b/exercises/static/exercises/position_control/web-template/interfaces/threadPublisher.py new file mode 100644 index 000000000..69aa0ad48 --- /dev/null +++ b/exercises/static/exercises/position_control/web-template/interfaces/threadPublisher.py @@ -0,0 +1,46 @@ +# +# Copyright (C) 1997-2016 JDE Developers Team +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. +# Authors : +# Alberto Martin Florido +# Aitor Martinez Fernandez +# +import threading +import time +from datetime import datetime + +time_cycle = 80 + + +class ThreadPublisher(threading.Thread): + + def __init__(self, pub, kill_event): + self.pub = pub + self.kill_event = kill_event + threading.Thread.__init__(self, args=kill_event) + + def run(self): + while (not self.kill_event.is_set()): + start_time = datetime.now() + + self.pub.publish() + + finish_Time = datetime.now() + + dt = finish_Time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + #print (ms) + if (ms < time_cycle): + time.sleep((time_cycle - ms) / 1000.0) \ No newline at end of file diff --git a/exercises/static/exercises/position_control/web-template/interfaces/threadStoppable.py b/exercises/static/exercises/position_control/web-template/interfaces/threadStoppable.py new file mode 100644 index 000000000..b631d180f --- /dev/null +++ b/exercises/static/exercises/position_control/web-template/interfaces/threadStoppable.py @@ -0,0 +1,36 @@ +import threading +import time +from datetime import datetime + +time_cycle = 80 + + +class StoppableThread(threading.Thread): + """Thread class with a stop() method. The thread itself has to check + regularly for the stopped() condition.""" + + def __init__(self, target, kill_event=threading.Event(), *args, **kwargs): + super(StoppableThread, self).__init__(*args, **kwargs) + self._target = target + self._target_args = kwargs["args"] + self._kill_event = kill_event + + def run(self): + while not self.stopped(): + start_time = datetime.now() + + self._target(*self._target_args) + + finish_time = datetime.now() + + dt = finish_time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + # print (ms) + if ms < time_cycle: + time.sleep((time_cycle - ms) / 1000.0) + + def stop(self): + self._kill_event.set() + + def stopped(self): + return self._kill_event.is_set() diff --git a/exercises/static/exercises/position_control/web-template/launch/gazebo.launch b/exercises/static/exercises/position_control/web-template/launch/gazebo.launch new file mode 100644 index 000000000..9e5114f8d --- /dev/null +++ b/exercises/static/exercises/position_control/web-template/launch/gazebo.launch @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/exercises/static/exercises/position_control/web-template/launch/launch.py b/exercises/static/exercises/position_control/web-template/launch/launch.py new file mode 100644 index 000000000..6e4c166ad --- /dev/null +++ b/exercises/static/exercises/position_control/web-template/launch/launch.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python3 + +import stat +import rospy +from os import lstat +from subprocess import Popen, PIPE + + +DRI_PATH = "/dev/dri/card0" +EXERCISE = "position_control" +TIMEOUT = 30 +MAX_ATTEMPT = 2 + + +# Check if acceleration can be enabled +def check_device(device_path): + try: + return stat.S_ISCHR(lstat(device_path)[stat.ST_MODE]) + except: + return False + + +# Spawn new process +def spawn_process(args, insert_vglrun=False): + if insert_vglrun: + args.insert(0, "vglrun") + process = Popen(args, stdout=PIPE, bufsize=1, universal_newlines=True) + return process + + +class Test(): + def gazebo(self): + rospy.logwarn("[GAZEBO] Launching") + try: + rospy.wait_for_service("/gazebo/get_model_properties", TIMEOUT) + return True + except rospy.ROSException: + return False + + def px4(self): + rospy.logwarn("[PX4-SITL] Launching") + start_time = rospy.get_time() + args = ["./PX4-Autopilot/build/px4_sitl_default/bin/px4-commander","--instance", "0", "check"] + while rospy.get_time() - start_time < TIMEOUT: + process = spawn_process(args, insert_vglrun=False) + with process.stdout: + for line in iter(process.stdout.readline, ''): + if ("Prearm check: OK" in line): + return True + rospy.sleep(2) + return False + + def mavros(self, ns=""): + rospy.logwarn("[MAVROS] Launching") + try: + rospy.wait_for_service(ns + "/mavros/cmd/arming", TIMEOUT) + return True + except rospy.ROSException: + return False + + +class Launch(): + def __init__(self): + self.test = Test() + self.acceleration_enabled = check_device(DRI_PATH) + + # Start roscore + args = ["/opt/ros/noetic/bin/roscore"] + spawn_process(args, insert_vglrun=False) + + rospy.init_node("launch", anonymous=True) + + def start(self): + ######## LAUNCH GAZEBO ######## + args = ["/opt/ros/noetic/bin/roslaunch", + "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/gazebo.launch", + "--wait", + "--log" + ] + + attempt = 1 + while True: + spawn_process(args, insert_vglrun=self.acceleration_enabled) + if self.test.gazebo() == True: + break + if attempt == MAX_ATTEMPT: + rospy.logerr("[GAZEBO] Launch Failed") + return + attempt = attempt + 1 + + + ######## LAUNCH PX4 ######## + args = ["/opt/ros/noetic/bin/roslaunch", + "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/px4.launch", + "--log" + ] + + attempt = 1 + while True: + spawn_process(args, insert_vglrun=self.acceleration_enabled) + if self.test.px4() == True: + break + if attempt == MAX_ATTEMPT: + rospy.logerr("[PX4] Launch Failed") + return + attempt = attempt + 1 + + + ######## LAUNCH MAVROS ######## + args = ["/opt/ros/noetic/bin/roslaunch", + "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/mavros.launch", + "--log" + ] + + attempt = 1 + while True: + spawn_process(args, insert_vglrun=self.acceleration_enabled) + if self.test.mavros() == True: + break + if attempt == MAX_ATTEMPT: + rospy.logerr("[MAVROS] Launch Failed") + return + attempt = attempt + 1 + + +if __name__ == "__main__": + launch = Launch() + launch.start() + + with open("/drones_launch.log", "w") as f: + f.write("success") diff --git a/exercises/static/exercises/position_control/web-template/launch/mavros.launch b/exercises/static/exercises/position_control/web-template/launch/mavros.launch new file mode 100644 index 000000000..b899c0ec1 --- /dev/null +++ b/exercises/static/exercises/position_control/web-template/launch/mavros.launch @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/exercises/static/exercises/position_control/web-template/launch/px4.launch b/exercises/static/exercises/position_control/web-template/launch/px4.launch new file mode 100644 index 000000000..43f1f66a9 --- /dev/null +++ b/exercises/static/exercises/position_control/web-template/launch/px4.launch @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/exercises/static/exercises/position_control/web-template/position_control.world b/exercises/static/exercises/position_control/web-template/position_control.world new file mode 100644 index 000000000..f5e9a5955 --- /dev/null +++ b/exercises/static/exercises/position_control/web-template/position_control.world @@ -0,0 +1,90 @@ + + + + + + + + beacon1 + model://construction_cone + 0 5 0 0 0 0 + + + beacon2 + model://construction_cone + 5 0 0 0 0 0 + + + beacon3 + model://construction_cone + 0 -5 0 0 0 0 + + + beacon4 + model://construction_cone + -5 0 0 0 0 0 + + + beacon5 + model://construction_cone + 10 0 0 0 0 0 + + + + + model://slab + + + + + model://grass_plane + + + + + logo + model://logoJdeRobot + -12.0 0.0 0.0 0.0 0.0 0.0 + + + + 0 + 0.4 0.4 0.4 1.0 + 0.7 0.7 0.7 1 + + + 12 + + + + + + + model://sun + + + + + 0 0 -9.8066 + + + quick + 10 + 1.3 + 0 + + + 0 + 0.2 + 100 + 0.001 + + + 0.004 + 1 + 250 + 6.0e-6 2.3e-5 -4.2e-5 + + + + \ No newline at end of file diff --git a/exercises/static/exercises/power_tower_inspection/README.md b/exercises/static/exercises/power_tower_inspection/README.md new file mode 100755 index 000000000..0f042caac --- /dev/null +++ b/exercises/static/exercises/power_tower_inspection/README.md @@ -0,0 +1 @@ +[Exercise Documentation Website](https://jderobot.github.io/RoboticsAcademy/exercises/Drones/power_tower_inspection) diff --git a/exercises/static/exercises/power_tower_inspection/my_solution.py b/exercises/static/exercises/power_tower_inspection/my_solution.py new file mode 100755 index 000000000..17ebd9257 --- /dev/null +++ b/exercises/static/exercises/power_tower_inspection/my_solution.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python + +import rospy +import numpy as np +import cv2 +from drone_wrapper import DroneWrapper +from std_msgs.msg import Bool, Float64 +from sensor_msgs.msg import Image +from geometry_msgs.msg import Twist, Pose + +code_live_flag = False + +def gui_play_stop_cb(msg): + global code_live_flag, code_live_timer + if msg.data == True: + if not code_live_flag: + code_live_flag = True + code_live_timer = rospy.Timer(rospy.Duration(nsecs=50000000), execute) + else: + if code_live_flag: + code_live_flag = False + code_live_timer.shutdown() + + +def set_image_filtered(img): + gui_filtered_img_pub.publish(HAL.bridge.cv2_to_imgmsg(img)) + + +def set_image_threshed(img): + gui_threshed_img_pub.publish(HAL.bridge.cv2_to_imgmsg(img)) + + +def execute(event): + global HAL + img_frontal = HAL.get_frontal_image() + img_ventral = HAL.get_ventral_image() + # Both the above images are cv2 images + ################# Insert your code here ################################# + + set_image_filtered(img_frontal) + set_image_threshed(img_ventral) + + ######################################################################### + + +if __name__ == "__main__": + HAL = DroneWrapper() # Hardware Abstraction Layer for drone + rospy.Subscriber('gui/play_stop', Bool, gui_play_stop_cb) + gui_filtered_img_pub = rospy.Publisher('interface/filtered_img', Image, queue_size = 1) + gui_threshed_img_pub = rospy.Publisher('interface/threshed_img', Image, queue_size = 1) + code_live_flag = False + code_live_timer = rospy.Timer(rospy.Duration(nsecs=50000000), execute) + code_live_timer.shutdown() + while not rospy.is_shutdown(): + rospy.spin() \ No newline at end of file diff --git a/exercises/static/exercises/power_tower_inspection/power_tower_inspection.launch b/exercises/static/exercises/power_tower_inspection/power_tower_inspection.launch new file mode 100644 index 000000000..1268284fe --- /dev/null +++ b/exercises/static/exercises/power_tower_inspection/power_tower_inspection.launch @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/exercises/static/exercises/power_tower_inspection/power_tower_inspection.world b/exercises/static/exercises/power_tower_inspection/power_tower_inspection.world new file mode 100644 index 000000000..31fa59c27 --- /dev/null +++ b/exercises/static/exercises/power_tower_inspection/power_tower_inspection.world @@ -0,0 +1,353 @@ + + + + + + + -4.70385 10.895 16.2659 -0 0.921795 -1.12701 + orbit + perspective + + + + + + model://sun + + + + + model://grass_plane + 0 0 -0.045 0 0 + + + + + model://logoJdeRobot + 20 -5 0 0 0 0 + 0.25 0.25 0.25 + + + + + model://logoPadGreen + -21.0 -4.0 0 0 0 + launching_pad + + + + + model://power_tower_danube_wires + -41 -4 0.05 0 0 + power_tower_wires0 + + + + + model://power_tower_danube + -1 -4 0.05 0 0 + power_tower1 + + + + + model://power_tower_danube_wires + -1 -4 0.05 0 0 + power_tower_wires1 + + + + + + model://power_tower_danube + 39 -4 0.05 0 0 + power_tower2 + + + + + model://power_tower_danube_wires + 39 -4 0.05 0 0 + power_tower_wires2 + + + + + model://power_tower_danube + 79 -4 0.05 0 0 + power_tower3 + + + + + model://power_tower_danube_wires + 79 -4 0.05 0 0 + power_tower_wires3 + + + + + + + model://rust_defect_01 + + -2.31 -4 3.79 0 0.17 + rust_def1_tower1 + + + + + model://rust_defect_01 + + 0.232054 -4.00614 3.96187 0 -0.15255 0 + rust_def1_tower1_opp + + + + + model://rust_defect_02 + + -1.80605 -4.40198 14.7671 0 0.0 + rust_def2_tower1 + + + + + model://rust_defect_02 + + -0.438877 -4.04055 14.7724 0 0.0 0 + rust_def2_tower1_opp + + + + + model://rust_defect_02 + + -1.81251 -3.67691 14.7727 0 0.0 + rust_def3_tower1 + + + + + model://rust_defect_02 + + -0.435477 -3.59453 14.7738 0 0.0 0 + rust_def3_tower1_opp + + + + + model://rust_defect_03 + + -1.59 -4.33479 20.438 0 0.17 + rust_def4_tower1 + + + + + model://rust_defect_03 + + -0.577392 -3.56405 19.08 0 -0.028 0 + rust_def4_tower1_opp + + + + + model://rust_defect_03 + + -1.73 -3.51 19.1255 0 0.17 + rust_def5_tower1 + + + + + + model://rust_defect_01 + + 37.69 -4 3.79 0 0.17 + rust_def1_tower2 + + + + + model://rust_defect_01 + + 40.232054 -4.00614 3.96187 0 -0.15255 0 + rust_def1_tower2_opp + + + + + model://rust_defect_03 + + 38.19395 -4.40198 14.7671 0 0.0 + rust_def2_tower2 + + + + + model://rust_defect_02 + + 39.561123 -4.04055 14.7724 0 0.0 0 + rust_def2_tower2_opp + + + + + model://rust_defect_03 + + 38.18749 -3.67691 14.7727 0 0.0 + rust_def3_tower2 + + + + + model://rust_defect_02 + + 39.564523 -3.59453 14.7738 0 0.0 0 + rust_def3_tower2_opp + + + + + model://rust_defect_02 + + 38.41 -4.33479 20.438 0 0.17 + rust_def4_tower2 + + + + + model://rust_defect_03 + + 39.422608 -3.56405 19.08 0 -0.028 0 + rust_def4_tower2_opp + + + + + model://rust_defect_wire + -7.72635 -0.241255 12.3353 -0.004615 -0.109891 -0.001474 + rust_def_wire1 + + + + + model://rust_defect_wire + -11.058 1.31607 16.5176 -0.004602 -0.082412 -0.001602 + rust_def_wire2 + + + + + model://rust_defect_wire + 19.8548 2.72211 11.5241 -0.004587 -0.002014 -0.001971 + rust_def_wire3 + + + + + model://rust_defect_wire + 30.6961 -7.7434 12.1612 -0.004616 -0.113092 -0.001459 + rust_def_wire4 + + + + + model://rust_defect_wire + -2.37899 -0.352206 13.0936 -0.004633 -0.140673 -0.001331 + rust_def_spring_tower1 + + + + + model://rust_defect_wire + 37.8828 2.61147 13.101 -0.004636 -0.145252 -0.001309 + rust_def_spring_tower2 + + + + + model://nest1 + -1.3 1.56 12.55 0 0 0 + nest1 + + + + + model://egg + -1.3 1.5 13.3197 0 -0 1.57 + egg1 + + + + + model://bird + -1.06401 1.28978 13.4012 0 -0 -2.07145 + bird + + + + + model://nest1 + 14.86 5.48 2.51339 0 0 0 + nest2 + + + + + model://egg + 14.86 5.42 3.25989 0 0 0 + egg2 + + + + + model://electrical_box + 15 5 0 0 0 1.57 + electrical_box1 + + + + + model://electrical_box + 55 5 0 0 0 1.57 + electrical_box2 + + + + + + 0 + 0.4 0.4 0.4 1.0 + 0.7 0.7 0.7 1 + + + 12 + + + + + + 0 0 -9.8066 + + + quick + 10 + 1.3 + 0 + + + 0 + 0.2 + 100 + 0.001 + + + 0.004 + 1 + 250 + 6.0e-6 2.3e-5 -4.2e-5 + + + \ No newline at end of file diff --git a/exercises/static/exercises/power_tower_inspection/web-template/README.md b/exercises/static/exercises/power_tower_inspection/web-template/README.md new file mode 100644 index 000000000..0f042caac --- /dev/null +++ b/exercises/static/exercises/power_tower_inspection/web-template/README.md @@ -0,0 +1 @@ +[Exercise Documentation Website](https://jderobot.github.io/RoboticsAcademy/exercises/Drones/power_tower_inspection) diff --git a/exercises/static/exercises/power_tower_inspection/web-template/code/academy.py b/exercises/static/exercises/power_tower_inspection/web-template/code/academy.py new file mode 100644 index 000000000..7a59ce7f5 --- /dev/null +++ b/exercises/static/exercises/power_tower_inspection/web-template/code/academy.py @@ -0,0 +1,8 @@ +# Enter sequential code! +from GUI import GUI +from HAL import HAL + +while True: + # Enter iterative code! + img = HAL.get_ventral_image() + GUI.showImage(img) \ No newline at end of file diff --git a/exercises/static/exercises/power_tower_inspection/web-template/console.py b/exercises/static/exercises/power_tower_inspection/web-template/console.py new file mode 100755 index 000000000..23d0efad3 --- /dev/null +++ b/exercises/static/exercises/power_tower_inspection/web-template/console.py @@ -0,0 +1,18 @@ +# Functions to start and close console +import os +import sys + +def start_console(): + # Get all the file descriptors and choose the latest one + fds = os.listdir("/dev/pts/") + fds.sort() + console_fd = fds[-2] + + sys.stderr = open('/dev/pts/' + console_fd, 'w') + sys.stdout = open('/dev/pts/' + console_fd, 'w') + sys.stdin = open('/dev/pts/' + console_fd, 'w') + +def close_console(): + sys.stderr.close() + sys.stdout.close() + sys.stdin.close() \ No newline at end of file diff --git a/exercises/static/exercises/power_tower_inspection/web-template/exercise.py b/exercises/static/exercises/power_tower_inspection/web-template/exercise.py new file mode 100755 index 000000000..4450dd975 --- /dev/null +++ b/exercises/static/exercises/power_tower_inspection/web-template/exercise.py @@ -0,0 +1,355 @@ +#!/usr/bin/env python + +from __future__ import print_function + +from websocket_server import WebsocketServer +import time +import threading +import subprocess +import sys +from datetime import datetime +import re +import json +import importlib + +import rospy +from std_srvs.srv import Empty + +from gui import GUI, ThreadGUI +from hal import HAL +from console import start_console, close_console + + +class Template: + # Initialize class variables + # self.ideal_cycle to run an execution for at least 1 second + # self.process for the current running process + def __init__(self): + self.measure_thread = None + self.thread = None + self.reload = False + self.stop_brain = False + self.user_code = "" + + # Time variables + self.ideal_cycle = 80 + self.measured_cycle = 80 + self.iteration_counter = 0 + self.real_time_factor = 0 + self.frequency_message = {'brain': '', 'gui': '', 'rtf': ''} + + self.server = None + self.client = None + self.host = sys.argv[1] + + # Initialize the GUI, HAL and Console behind the scenes + self.hal = HAL() + self.gui = GUI(self.host) + + # Function to parse the code + # A few assumptions: + # 1. The user always passes sequential and iterative codes + # 2. Only a single infinite loop + def parse_code(self, source_code): + sequential_code, iterative_code = self.seperate_seq_iter(source_code) + return iterative_code, sequential_code + + # Function to separate the iterative and sequential code + def seperate_seq_iter(self, source_code): + if source_code == "": + return "", "" + + # Search for an instance of while True + infinite_loop = re.search(r'[^ ]while\s*\(\s*True\s*\)\s*:|[^ ]while\s*True\s*:|[^ ]while\s*1\s*:|[^ ]while\s*\(\s*1\s*\)\s*:', source_code) + + # Separate the content inside while True and the other + # (Separating the sequential and iterative part!) + try: + start_index = infinite_loop.start() + iterative_code = source_code[start_index:] + sequential_code = source_code[:start_index] + + # Remove while True: syntax from the code + # And remove the the 4 spaces indentation before each command + iterative_code = re.sub(r'[^ ]while\s*\(\s*True\s*\)\s*:|[^ ]while\s*True\s*:|[^ ]while\s*1\s*:|[^ ]while\s*\(\s*1\s*\)\s*:', '', iterative_code) + iterative_code = re.sub(r'^[ ]{4}', '', iterative_code, flags=re.M) + + except: + sequential_code = source_code + iterative_code = "" + + return sequential_code, iterative_code + + # The process function + def process_code(self, source_code): + # Redirect the information to console + start_console() + + iterative_code, sequential_code = self.parse_code(source_code) + + # print(sequential_code) + # print(iterative_code) + + # The Python exec function + # Run the sequential part + gui_module, hal_module = self.generate_modules() + reference_environment = {"GUI": gui_module, "HAL": hal_module} + exec(sequential_code, reference_environment) + + # Run the iterative part inside template + # and keep the check for flag + while self.reload == False: + while (self.stop_brain == True): + if (self.reload == True): + break + time.sleep(0.1) + + start_time = datetime.now() + + # Execute the iterative portion + exec(iterative_code, reference_environment) + + # Template specifics to run! + finish_time = datetime.now() + dt = finish_time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + + # Keep updating the iteration counter + if (iterative_code == ""): + self.iteration_counter = 0 + else: + self.iteration_counter = self.iteration_counter + 1 + + # The code should be run for atleast the target time step + # If it's less put to sleep + if (ms < self.ideal_cycle): + time.sleep((self.ideal_cycle - ms) / 1000.0) + + close_console() + print("Current Thread Joined!") + + # Function to generate the modules for use in ACE Editor + def generate_modules(self): + # Define HAL module + hal_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) + hal_module.HAL = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) + # hal_module.drone = imp.new_module("drone") + # motors# hal_module.HAL.motors = imp.new_module("motors") + + # Add HAL functions + hal_module.HAL.get_frontal_image = self.hal.get_frontal_image + hal_module.HAL.get_ventral_image = self.hal.get_ventral_image + hal_module.HAL.get_position = self.hal.get_position + hal_module.HAL.get_velocity = self.hal.get_velocity + hal_module.HAL.get_yaw_rate = self.hal.get_yaw_rate + hal_module.HAL.get_orientation = self.hal.get_orientation + hal_module.HAL.get_roll = self.hal.get_roll + hal_module.HAL.get_pitch = self.hal.get_pitch + hal_module.HAL.get_yaw = self.hal.get_yaw + hal_module.HAL.get_landed_state = self.hal.get_landed_state + hal_module.HAL.set_cmd_pos = self.hal.set_cmd_pos + hal_module.HAL.set_cmd_vel = self.hal.set_cmd_vel + hal_module.HAL.set_cmd_mix = self.hal.set_cmd_mix + hal_module.HAL.takeoff = self.hal.takeoff + hal_module.HAL.land = self.hal.land + + + + # Define GUI module + gui_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) + gui_module.GUI = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) + + # Add GUI functions + gui_module.GUI.showImage = self.gui.showImage + gui_module.GUI.showLeftImage = self.gui.showLeftImage + + # Adding modules to system + # Protip: The names should be different from + # other modules, otherwise some errors + sys.modules["HAL"] = hal_module + sys.modules["GUI"] = gui_module + + return gui_module, hal_module + + # Function to measure the frequency of iterations + def measure_frequency(self): + previous_time = datetime.now() + # An infinite loop + while True: + # Sleep for 2 seconds + time.sleep(2) + + # Measure the current time and subtract from the previous time to get real time interval + current_time = datetime.now() + dt = current_time - previous_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + previous_time = current_time + + # Get the time period + try: + # Division by zero + self.measured_cycle = ms / self.iteration_counter + except: + self.measured_cycle = 0 + + # Reset the counter + self.iteration_counter = 0 + + # Send to client + self.send_frequency_message() + + # Function to generate and send frequency messages + def send_frequency_message(self): + # This function generates and sends frequency measures of the brain and gui + brain_frequency = 0; gui_frequency = 0 + try: + brain_frequency = round(1000 / self.measured_cycle, 1) + except ZeroDivisionError: + brain_frequency = 0 + + try: + gui_frequency = round(1000 / self.thread_gui.measured_cycle, 1) + except ZeroDivisionError: + gui_frequency = 0 + + self.frequency_message["brain"] = brain_frequency + self.frequency_message["gui"] = gui_frequency + self.frequency_message["rtf"] = self.real_time_factor + + message = "#freq" + json.dumps(self.frequency_message) + self.server.send_message(self.client, message) + + def send_ping_message(self): + self.server.send_message(self.client, "#ping") + + # Function to notify the front end that the code was received and sent to execution + def send_code_message(self): + self.server.send_message(self.client, "#exec") + + # Function to track the real time factor from Gazebo statistics + # https://stackoverflow.com/a/17698359 + # (For reference, Python3 solution specified in the same answer) + def track_stats(self): + args = ["gz", "stats", "-p"] + # Prints gz statistics. "-p": Output comma-separated values containing- + # real-time factor (percent), simtime (sec), realtime (sec), paused (T or F) + stats_process = subprocess.Popen(args, stdout=subprocess.PIPE, bufsize=1, universal_newlines=True) + # bufsize=1 enables line-bufferred mode (the input buffer is flushed + # automatically on newlines if you would write to process.stdin ) + with stats_process.stdout: + for line in iter(stats_process.stdout.readline, ''): + stats_list = [x.strip() for x in line.split(',')] + self.real_time_factor = stats_list[0] + + # Function to maintain thread execution + def execute_thread(self, source_code): + # Keep checking until the thread is alive + # The thread will die when the coming iteration reads the flag + if self.thread is not None: + while self.thread.is_alive(): + time.sleep(0.2) + + # Turn the flag down, the iteration has successfully stopped! + self.reload = False + # New thread execution + self.thread = threading.Thread(target=self.process_code, args=[source_code]) + self.thread.start() + self.send_code_message() + print("New Thread Started!") + + # Function to read and set frequency from incoming message + def read_frequency_message(self, message): + frequency_message = json.loads(message) + + # Set brain frequency + frequency = float(frequency_message["brain"]) + self.ideal_cycle = 1000.0 / frequency + + # Set gui frequency + frequency = float(frequency_message["gui"]) + self.thread_gui.ideal_cycle = 1000.0 / frequency + + return + + # The websocket function + # Gets called when there is an incoming message from the client + def handle(self, client, server, message): + if message[:5] == "#freq": + frequency_message = message[5:] + self.read_frequency_message(frequency_message) + time.sleep(1) + return + + elif(message[:5] == "#ping"): + time.sleep(1) + self.send_ping_message() + return + + elif (message[:5] == "#code"): + try: + # Once received turn the reload flag up and send it to execute_thread function + self.user_code = message + # print(repr(code)) + self.reload = True + self.execute_thread(self.user_code) + except: + pass + + elif (message[:5] == "#rest"): + try: + self.reload = True + self.stop_brain = True + self.execute_thread(self.user_code) + except: + pass + + elif (message[:5] == "#stop"): + self.stop_brain = True + + elif (message[:5] == "#play"): + self.stop_brain = False + + # Function that gets called when the server is connected + def connected(self, client, server): + self.client = client + # Start the GUI update thread + self.thread_gui = ThreadGUI(self.gui) + self.thread_gui.start() + + # Start the real time factor tracker thread + self.stats_thread = threading.Thread(target=self.track_stats) + self.stats_thread.start() + + # Start measure frequency + self.measure_thread = threading.Thread(target=self.measure_frequency) + self.measure_thread.start() + + print(client, 'connected') + + # Function that gets called when the connected closes + def handle_close(self, client, server): + print(client, 'closed') + + def run_server(self): + self.server = WebsocketServer(port=1905, host=self.host) + self.server.set_fn_new_client(self.connected) + self.server.set_fn_client_left(self.handle_close) + self.server.set_fn_message_received(self.handle) + + logged = False + while not logged: + try: + f = open("/ws_code.log", "w") + f.write("websocket_code=ready") + f.close() + logged = True + except: + time.sleep(0.1) + + self.server.run_forever() + + +# Execute! +if __name__ == "__main__": + server = Template() + server.run_server() \ No newline at end of file diff --git a/exercises/static/exercises/power_tower_inspection/web-template/gui.py b/exercises/static/exercises/power_tower_inspection/web-template/gui.py new file mode 100755 index 000000000..f4fd34044 --- /dev/null +++ b/exercises/static/exercises/power_tower_inspection/web-template/gui.py @@ -0,0 +1,250 @@ +import json +import cv2 +import base64 +import threading +import time +from datetime import datetime +from websocket_server import WebsocketServer +import logging + + +# Graphical User Interface Class +class GUI: + # Initialization function + # The actual initialization + def __init__(self, host): + t = threading.Thread(target=self.run_server) + + self.payload = {'image': ''} + self.left_payload = {'image': ''} + self.server = None + self.client = None + + self.host = host + + # Image variables + self.image_to_be_shown = None + self.image_to_be_shown_updated = False + self.image_show_lock = threading.Lock() + + self.left_image_to_be_shown = None + self.left_image_to_be_shown_updated = False + self.left_image_show_lock = threading.Lock() + + self.acknowledge = False + self.acknowledge_lock = threading.Lock() + + # Take the console object to set the same websocket and client + t.start() + + # Explicit initialization function + # Class method, so user can call it without instantiation + @classmethod + def initGUI(cls, host): + # self.payload = {'image': '', 'shape': []} + new_instance = cls(host) + return new_instance + + # Function to prepare image payload + # Encodes the image as a JSON string and sends through the WS + def payloadImage(self): + self.image_show_lock.acquire() + image_to_be_shown_updated = self.image_to_be_shown_updated + image_to_be_shown = self.image_to_be_shown + self.image_show_lock.release() + + image = image_to_be_shown + payload = {'image': '', 'shape': ''} + + if not image_to_be_shown_updated: + return payload + + shape = image.shape + frame = cv2.imencode('.JPEG', image)[1] + encoded_image = base64.b64encode(frame) + + payload['image'] = encoded_image.decode('utf-8') + payload['shape'] = shape + + self.image_show_lock.acquire() + self.image_to_be_shown_updated = False + self.image_show_lock.release() + + return payload + + # Function to prepare image payload + # Encodes the image as a JSON string and sends through the WS + def payloadLeftImage(self): + self.left_image_show_lock.acquire() + left_image_to_be_shown_updated = self.left_image_to_be_shown_updated + left_image_to_be_shown = self.left_image_to_be_shown + self.left_image_show_lock.release() + + image = left_image_to_be_shown + payload = {'image': '', 'shape': ''} + + if not left_image_to_be_shown_updated: + return payload + + shape = image.shape + frame = cv2.imencode('.JPEG', image)[1] + encoded_image = base64.b64encode(frame) + + payload['image'] = encoded_image.decode('utf-8') + payload['shape'] = shape + + self.left_image_show_lock.acquire() + self.left_image_to_be_shown_updated = False + self.left_image_show_lock.release() + + return payload + + # Function for student to call + def showImage(self, image): + self.image_show_lock.acquire() + self.image_to_be_shown = image + self.image_to_be_shown_updated = True + self.image_show_lock.release() + + # Function for student to call + def showLeftImage(self, image): + self.left_image_show_lock.acquire() + self.left_image_to_be_shown = image + self.left_image_to_be_shown_updated = True + self.left_image_show_lock.release() + + # Function to get the client + # Called when a new client is received + def get_client(self, client, server): + self.client = client + + # Function to get value of Acknowledge + def get_acknowledge(self): + self.acknowledge_lock.acquire() + acknowledge = self.acknowledge + self.acknowledge_lock.release() + + return acknowledge + + # Function to get value of Acknowledge + def set_acknowledge(self, value): + self.acknowledge_lock.acquire() + self.acknowledge = value + self.acknowledge_lock.release() + + # Update the gui + def update_gui(self): + # Payload Image Message + payload = self.payloadImage() + self.payload["image"] = json.dumps(payload) + + message = "#gui" + json.dumps(self.payload) + self.server.send_message(self.client, message) + + # Payload Left Image Message + left_payload = self.payloadLeftImage() + self.left_payload["image"] = json.dumps(left_payload) + + message = "#gul" + json.dumps(self.left_payload) + self.server.send_message(self.client, message) + + # Function to read the message from websocket + # Gets called when there is an incoming message from the client + def get_message(self, client, server, message): + # Acknowledge Message for GUI Thread + if message[:4] == "#ack": + self.set_acknowledge(True) + + + # Activate the server + def run_server(self): + self.server = WebsocketServer(port=2303, host=self.host) + self.server.set_fn_new_client(self.get_client) + self.server.set_fn_message_received(self.get_message) + + logged = False + while not logged: + try: + f = open("/ws_gui.log", "w") + f.write("websocket_gui=ready") + f.close() + logged = True + except: + time.sleep(0.1) + + self.server.run_forever() + + # Function to reset + def reset_gui(self): + pass + + +# This class decouples the user thread +# and the GUI update thread +class ThreadGUI: + def __init__(self, gui): + self.gui = gui + + # Time variables + self.ideal_cycle = 80 + self.measured_cycle = 80 + self.iteration_counter = 0 + + # Function to start the execution of threads + def start(self): + self.measure_thread = threading.Thread(target=self.measure_thread) + self.thread = threading.Thread(target=self.run) + + self.measure_thread.start() + self.thread.start() + + print("GUI Thread Started!") + + # The measuring thread to measure frequency + def measure_thread(self): + while self.gui.client is None: + pass + + previous_time = datetime.now() + while True: + # Sleep for 2 seconds + time.sleep(2) + + # Measure the current time and subtract from previous time to get real time interval + current_time = datetime.now() + dt = current_time - previous_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + previous_time = current_time + + # Get the time period + try: + # Division by zero + self.measured_cycle = ms / self.iteration_counter + except: + self.measured_cycle = 0 + + # Reset the counter + self.iteration_counter = 0 + + # The main thread of execution + def run(self): + while self.gui.client is None: + pass + + while True: + start_time = datetime.now() + self.gui.update_gui() + acknowledge_message = self.gui.get_acknowledge() + + while not acknowledge_message: + acknowledge_message = self.gui.get_acknowledge() + + self.gui.set_acknowledge(False) + + finish_time = datetime.now() + self.iteration_counter = self.iteration_counter + 1 + + dt = finish_time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + if ms < self.ideal_cycle: + time.sleep((self.ideal_cycle-ms) / 1000.0) diff --git a/exercises/static/exercises/power_tower_inspection/web-template/hal.py b/exercises/static/exercises/power_tower_inspection/web-template/hal.py new file mode 100644 index 000000000..4e68e2c52 --- /dev/null +++ b/exercises/static/exercises/power_tower_inspection/web-template/hal.py @@ -0,0 +1,86 @@ +import rospy +import cv2 +import threading +import time +from datetime import datetime + +from drone_wrapper import DroneWrapper + +# Hardware Abstraction Layer +class HAL: + IMG_WIDTH = 320 + IMG_HEIGHT = 240 + + def __init__(self): + rospy.init_node("HAL") + + self.image = None + self.drone = DroneWrapper(name="rqt") + + + # Explicit initialization functions + # Class method, so user can call it without instantiation + @classmethod + def initRobot(cls): + new_instance = cls() + return new_instance + + # Get Image from ROS Driver Camera + def get_frontal_image(self): + image = self.drone.get_frontal_image() + image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) + return image_rgb + + def get_ventral_image(self): + image = self.drone.get_ventral_image() + image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) + return image_rgb + + def get_position(self): + pos = self.drone.get_position() + return pos + + def get_velocity(self): + vel = self.drone.get_velocity() + return vel + + def get_yaw_rate(self): + yaw_rate = self.drone.get_yaw_rate() + return yaw_rate + + def get_orientation(self): + orientation = self.drone.get_orientation() + return orientation + + def get_roll(self): + roll = self.drone.get_roll() + return roll + + def get_pitch(self): + pitch = self.drone.get_pitch() + return pitch + + def get_yaw(self): + yaw = self.drone.get_yaw() + return yaw + + def get_landed_state(self): + state = self.drone.get_landed_state() + return state + + def set_cmd_pos(self, x, y, z, az): + self.drone.set_cmd_pos(x, y, z, az) + + def set_cmd_vel(self, vx, vy, vz, az): + self.drone.set_cmd_vel(vx, vy, vz, az) + + def set_cmd_mix(self, vx, vy, z, az): + self.drone.set_cmd_mix(vx, vy, z, az) + + def takeoff(self, h=5): + self.drone.takeoff(h) + + def land(self): + self.drone.land() + + diff --git a/exercises/static/exercises/power_tower_inspection/web-template/interfaces/__init__.py b/exercises/static/exercises/power_tower_inspection/web-template/interfaces/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/exercises/static/exercises/power_tower_inspection/web-template/interfaces/camera.py b/exercises/static/exercises/power_tower_inspection/web-template/interfaces/camera.py new file mode 100644 index 000000000..5a021a13e --- /dev/null +++ b/exercises/static/exercises/power_tower_inspection/web-template/interfaces/camera.py @@ -0,0 +1,89 @@ +import rospy +from sensor_msgs.msg import Image as ImageROS +import threading +from math import pi as PI +import cv2 +from cv_bridge import CvBridge, CvBridgeError + + +MAXRANGE = 8 # max length received from imageD +MINRANGE = 0 + + +def imageMsg2Image(img, bridge): + + image = Image() + + image.width = img.width + image.height = img.height + image.format = "BGR8" + image.timeStamp = img.header.stamp.secs + (img.header.stamp.nsecs * 1e-9) + cv_image = 0 + if img.encoding[-2:] == "C1": + gray_img_buff = bridge.imgmsg_to_cv2(img, img.encoding) + cv_image = depthToRGB8(gray_img_buff, img.encoding) + else: + cv_image = bridge.imgmsg_to_cv2(img, "bgr8") + image.data = cv_image + return image + + +import numpy as np + + +class Image: + + def __init__(self): + + self.height = 3 # Image height [pixels] + self.width = 3 # Image width [pixels] + self.timeStamp = 0 # Time stamp [s] */ + self.format = "" # Image format string (RGB8, BGR,...) + self.data = np.zeros((self.height, self.width, 3), np.uint8) # The image data itself + self.data.shape = self.height, self.width, 3 + + def __str__(self): + s = "Image: {\n height: " + str(self.height) + "\n width: " + str(self.width) + s = s + "\n format: " + self.format + "\n timeStamp: " + str(self.timeStamp) + s = s + "\n data: " + str(self.data) + "\n}" + return s + + +class ListenerCamera: + + def __init__(self, topic): + + self.topic = topic + self.data = Image() + self.sub = None + self.lock = threading.Lock() + + self.bridge = CvBridge() + self.start() + + def __callback(self, img): + + image = imageMsg2Image(img, self.bridge) + + self.lock.acquire() + self.data = image + self.lock.release() + + def stop(self): + + self.sub.unregister() + + def start(self): + self.sub = rospy.Subscriber(self.topic, ImageROS, self.__callback) + + def getImage(self): + + self.lock.acquire() + image = self.data + self.lock.release() + + return image + + def hasproxy(self): + + return hasattr(self, "sub") and self.sub diff --git a/exercises/static/exercises/power_tower_inspection/web-template/interfaces/motors.py b/exercises/static/exercises/power_tower_inspection/web-template/interfaces/motors.py new file mode 100644 index 000000000..70dca8a46 --- /dev/null +++ b/exercises/static/exercises/power_tower_inspection/web-template/interfaces/motors.py @@ -0,0 +1,123 @@ +import rospy +from geometry_msgs.msg import Twist +import threading +from math import pi as PI +from .threadPublisher import ThreadPublisher + + + +def cmdvel2Twist(vel): + + tw = Twist() + tw.linear.x = vel.vx + tw.linear.y = vel.vy + tw.linear.z = vel.vz + tw.angular.x = vel.ax + tw.angular.y = vel.ay + tw.angular.z = vel.az + + return tw + + +class CMDVel (): + + def __init__(self): + + self.vx = 0 # vel in x[m/s] (use this for V in wheeled robots) + self.vy = 0 # vel in y[m/s] + self.vz = 0 # vel in z[m/s] + self.ax = 0 # angular vel in X axis [rad/s] + self.ay = 0 # angular vel in X axis [rad/s] + self.az = 0 # angular vel in Z axis [rad/s] (use this for W in wheeled robots) + self.timeStamp = 0 # Time stamp [s] + + + def __str__(self): + s = "CMDVel: {\n vx: " + str(self.vx) + "\n vy: " + str(self.vy) + s = s + "\n vz: " + str(self.vz) + "\n ax: " + str(self.ax) + s = s + "\n ay: " + str(self.ay) + "\n az: " + str(self.az) + s = s + "\n timeStamp: " + str(self.timeStamp) + "\n}" + return s + +class PublisherMotors: + + def __init__(self, topic, maxV, maxW): + + self.maxW = maxW + self.maxV = maxV + + self.topic = topic + self.data = CMDVel() + self.pub = rospy.Publisher(self.topic, Twist, queue_size=1) + + self.lock = threading.Lock() + + self.kill_event = threading.Event() + self.thread = ThreadPublisher(self, self.kill_event) + + self.thread.daemon = True + self.start() + + def publish (self): + + self.lock.acquire() + tw = cmdvel2Twist(self.data) + self.lock.release() + self.pub.publish(tw) + + def stop(self): + + self.kill_event.set() + self.pub.unregister() + + def start (self): + + self.kill_event.clear() + self.thread.start() + + + + def getMaxW(self): + return self.maxW + + def getMaxV(self): + return self.maxV + + + def sendVelocities(self, vel): + + self.lock.acquire() + self.data = vel + self.lock.release() + + def sendV(self, v): + + self.sendVX(v) + + def sendL(self, l): + + self.sendVY(l) + + def sendW(self, w): + + self.sendAZ(w) + + def sendVX(self, vx): + + self.lock.acquire() + self.data.vx = vx + self.lock.release() + + def sendVY(self, vy): + + self.lock.acquire() + self.data.vy = vy + self.lock.release() + + def sendAZ(self, az): + + self.lock.acquire() + self.data.az = az + self.lock.release() + + diff --git a/exercises/static/exercises/power_tower_inspection/web-template/interfaces/pose3d.py b/exercises/static/exercises/power_tower_inspection/web-template/interfaces/pose3d.py new file mode 100644 index 000000000..fd0bfc37a --- /dev/null +++ b/exercises/static/exercises/power_tower_inspection/web-template/interfaces/pose3d.py @@ -0,0 +1,176 @@ +import rospy +import threading +from math import asin, atan2, pi +from nav_msgs.msg import Odometry + +def quat2Yaw(qw, qx, qy, qz): + ''' + Translates from Quaternion to Yaw. + + @param qw,qx,qy,qz: Quaternion values + + @type qw,qx,qy,qz: float + + @return Yaw value translated from Quaternion + + ''' + rotateZa0=2.0*(qx*qy + qw*qz) + rotateZa1=qw*qw + qx*qx - qy*qy - qz*qz + rotateZ=0.0 + if(rotateZa0 != 0.0 and rotateZa1 != 0.0): + rotateZ=atan2(rotateZa0,rotateZa1) + return rotateZ + +def quat2Pitch(qw, qx, qy, qz): + ''' + Translates from Quaternion to Pitch. + + @param qw,qx,qy,qz: Quaternion values + + @type qw,qx,qy,qz: float + + @return Pitch value translated from Quaternion + + ''' + + rotateYa0=-2.0*(qx*qz - qw*qy) + rotateY=0.0 + if(rotateYa0 >= 1.0): + rotateY = pi/2.0 + elif(rotateYa0 <= -1.0): + rotateY = -pi/2.0 + else: + rotateY = asin(rotateYa0) + + return rotateY + +def quat2Roll (qw, qx, qy, qz): + ''' + Translates from Quaternion to Roll. + + @param qw,qx,qy,qz: Quaternion values + + @type qw,qx,qy,qz: float + + @return Roll value translated from Quaternion + + ''' + rotateXa0=2.0*(qy*qz + qw*qx) + rotateXa1=qw*qw - qx*qx - qy*qy + qz*qz + rotateX=0.0 + + if(rotateXa0 != 0.0 and rotateXa1 != 0.0): + rotateX=atan2(rotateXa0, rotateXa1) + return rotateX + + +def odometry2Pose3D(odom): + ''' + Translates from ROS Odometry to JderobotTypes Pose3d. + + @param odom: ROS Odometry to translate + + @type odom: Odometry + + @return a Pose3d translated from odom + + ''' + pose = Pose3d() + ori = odom.pose.pose.orientation + + pose.x = odom.pose.pose.position.x + pose.y = odom.pose.pose.position.y + pose.z = odom.pose.pose.position.z + #pose.h = odom.pose.pose.position.h + pose.yaw = quat2Yaw(ori.w, ori.x, ori.y, ori.z) + pose.pitch = quat2Pitch(ori.w, ori.x, ori.y, ori.z) + pose.roll = quat2Roll(ori.w, ori.x, ori.y, ori.z) + pose.q = [ori.w, ori.x, ori.y, ori.z] + pose.timeStamp = odom.header.stamp.secs + (odom.header.stamp.nsecs *1e-9) + + return pose + +class Pose3d (): + + def __init__(self): + + self.x = 0 # X coord [meters] + self.y = 0 # Y coord [meters] + self.z = 0 # Z coord [meters] + self.h = 1 # H param + self.yaw = 0 #Yaw angle[rads] + self.pitch = 0 # Pitch angle[rads] + self.roll = 0 # Roll angle[rads] + self.q = [0,0,0,0] # Quaternion + self.timeStamp = 0 # Time stamp [s] + + + def __str__(self): + s = "Pose3D: {\n x: " + str(self.x) + "\n Y: " + str(self.y) + s = s + "\n Z: " + str(self.z) + "\n H: " + str(self.h) + s = s + "\n Yaw: " + str(self.yaw) + "\n Pitch: " + str(self.pitch) + "\n Roll: " + str(self.roll) + s = s + "\n quaternion: " + str(self.q) + "\n timeStamp: " + str(self.timeStamp) + "\n}" + return s + + +class ListenerPose3d: + ''' + ROS Pose3D Subscriber. Pose3D Client to Receive pose3d from ROS nodes. + ''' + def __init__(self, topic): + ''' + ListenerPose3d Constructor. + + @param topic: ROS topic to subscribe + + @type topic: String + + ''' + self.topic = topic + self.data = Pose3d() + self.sub = None + self.lock = threading.Lock() + self.start() + + def __callback (self, odom): + ''' + Callback function to receive and save Pose3d. + + @param odom: ROS Odometry received + + @type odom: Odometry + + ''' + pose = odometry2Pose3D(odom) + + self.lock.acquire() + self.data = pose + self.lock.release() + + def stop(self): + ''' + Stops (Unregisters) the client. + + ''' + self.sub.unregister() + + def start (self): + ''' + Starts (Subscribes) the client. + + ''' + self.sub = rospy.Subscriber(self.topic, Odometry, self.__callback) + + def getPose3d(self): + ''' + Returns last Pose3d. + + @return last JdeRobotTypes Pose3d saved + + ''' + self.lock.acquire() + pose = self.data + self.lock.release() + + return pose + diff --git a/exercises/static/exercises/power_tower_inspection/web-template/interfaces/threadPublisher.py b/exercises/static/exercises/power_tower_inspection/web-template/interfaces/threadPublisher.py new file mode 100644 index 000000000..69aa0ad48 --- /dev/null +++ b/exercises/static/exercises/power_tower_inspection/web-template/interfaces/threadPublisher.py @@ -0,0 +1,46 @@ +# +# Copyright (C) 1997-2016 JDE Developers Team +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. +# Authors : +# Alberto Martin Florido +# Aitor Martinez Fernandez +# +import threading +import time +from datetime import datetime + +time_cycle = 80 + + +class ThreadPublisher(threading.Thread): + + def __init__(self, pub, kill_event): + self.pub = pub + self.kill_event = kill_event + threading.Thread.__init__(self, args=kill_event) + + def run(self): + while (not self.kill_event.is_set()): + start_time = datetime.now() + + self.pub.publish() + + finish_Time = datetime.now() + + dt = finish_Time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + #print (ms) + if (ms < time_cycle): + time.sleep((time_cycle - ms) / 1000.0) \ No newline at end of file diff --git a/exercises/static/exercises/power_tower_inspection/web-template/launch/gazebo.launch b/exercises/static/exercises/power_tower_inspection/web-template/launch/gazebo.launch new file mode 100644 index 000000000..f8164e32f --- /dev/null +++ b/exercises/static/exercises/power_tower_inspection/web-template/launch/gazebo.launch @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/exercises/static/exercises/power_tower_inspection/web-template/launch/launch.py b/exercises/static/exercises/power_tower_inspection/web-template/launch/launch.py new file mode 100644 index 000000000..e37e3f76f --- /dev/null +++ b/exercises/static/exercises/power_tower_inspection/web-template/launch/launch.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python3 + +import stat +import rospy +from os import lstat +from subprocess import Popen, PIPE + + +DRI_PATH = "/dev/dri/card0" +EXERCISE = "power_tower_inspection" +TIMEOUT = 30 +MAX_ATTEMPT = 2 + + +# Function to check if a device exists +def check_device(device_path): + try: + return stat.S_ISCHR(lstat(device_path)[stat.ST_MODE]) + except: + return False + + +# Spawn new process +def spawn_process(args, insert_vglrun=False): + if insert_vglrun: + args.insert(0, "vglrun") + process = Popen(args, stdout=PIPE, bufsize=1, universal_newlines=True) + return process + + +class Test(): + def gazebo(self): + rospy.logwarn("[GAZEBO] Launching") + try: + rospy.wait_for_service("/gazebo/get_model_properties", TIMEOUT) + return True + except rospy.ROSException: + return False + + def px4(self): + rospy.logwarn("[PX4-SITL] Launching") + start_time = rospy.get_time() + args = ["./PX4-Autopilot/build/px4_sitl_default/bin/px4-commander","--instance", "0", "check"] + while rospy.get_time() - start_time < TIMEOUT: + process = spawn_process(args, insert_vglrun=False) + with process.stdout: + for line in iter(process.stdout.readline, ''): + if ("Prearm check: OK" in line): + return True + rospy.sleep(2) + return False + + def mavros(self, ns=""): + rospy.logwarn("[MAVROS] Launching") + try: + rospy.wait_for_service(ns + "/mavros/cmd/arming", TIMEOUT) + return True + except rospy.ROSException: + return False + + +class Launch(): + def __init__(self): + self.test = Test() + self.acceleration_enabled = check_device(DRI_PATH) + + # Start roscore + args = ["/opt/ros/noetic/bin/roscore"] + spawn_process(args, insert_vglrun=False) + + rospy.init_node("launch", anonymous=True) + + def start(self): + ######## LAUNCH GAZEBO ######## + args = ["/opt/ros/noetic/bin/roslaunch", + "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/gazebo.launch", + "--wait", + "--log" + ] + + attempt = 1 + while True: + spawn_process(args, insert_vglrun=self.acceleration_enabled) + if self.test.gazebo() == True: + break + if attempt == MAX_ATTEMPT: + rospy.logerr("[GAZEBO] Launch Failed") + return + attempt = attempt + 1 + + + ######## LAUNCH PX4 ######## + args = ["/opt/ros/noetic/bin/roslaunch", + "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/px4.launch", + "--log" + ] + + attempt = 1 + while True: + spawn_process(args, insert_vglrun=self.acceleration_enabled) + if self.test.px4() == True: + break + if attempt == MAX_ATTEMPT: + rospy.logerr("[PX4] Launch Failed") + return + attempt = attempt + 1 + + + ######## LAUNCH MAVROS ######## + args = ["/opt/ros/noetic/bin/roslaunch", + "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/mavros.launch", + "--log" + ] + + attempt = 1 + while True: + spawn_process(args, insert_vglrun=self.acceleration_enabled) + if self.test.mavros() == True: + break + if attempt == MAX_ATTEMPT: + rospy.logerr("[MAVROS] Launch Failed") + return + attempt = attempt + 1 + + +if __name__ == "__main__": + launch = Launch() + launch.start() + + with open("/drones_launch.log", "w") as f: + f.write("success") diff --git a/exercises/static/exercises/power_tower_inspection/web-template/launch/mavros.launch b/exercises/static/exercises/power_tower_inspection/web-template/launch/mavros.launch new file mode 100644 index 000000000..b899c0ec1 --- /dev/null +++ b/exercises/static/exercises/power_tower_inspection/web-template/launch/mavros.launch @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/exercises/static/exercises/power_tower_inspection/web-template/launch/px4.launch b/exercises/static/exercises/power_tower_inspection/web-template/launch/px4.launch new file mode 100644 index 000000000..7aebd7951 --- /dev/null +++ b/exercises/static/exercises/power_tower_inspection/web-template/launch/px4.launch @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/exercises/static/exercises/power_tower_inspection/web-template/power_tower_inspection.world b/exercises/static/exercises/power_tower_inspection/web-template/power_tower_inspection.world new file mode 100644 index 000000000..5697153c1 --- /dev/null +++ b/exercises/static/exercises/power_tower_inspection/web-template/power_tower_inspection.world @@ -0,0 +1,352 @@ + + + + + + + -4.70385 10.895 16.2659 -0 0.921795 -1.12701 + orbit + perspective + + + + + + model://sun + + + + + model://grass_plane + 0 0 -0.045 0 0 + + + + + model://logoJdeRobot + 20 -5 0 0 0 0 + 0.25 0.25 0.25 + + + + + model://logoPadGreen + -21.0 -4.0 0 0 0 + launching_pad + + + + + model://power_tower_danube_wires + -41 -4 0.05 0 0 + power_tower_wires0 + + + + + model://power_tower_danube + -1 -4 0.05 0 0 + power_tower1 + + + + + model://power_tower_danube_wires + -1 -4 0.05 0 0 + power_tower_wires1 + + + + + + model://power_tower_danube + 39 -4 0.05 0 0 + power_tower2 + + + + + model://power_tower_danube_wires + 39 -4 0.05 0 0 + power_tower_wires2 + + + + + model://power_tower_danube + 79 -4 0.05 0 0 + power_tower3 + + + + + model://power_tower_danube_wires + 79 -4 0.05 0 0 + power_tower_wires3 + + + + + + model://rust_defect_01 + + -2.31 -4 3.79 0 0.17 + rust_def1_tower1 + + + + + model://rust_defect_01 + + 0.232054 -4.00614 3.96187 0 -0.15255 0 + rust_def1_tower1_opp + + + + + model://rust_defect_02 + + -1.80605 -4.40198 14.7671 0 0.0 + rust_def2_tower1 + + + + + model://rust_defect_02 + + -0.438877 -4.04055 14.7724 0 0.0 0 + rust_def2_tower1_opp + + + + + model://rust_defect_02 + + -1.81251 -3.67691 14.7727 0 0.0 + rust_def3_tower1 + + + + + model://rust_defect_02 + + -0.435477 -3.59453 14.7738 0 0.0 0 + rust_def3_tower1_opp + + + + + model://rust_defect_03 + + -1.59 -4.33479 20.438 0 0.17 + rust_def4_tower1 + + + + + model://rust_defect_03 + + -0.577392 -3.56405 19.08 0 -0.028 0 + rust_def4_tower1_opp + + + + + model://rust_defect_03 + + -1.73 -3.51 19.1255 0 0.17 + rust_def5_tower1 + + + + + + model://rust_defect_01 + + 37.69 -4 3.79 0 0.17 + rust_def1_tower2 + + + + + model://rust_defect_01 + + 40.232054 -4.00614 3.96187 0 -0.15255 0 + rust_def1_tower2_opp + + + + + model://rust_defect_03 + + 38.19395 -4.40198 14.7671 0 0.0 + rust_def2_tower2 + + + + + model://rust_defect_02 + + 39.561123 -4.04055 14.7724 0 0.0 0 + rust_def2_tower2_opp + + + + + model://rust_defect_03 + + 38.18749 -3.67691 14.7727 0 0.0 + rust_def3_tower2 + + + + + model://rust_defect_02 + + 39.564523 -3.59453 14.7738 0 0.0 0 + rust_def3_tower2_opp + + + + + model://rust_defect_02 + + 38.41 -4.33479 20.438 0 0.17 + rust_def4_tower2 + + + + + model://rust_defect_03 + + 39.422608 -3.56405 19.08 0 -0.028 0 + rust_def4_tower2_opp + + + + + model://rust_defect_wire + -7.72635 -0.241255 12.3353 -0.004615 -0.109891 -0.001474 + rust_def_wire1 + + + + + model://rust_defect_wire + -11.058 1.31607 16.5176 -0.004602 -0.082412 -0.001602 + rust_def_wire2 + + + + + model://rust_defect_wire + 19.8548 2.72211 11.5241 -0.004587 -0.002014 -0.001971 + rust_def_wire3 + + + + + model://rust_defect_wire + 30.6961 -7.7434 12.1612 -0.004616 -0.113092 -0.001459 + rust_def_wire4 + + + + + model://rust_defect_wire + -2.37899 -0.352206 13.0936 -0.004633 -0.140673 -0.001331 + rust_def_spring_tower1 + + + + + model://rust_defect_wire + 37.8828 2.61147 13.101 -0.004636 -0.145252 -0.001309 + rust_def_spring_tower2 + + + + + model://nest1 + -1.3 1.56 12.55 0 0 0 + nest1 + + + + + model://egg + -1.3 1.5 13.3197 0 -0 1.57 + egg1 + + + + + model://bird + -1.06401 1.28978 13.4012 0 -0 -2.07145 + bird + + + + + model://nest1 + 14.86 5.48 2.51339 0 0 0 + nest2 + + + + + model://egg + 14.86 5.42 3.25989 0 0 0 + egg2 + + + + + model://electrical_box + 15 5 0 0 0 1.57 + electrical_box1 + + + + + model://electrical_box + 55 5 0 0 0 1.57 + electrical_box2 + + + + + + 0 + 0.4 0.4 0.4 1.0 + 0.7 0.7 0.7 1 + + + 12 + + + + + + 0 0 -9.8066 + + + quick + 10 + 1.3 + 0 + + + 0 + 0.2 + 100 + 0.001 + + + 0.004 + 1 + 250 + 6.0e-6 2.3e-5 -4.2e-5 + + + \ No newline at end of file diff --git a/exercises/static/exercises/rescue_people/README.md b/exercises/static/exercises/rescue_people/README.md new file mode 100644 index 000000000..739914387 --- /dev/null +++ b/exercises/static/exercises/rescue_people/README.md @@ -0,0 +1 @@ +[Exercise Documentation Website](https://jderobot.github.io/RoboticsAcademy/exercises/Drones/rescue_people) diff --git a/exercises/static/exercises/rescue_people/haarcascade_frontalface_default.xml b/exercises/static/exercises/rescue_people/haarcascade_frontalface_default.xml new file mode 100644 index 000000000..cbd1aa89e --- /dev/null +++ b/exercises/static/exercises/rescue_people/haarcascade_frontalface_default.xml @@ -0,0 +1,33314 @@ + + + +BOOST + HAAR + 24 + 24 + + 211 + + 0 + 25 + + <_> + 9 + -5.0425500869750977e+00 + + <_> + + 0 -1 0 -3.1511999666690826e-02 + + 2.0875380039215088e+00 -2.2172100543975830e+00 + <_> + + 0 -1 1 1.2396000325679779e-02 + + -1.8633940219879150e+00 1.3272049427032471e+00 + <_> + + 0 -1 2 2.1927999332547188e-02 + + -1.5105249881744385e+00 1.0625729560852051e+00 + <_> + + 0 -1 3 5.7529998011887074e-03 + + -8.7463897466659546e-01 1.1760339736938477e+00 + <_> + + 0 -1 4 1.5014000236988068e-02 + + -7.7945697307586670e-01 1.2608419656753540e+00 + <_> + + 0 -1 5 9.9371001124382019e-02 + + 5.5751299858093262e-01 -1.8743000030517578e+00 + <_> + + 0 -1 6 2.7340000960975885e-03 + + -1.6911929845809937e+00 4.4009700417518616e-01 + <_> + + 0 -1 7 -1.8859000876545906e-02 + + -1.4769539833068848e+00 4.4350099563598633e-01 + <_> + + 0 -1 8 5.9739998541772366e-03 + + -8.5909199714660645e-01 8.5255599021911621e-01 + <_> + 16 + -4.9842400550842285e+00 + + <_> + + 0 -1 9 -2.1110000088810921e-02 + + 1.2435649633407593e+00 -1.5713009834289551e+00 + <_> + + 0 -1 10 2.0355999469757080e-02 + + -1.6204780340194702e+00 1.1817760467529297e+00 + <_> + + 0 -1 11 2.1308999508619308e-02 + + -1.9415930509567261e+00 7.0069098472595215e-01 + <_> + + 0 -1 12 9.1660000383853912e-02 + + -5.5670100450515747e-01 1.7284419536590576e+00 + <_> + + 0 -1 13 3.6288000643253326e-02 + + 2.6763799786567688e-01 -2.1831810474395752e+00 + <_> + + 0 -1 14 -1.9109999760985374e-02 + + -2.6730210781097412e+00 4.5670801401138306e-01 + <_> + + 0 -1 15 8.2539999857544899e-03 + + -1.0852910280227661e+00 5.3564202785491943e-01 + <_> + + 0 -1 16 1.8355000764131546e-02 + + -3.5200199484825134e-01 9.3339198827743530e-01 + <_> + + 0 -1 17 -7.0569999516010284e-03 + + 9.2782098054885864e-01 -6.6349899768829346e-01 + <_> + + 0 -1 18 -9.8770000040531158e-03 + + 1.1577470302581787e+00 -2.9774799942970276e-01 + <_> + + 0 -1 19 1.5814000740647316e-02 + + -4.1960600018501282e-01 1.3576040267944336e+00 + <_> + + 0 -1 20 -2.0700000226497650e-02 + + 1.4590020179748535e+00 -1.9739399850368500e-01 + <_> + + 0 -1 21 -1.3760800659656525e-01 + + 1.1186759471893311e+00 -5.2915501594543457e-01 + <_> + + 0 -1 22 1.4318999834358692e-02 + + -3.5127198696136475e-01 1.1440860033035278e+00 + <_> + + 0 -1 23 1.0253000073134899e-02 + + -6.0850602388381958e-01 7.7098500728607178e-01 + <_> + + 0 -1 24 9.1508001089096069e-02 + + 3.8817799091339111e-01 -1.5122940540313721e+00 + <_> + 27 + -4.6551899909973145e+00 + + <_> + + 0 -1 25 6.9747000932693481e-02 + + -1.0130879878997803e+00 1.4687349796295166e+00 + <_> + + 0 -1 26 3.1502999365329742e-02 + + -1.6463639736175537e+00 1.0000629425048828e+00 + <_> + + 0 -1 27 1.4260999858379364e-02 + + 4.6480301022529602e-01 -1.5959889888763428e+00 + <_> + + 0 -1 28 1.4453000389039516e-02 + + -6.5511900186538696e-01 8.3021801710128784e-01 + <_> + + 0 -1 29 -3.0509999487549067e-03 + + -1.3982310295104980e+00 4.2550599575042725e-01 + <_> + + 0 -1 30 3.2722998410463333e-02 + + -5.0702601671218872e-01 1.0526109933853149e+00 + <_> + + 0 -1 31 -7.2960001416504383e-03 + + 3.6356899142265320e-01 -1.3464889526367188e+00 + <_> + + 0 -1 32 5.0425000488758087e-02 + + -3.0461400747299194e-01 1.4504129886627197e+00 + <_> + + 0 -1 33 4.6879000961780548e-02 + + -4.0286201238632202e-01 1.2145609855651855e+00 + <_> + + 0 -1 34 -6.9358997046947479e-02 + + 1.0539360046386719e+00 -4.5719701051712036e-01 + <_> + + 0 -1 35 -4.9033999443054199e-02 + + -1.6253089904785156e+00 1.5378999710083008e-01 + <_> + + 0 -1 36 8.4827996790409088e-02 + + 2.8402999043464661e-01 -1.5662059783935547e+00 + <_> + + 0 -1 37 -1.7229999648407102e-03 + + -1.0147459506988525e+00 2.3294800519943237e-01 + <_> + + 0 -1 38 1.1562199890613556e-01 + + -1.6732899844646454e-01 1.2804069519042969e+00 + <_> + + 0 -1 39 -5.1279999315738678e-02 + + 1.5162390470504761e+00 -3.0271100997924805e-01 + <_> + + 0 -1 40 -4.2706999927759171e-02 + + 1.7631920576095581e+00 -5.1832001656293869e-02 + <_> + + 0 -1 41 3.7178099155426025e-01 + + -3.1389200687408447e-01 1.5357979536056519e+00 + <_> + + 0 -1 42 1.9412999972701073e-02 + + -1.0017599910497665e-01 9.3655401468276978e-01 + <_> + + 0 -1 43 1.7439000308513641e-02 + + -4.0379899740219116e-01 9.6293002367019653e-01 + <_> + + 0 -1 44 3.9638999849557877e-02 + + 1.7039099335670471e-01 -2.9602990150451660e+00 + <_> + + 0 -1 45 -9.1469995677471161e-03 + + 8.8786798715591431e-01 -4.3818700313568115e-01 + <_> + + 0 -1 46 1.7219999572262168e-03 + + -3.7218600511550903e-01 4.0018901228904724e-01 + <_> + + 0 -1 47 3.0231000855565071e-02 + + 6.5924003720283508e-02 -2.6469180583953857e+00 + <_> + + 0 -1 48 -7.8795999288558960e-02 + + -1.7491459846496582e+00 2.8475299477577209e-01 + <_> + + 0 -1 49 2.1110000088810921e-03 + + -9.3908101320266724e-01 2.3205199837684631e-01 + <_> + + 0 -1 50 2.7091000229120255e-02 + + -5.2664000540971756e-02 1.0756820440292358e+00 + <_> + + 0 -1 51 -4.4964998960494995e-02 + + -1.8294479846954346e+00 9.9561996757984161e-02 + <_> + 32 + -4.4531588554382324e+00 + + <_> + + 0 -1 52 -6.5701000392436981e-02 + + 1.1558510065078735e+00 -1.0716359615325928e+00 + <_> + + 0 -1 53 1.5839999541640282e-02 + + -1.5634720325469971e+00 7.6877099275588989e-01 + <_> + + 0 -1 54 1.4570899307727814e-01 + + -5.7450097799301147e-01 1.3808720111846924e+00 + <_> + + 0 -1 55 6.1389999464154243e-03 + + -1.4570560455322266e+00 5.1610302925109863e-01 + <_> + + 0 -1 56 6.7179999314248562e-03 + + -8.3533602952957153e-01 5.8522200584411621e-01 + <_> + + 0 -1 57 1.8518000841140747e-02 + + -3.1312099099159241e-01 1.1696679592132568e+00 + <_> + + 0 -1 58 1.9958000630140305e-02 + + -4.3442600965499878e-01 9.5446902513504028e-01 + <_> + + 0 -1 59 -2.7755001187324524e-01 + + 1.4906179904937744e+00 -1.3815900683403015e-01 + <_> + + 0 -1 60 9.1859996318817139e-03 + + -9.6361500024795532e-01 2.7665498852729797e-01 + <_> + + 0 -1 61 -3.7737999111413956e-02 + + -2.4464108943939209e+00 2.3619599640369415e-01 + <_> + + 0 -1 62 1.8463000655174255e-02 + + 1.7539200186729431e-01 -1.3423130512237549e+00 + <_> + + 0 -1 63 -1.1114999651908875e-02 + + 4.8710799217224121e-01 -8.9851897954940796e-01 + <_> + + 0 -1 64 3.3927999436855316e-02 + + 1.7874200642108917e-01 -1.6342279911041260e+00 + <_> + + 0 -1 65 -3.5649001598358154e-02 + + -1.9607399702072144e+00 1.8102499842643738e-01 + <_> + + 0 -1 66 -1.1438000015914440e-02 + + 9.9010699987411499e-01 -3.8103199005126953e-01 + <_> + + 0 -1 67 -6.5236002206802368e-02 + + -2.5794160366058350e+00 2.4753600358963013e-01 + <_> + + 0 -1 68 -4.2272001504898071e-02 + + 1.4411840438842773e+00 -2.9508298635482788e-01 + <_> + + 0 -1 69 1.9219999667257071e-03 + + -4.9608600139617920e-01 6.3173598051071167e-01 + <_> + + 0 -1 70 -1.2921799719333649e-01 + + -2.3314270973205566e+00 5.4496999830007553e-02 + <_> + + 0 -1 71 2.2931000217795372e-02 + + -8.4447097778320312e-01 3.8738098740577698e-01 + <_> + + 0 -1 72 -3.4120000898838043e-02 + + -1.4431500434875488e+00 9.8422996699810028e-02 + <_> + + 0 -1 73 2.6223000138998032e-02 + + 1.8223099410533905e-01 -1.2586519718170166e+00 + <_> + + 0 -1 74 2.2236999124288559e-02 + + 6.9807998836040497e-02 -2.3820950984954834e+00 + <_> + + 0 -1 75 -5.8240001089870930e-03 + + 3.9332500100135803e-01 -2.7542799711227417e-01 + <_> + + 0 -1 76 4.3653000146150589e-02 + + 1.4832699298858643e-01 -1.1368780136108398e+00 + <_> + + 0 -1 77 5.7266999036073685e-02 + + 2.4628099799156189e-01 -1.2687400579452515e+00 + <_> + + 0 -1 78 2.3409998975694180e-03 + + -7.5448900461196899e-01 2.7163800597190857e-01 + <_> + + 0 -1 79 1.2996000237762928e-02 + + -3.6394900083541870e-01 7.0959198474884033e-01 + <_> + + 0 -1 80 -2.6517000049352646e-02 + + -2.3221859931945801e+00 3.5744000226259232e-02 + <_> + + 0 -1 81 -5.8400002308189869e-03 + + 4.2194300889968872e-01 -4.8184998333454132e-02 + <_> + + 0 -1 82 -1.6568999737501144e-02 + + 1.1099940538406372e+00 -3.4849700331687927e-01 + <_> + + 0 -1 83 -6.8157002329826355e-02 + + -3.3269989490509033e+00 2.1299000084400177e-01 + <_> + 52 + -4.3864588737487793e+00 + + <_> + + 0 -1 84 3.9974000304937363e-02 + + -1.2173449993133545e+00 1.0826710462570190e+00 + <_> + + 0 -1 85 1.8819500505924225e-01 + + -4.8289400339126587e-01 1.4045250415802002e+00 + <_> + + 0 -1 86 7.8027002513408661e-02 + + -1.0782150030136108e+00 7.4040299654006958e-01 + <_> + + 0 -1 87 1.1899999663000926e-04 + + -1.2019979953765869e+00 3.7749201059341431e-01 + <_> + + 0 -1 88 8.5056997835636139e-02 + + -4.3939098715782166e-01 1.2647340297698975e+00 + <_> + + 0 -1 89 8.9720003306865692e-03 + + -1.8440499901771545e-01 4.5726400613784790e-01 + <_> + + 0 -1 90 8.8120000436902046e-03 + + 3.0396699905395508e-01 -9.5991098880767822e-01 + <_> + + 0 -1 91 -2.3507999256253242e-02 + + 1.2487529516220093e+00 4.6227999031543732e-02 + <_> + + 0 -1 92 7.0039997808635235e-03 + + -5.9442102909088135e-01 5.3963297605514526e-01 + <_> + + 0 -1 93 3.3851999789476395e-02 + + 2.8496098518371582e-01 -1.4895249605178833e+00 + <_> + + 0 -1 94 -3.2530000898987055e-03 + + 4.8120799660682678e-01 -5.2712398767471313e-01 + <_> + + 0 -1 95 2.9097000136971474e-02 + + 2.6743900775909424e-01 -1.6007850170135498e+00 + <_> + + 0 -1 96 -8.4790000692009926e-03 + + -1.3107639551162720e+00 1.5243099629878998e-01 + <_> + + 0 -1 97 -1.0795000009238720e-02 + + 4.5613598823547363e-01 -7.2050899267196655e-01 + <_> + + 0 -1 98 -2.4620000272989273e-02 + + -1.7320619821548462e+00 6.8363003432750702e-02 + <_> + + 0 -1 99 3.7380000576376915e-03 + + -1.9303299486637115e-01 6.8243497610092163e-01 + <_> + + 0 -1 100 -1.2264000251889229e-02 + + -1.6095290184020996e+00 7.5268000364303589e-02 + <_> + + 0 -1 101 -4.8670000396668911e-03 + + 7.4286502599716187e-01 -2.1510200202465057e-01 + <_> + + 0 -1 102 7.6725997030735016e-02 + + -2.6835098862648010e-01 1.3094140291213989e+00 + <_> + + 0 -1 103 2.8578000143170357e-02 + + -5.8793000876903534e-02 1.2196329832077026e+00 + <_> + + 0 -1 104 1.9694000482559204e-02 + + -3.5142898559570312e-01 8.4926998615264893e-01 + <_> + + 0 -1 105 -2.9093999415636063e-02 + + -1.0507299900054932e+00 2.9806300997734070e-01 + <_> + + 0 -1 106 -2.9144000262022018e-02 + + 8.2547801733016968e-01 -3.2687199115753174e-01 + <_> + + 0 -1 107 1.9741000607609749e-02 + + 2.0452600717544556e-01 -8.3760201930999756e-01 + <_> + + 0 -1 108 4.3299999088048935e-03 + + 2.0577900111675262e-01 -6.6829800605773926e-01 + <_> + + 0 -1 109 -3.5500999540090561e-02 + + -1.2969900369644165e+00 1.3897499442100525e-01 + <_> + + 0 -1 110 -1.6172999516129494e-02 + + -1.3110569715499878e+00 7.5751997530460358e-02 + <_> + + 0 -1 111 -2.2151000797748566e-02 + + -1.0524389743804932e+00 1.9241100549697876e-01 + <_> + + 0 -1 112 -2.2707000374794006e-02 + + -1.3735309839248657e+00 6.6780999302864075e-02 + <_> + + 0 -1 113 1.6607999801635742e-02 + + -3.7135999649763107e-02 7.7846401929855347e-01 + <_> + + 0 -1 114 -1.3309000059962273e-02 + + -9.9850702285766602e-01 1.2248100340366364e-01 + <_> + + 0 -1 115 -3.3732000738382339e-02 + + 1.4461359977722168e+00 1.3151999562978745e-02 + <_> + + 0 -1 116 1.6935000196099281e-02 + + -3.7121298909187317e-01 5.2842199802398682e-01 + <_> + + 0 -1 117 3.3259999472647905e-03 + + -5.7568502426147461e-01 3.9261901378631592e-01 + <_> + + 0 -1 118 8.3644002676010132e-02 + + 1.6116000711917877e-02 -2.1173279285430908e+00 + <_> + + 0 -1 119 2.5785198807716370e-01 + + -8.1609003245830536e-02 9.8782497644424438e-01 + <_> + + 0 -1 120 -3.6566998809576035e-02 + + -1.1512110233306885e+00 9.6459001302719116e-02 + <_> + + 0 -1 121 -1.6445999965071678e-02 + + 3.7315499782562256e-01 -1.4585399627685547e-01 + <_> + + 0 -1 122 -3.7519999314099550e-03 + + 2.6179298758506775e-01 -5.8156698942184448e-01 + <_> + + 0 -1 123 -6.3660000450909138e-03 + + 7.5477397441864014e-01 -1.7055200040340424e-01 + <_> + + 0 -1 124 -3.8499999791383743e-03 + + 2.2653999924659729e-01 -6.3876402378082275e-01 + <_> + + 0 -1 125 -4.5494001358747482e-02 + + -1.2640299797058105e+00 2.5260698795318604e-01 + <_> + + 0 -1 126 -2.3941000923514366e-02 + + 8.7068402767181396e-01 -2.7104699611663818e-01 + <_> + + 0 -1 127 -7.7558003365993500e-02 + + -1.3901610374450684e+00 2.3612299561500549e-01 + <_> + + 0 -1 128 2.3614000529050827e-02 + + 6.6140003502368927e-02 -1.2645419836044312e+00 + <_> + + 0 -1 129 -2.5750000495463610e-03 + + -5.3841698169708252e-01 3.0379098653793335e-01 + <_> + + 0 -1 130 1.2010800093412399e-01 + + -3.5343000292778015e-01 5.2866202592849731e-01 + <_> + + 0 -1 131 2.2899999748915434e-03 + + -5.8701997995376587e-01 2.4061000347137451e-01 + <_> + + 0 -1 132 6.9716997444629669e-02 + + -3.3348900079727173e-01 5.1916301250457764e-01 + <_> + + 0 -1 133 -4.6670001000165939e-02 + + 6.9795399904251099e-01 -1.4895999804139137e-02 + <_> + + 0 -1 134 -5.0129000097513199e-02 + + 8.6146199703216553e-01 -2.5986000895500183e-01 + <_> + + 0 -1 135 3.0147999525070190e-02 + + 1.9332799315452576e-01 -5.9131097793579102e-01 + <_> + 53 + -4.1299300193786621e+00 + + <_> + + 0 -1 136 9.1085001826286316e-02 + + -8.9233100414276123e-01 1.0434230566024780e+00 + <_> + + 0 -1 137 1.2818999588489532e-02 + + -1.2597670555114746e+00 5.5317097902297974e-01 + <_> + + 0 -1 138 1.5931999310851097e-02 + + -8.6254400014877319e-01 6.3731801509857178e-01 + <_> + + 0 -1 139 2.2780001163482666e-03 + + -7.4639201164245605e-01 5.3155601024627686e-01 + <_> + + 0 -1 140 3.1840998679399490e-02 + + -1.2650489807128906e+00 3.6153900623321533e-01 + <_> + + 0 -1 141 2.6960000395774841e-03 + + -9.8290401697158813e-01 3.6013001203536987e-01 + <_> + + 0 -1 142 -1.2055000290274620e-02 + + 6.4068400859832764e-01 -5.0125002861022949e-01 + <_> + + 0 -1 143 2.1324999630451202e-02 + + -2.4034999310970306e-01 8.5448002815246582e-01 + <_> + + 0 -1 144 3.0486000701785088e-02 + + -3.4273600578308105e-01 1.1428849697113037e+00 + <_> + + 0 -1 145 -4.5079998672008514e-02 + + 1.0976949930191040e+00 -1.7974600195884705e-01 + <_> + + 0 -1 146 -7.1700997650623322e-02 + + 1.5735000371932983e+00 -3.1433498859405518e-01 + <_> + + 0 -1 147 5.9218000620603561e-02 + + -2.7582401037216187e-01 1.0448570251464844e+00 + <_> + + 0 -1 148 6.7010000348091125e-03 + + -1.0974019765853882e+00 1.9801199436187744e-01 + <_> + + 0 -1 149 4.1046999394893646e-02 + + 3.0547699332237244e-01 -1.3287999629974365e+00 + <_> + + 0 -1 150 -8.5499999113380909e-04 + + 2.5807100534439087e-01 -7.0052897930145264e-01 + <_> + + 0 -1 151 -3.0360000208020210e-02 + + -1.2306419610977173e+00 2.2609399259090424e-01 + <_> + + 0 -1 152 -1.2930000200867653e-02 + + 4.0758600831031799e-01 -5.1234501600265503e-01 + <_> + + 0 -1 153 3.7367999553680420e-02 + + -9.4755001366138458e-02 6.1765098571777344e-01 + <_> + + 0 -1 154 2.4434000253677368e-02 + + -4.1100600361824036e-01 4.7630500793457031e-01 + <_> + + 0 -1 155 5.7007998228073120e-02 + + 2.5249299407005310e-01 -6.8669801950454712e-01 + <_> + + 0 -1 156 -1.6313999891281128e-02 + + -9.3928402662277222e-01 1.1448100209236145e-01 + <_> + + 0 -1 157 -1.7648899555206299e-01 + + 1.2451089620590210e+00 -5.6519001722335815e-02 + <_> + + 0 -1 158 1.7614600062370300e-01 + + -3.2528200745582581e-01 8.2791501283645630e-01 + <_> + + 0 -1 159 -7.3910001665353775e-03 + + 3.4783700108528137e-01 -1.7929099500179291e-01 + <_> + + 0 -1 160 6.0890998691320419e-02 + + 5.5098000913858414e-02 -1.5480779409408569e+00 + <_> + + 0 -1 161 -2.9123000800609589e-02 + + -1.0255639553070068e+00 2.4106900393962860e-01 + <_> + + 0 -1 162 -4.5648999512195587e-02 + + 1.0301599502563477e+00 -3.1672099232673645e-01 + <_> + + 0 -1 163 3.7333000451326370e-02 + + 2.1620599925518036e-01 -8.2589900493621826e-01 + <_> + + 0 -1 164 -2.4411000311374664e-02 + + -1.5957959890365601e+00 5.1139000803232193e-02 + <_> + + 0 -1 165 -5.9806998819112778e-02 + + -1.0312290191650391e+00 1.3092300295829773e-01 + <_> + + 0 -1 166 -3.0106000602245331e-02 + + -1.4781630039215088e+00 3.7211999297142029e-02 + <_> + + 0 -1 167 7.4209999293088913e-03 + + -2.4024100601673126e-01 4.9333998560905457e-01 + <_> + + 0 -1 168 -2.1909999195486307e-03 + + 2.8941500186920166e-01 -5.7259601354598999e-01 + <_> + + 0 -1 169 2.0860999822616577e-02 + + -2.3148399591445923e-01 6.3765901327133179e-01 + <_> + + 0 -1 170 -6.6990000195801258e-03 + + -1.2107750177383423e+00 6.4018003642559052e-02 + <_> + + 0 -1 171 1.8758000805974007e-02 + + 2.4461300671100616e-01 -9.9786698818206787e-01 + <_> + + 0 -1 172 -4.4323001056909561e-02 + + -1.3699189424514771e+00 3.6051999777555466e-02 + <_> + + 0 -1 173 2.2859999909996986e-02 + + 2.1288399398326874e-01 -1.0397620201110840e+00 + <_> + + 0 -1 174 -9.8600005730986595e-04 + + 3.2443600893020630e-01 -5.4291802644729614e-01 + <_> + + 0 -1 175 1.7239000648260117e-02 + + -2.8323900699615479e-01 4.4468200206756592e-01 + <_> + + 0 -1 176 -3.4531001001596451e-02 + + -2.3107020854949951e+00 -3.1399999279528856e-03 + <_> + + 0 -1 177 6.7006997764110565e-02 + + 2.8715699911117554e-01 -6.4481002092361450e-01 + <_> + + 0 -1 178 2.3776899278163910e-01 + + -2.7174800634384155e-01 8.0219101905822754e-01 + <_> + + 0 -1 179 -1.2903000228106976e-02 + + -1.5317620038986206e+00 2.1423600614070892e-01 + <_> + + 0 -1 180 1.0514999739825726e-02 + + 7.7037997543811798e-02 -1.0581140518188477e+00 + <_> + + 0 -1 181 1.6969000920653343e-02 + + 1.4306700229644775e-01 -8.5828399658203125e-01 + <_> + + 0 -1 182 -7.2460002265870571e-03 + + -1.1020129919052124e+00 6.4906999468803406e-02 + <_> + + 0 -1 183 1.0556999593973160e-02 + + 1.3964000158011913e-02 6.3601499795913696e-01 + <_> + + 0 -1 184 6.1380001716315746e-03 + + -3.4545901417732239e-01 5.6296801567077637e-01 + <_> + + 0 -1 185 1.3158000074326992e-02 + + 1.9927300512790680e-01 -1.5040320158004761e+00 + <_> + + 0 -1 186 3.1310000922530890e-03 + + -4.0903699398040771e-01 3.7796398997306824e-01 + <_> + + 0 -1 187 -1.0920699685811996e-01 + + -2.2227079868316650e+00 1.2178199738264084e-01 + <_> + + 0 -1 188 8.1820003688335419e-03 + + -2.8652000427246094e-01 6.7890799045562744e-01 + <_> + 62 + -4.0218091011047363e+00 + + <_> + + 0 -1 189 3.1346999108791351e-02 + + -8.8884598016738892e-01 9.4936800003051758e-01 + <_> + + 0 -1 190 3.1918000429868698e-02 + + -1.1146880388259888e+00 4.8888999223709106e-01 + <_> + + 0 -1 191 6.5939999185502529e-03 + + -1.0097689628601074e+00 4.9723801016807556e-01 + <_> + + 0 -1 192 2.6148000732064247e-02 + + 2.5991299748420715e-01 -1.2537480592727661e+00 + <_> + + 0 -1 193 1.2845000252127647e-02 + + -5.7138597965240479e-01 5.9659498929977417e-01 + <_> + + 0 -1 194 2.6344999670982361e-02 + + -5.5203199386596680e-01 3.0217400193214417e-01 + <_> + + 0 -1 195 -1.5083000063896179e-02 + + -1.2871240377426147e+00 2.2354200482368469e-01 + <_> + + 0 -1 196 -3.8887001574039459e-02 + + 1.7425049543380737e+00 -9.9747002124786377e-02 + <_> + + 0 -1 197 -5.7029998861253262e-03 + + -1.0523240566253662e+00 1.8362599611282349e-01 + <_> + + 0 -1 198 -1.4860000228509307e-03 + + 5.6784200668334961e-01 -4.6742001175880432e-01 + <_> + + 0 -1 199 -2.8486000373959541e-02 + + 1.3082909584045410e+00 -2.6460900902748108e-01 + <_> + + 0 -1 200 6.6224999725818634e-02 + + -4.6210700273513794e-01 4.1749599575996399e-01 + <_> + + 0 -1 201 8.8569996878504753e-03 + + -4.1474899649620056e-01 5.9204798936843872e-01 + <_> + + 0 -1 202 1.1355999857187271e-02 + + 3.6103099584579468e-01 -4.5781201124191284e-01 + <_> + + 0 -1 203 -2.7679998893290758e-03 + + -8.9238899946212769e-01 1.4199000597000122e-01 + <_> + + 0 -1 204 1.1246999725699425e-02 + + 2.9353401064872742e-01 -9.7330600023269653e-01 + <_> + + 0 -1 205 7.1970000863075256e-03 + + -7.9334902763366699e-01 1.8313400447368622e-01 + <_> + + 0 -1 206 3.1768999993801117e-02 + + 1.5523099899291992e-01 -1.3245639801025391e+00 + <_> + + 0 -1 207 2.5173999369144440e-02 + + 3.4214999526739120e-02 -2.0948131084442139e+00 + <_> + + 0 -1 208 7.5360001064836979e-03 + + -3.9450600743293762e-01 5.1333999633789062e-01 + <_> + + 0 -1 209 3.2873000949621201e-02 + + 8.8372997939586639e-02 -1.2814120054244995e+00 + <_> + + 0 -1 210 -2.7379998937249184e-03 + + 5.5286502838134766e-01 -4.6384999155998230e-01 + <_> + + 0 -1 211 -3.8075000047683716e-02 + + -1.8497270345687866e+00 4.5944001525640488e-02 + <_> + + 0 -1 212 -3.8984000682830811e-02 + + -4.8223701119422913e-01 3.4760600328445435e-01 + <_> + + 0 -1 213 2.8029999230057001e-03 + + -4.5154699683189392e-01 4.2806300520896912e-01 + <_> + + 0 -1 214 -5.4145999252796173e-02 + + -8.4520798921585083e-01 1.6674900054931641e-01 + <_> + + 0 -1 215 -8.3280000835657120e-03 + + 3.5348299145698547e-01 -4.7163200378417969e-01 + <_> + + 0 -1 216 3.3778000622987747e-02 + + 1.8463100492954254e-01 -1.6686669588088989e+00 + <_> + + 0 -1 217 -1.1238099634647369e-01 + + -1.2521569728851318e+00 3.5992000252008438e-02 + <_> + + 0 -1 218 -1.0408000089228153e-02 + + -8.1620401144027710e-01 2.3428599536418915e-01 + <_> + + 0 -1 219 -4.9439999274909496e-03 + + -9.2584699392318726e-01 1.0034800320863724e-01 + <_> + + 0 -1 220 -9.3029998242855072e-03 + + 5.6499302387237549e-01 -1.8881900608539581e-01 + <_> + + 0 -1 221 -1.1749999597668648e-02 + + 8.0302399396896362e-01 -3.8277000188827515e-01 + <_> + + 0 -1 222 -2.3217000067234039e-02 + + -8.4926998615264893e-01 1.9671200215816498e-01 + <_> + + 0 -1 223 1.6866000369191170e-02 + + -4.0591898560523987e-01 5.0695300102233887e-01 + <_> + + 0 -1 224 -2.4031000211834908e-02 + + -1.5297520160675049e+00 2.3344999551773071e-01 + <_> + + 0 -1 225 -3.6945998668670654e-02 + + 6.3007700443267822e-01 -3.1780400872230530e-01 + <_> + + 0 -1 226 -6.1563998460769653e-02 + + 5.8627897500991821e-01 -1.2107999995350838e-02 + <_> + + 0 -1 227 2.1661000326275826e-02 + + -2.5623700022697449e-01 1.0409849882125854e+00 + <_> + + 0 -1 228 -3.6710000131279230e-03 + + 2.9171100258827209e-01 -8.3287298679351807e-01 + <_> + + 0 -1 229 4.4849000871181488e-02 + + -3.9633199572563171e-01 4.5662000775337219e-01 + <_> + + 0 -1 230 5.7195000350475311e-02 + + 2.1023899316787720e-01 -1.5004800558090210e+00 + <_> + + 0 -1 231 -1.1342000216245651e-02 + + 4.4071298837661743e-01 -3.8653799891471863e-01 + <_> + + 0 -1 232 -1.2004000134766102e-02 + + 9.3954598903656006e-01 -1.0589499771595001e-01 + <_> + + 0 -1 233 2.2515999153256416e-02 + + 9.4480002298951149e-03 -1.6799509525299072e+00 + <_> + + 0 -1 234 -1.9809000194072723e-02 + + -1.0133639574050903e+00 2.4146600067615509e-01 + <_> + + 0 -1 235 1.5891000628471375e-02 + + -3.7507599592208862e-01 4.6614098548889160e-01 + <_> + + 0 -1 236 -9.1420002281665802e-03 + + -8.0484098196029663e-01 1.7816999554634094e-01 + <_> + + 0 -1 237 -4.4740000739693642e-03 + + -1.0562069416046143e+00 7.3305003345012665e-02 + <_> + + 0 -1 238 1.2742500007152557e-01 + + 2.0165599882602692e-01 -1.5467929840087891e+00 + <_> + + 0 -1 239 4.7703001648187637e-02 + + -3.7937799096107483e-01 3.7885999679565430e-01 + <_> + + 0 -1 240 5.3608000278472900e-02 + + 2.1220499277114868e-01 -1.2399710416793823e+00 + <_> + + 0 -1 241 -3.9680998772382736e-02 + + -1.0257550477981567e+00 5.1282998174428940e-02 + <_> + + 0 -1 242 -6.7327000200748444e-02 + + -1.0304750204086304e+00 2.3005299270153046e-01 + <_> + + 0 -1 243 1.3337600231170654e-01 + + -2.0869000256061554e-01 1.2272510528564453e+00 + <_> + + 0 -1 244 -2.0919300615787506e-01 + + 8.7929898500442505e-01 -4.4254999607801437e-02 + <_> + + 0 -1 245 -6.5589003264904022e-02 + + 1.0443429946899414e+00 -2.1682099997997284e-01 + <_> + + 0 -1 246 6.1882998794317245e-02 + + 1.3798199594020844e-01 -1.9009059667587280e+00 + <_> + + 0 -1 247 -2.5578999891877174e-02 + + -1.6607600450515747e+00 5.8439997956156731e-03 + <_> + + 0 -1 248 -3.4827001392841339e-02 + + 7.9940402507781982e-01 -8.2406997680664062e-02 + <_> + + 0 -1 249 -1.8209999427199364e-02 + + -9.6073997020721436e-01 6.6320002079010010e-02 + <_> + + 0 -1 250 1.5070999972522259e-02 + + 1.9899399578571320e-01 -7.6433002948760986e-01 + <_> + 72 + -3.8832089900970459e+00 + + <_> + + 0 -1 251 4.6324998140335083e-02 + + -1.0362670421600342e+00 8.2201498746871948e-01 + <_> + + 0 -1 252 1.5406999737024307e-02 + + -1.2327589988708496e+00 2.9647698998451233e-01 + <_> + + 0 -1 253 1.2808999978005886e-02 + + -7.5852298736572266e-01 5.7985502481460571e-01 + <_> + + 0 -1 254 4.9150999635457993e-02 + + -3.8983899354934692e-01 8.9680302143096924e-01 + <_> + + 0 -1 255 1.2621000409126282e-02 + + -7.1799302101135254e-01 5.0440901517868042e-01 + <_> + + 0 -1 256 -1.8768999725580215e-02 + + 5.5147600173950195e-01 -7.0555400848388672e-01 + <_> + + 0 -1 257 4.1965000331401825e-02 + + -4.4782099127769470e-01 7.0985502004623413e-01 + <_> + + 0 -1 258 -5.1401998847723007e-02 + + -1.0932120084762573e+00 2.6701900362968445e-01 + <_> + + 0 -1 259 -7.0960998535156250e-02 + + 8.3618402481079102e-01 -3.8318100571632385e-01 + <_> + + 0 -1 260 1.6745999455451965e-02 + + -2.5733101367950439e-01 2.5966501235961914e-01 + <_> + + 0 -1 261 -6.2400000169873238e-03 + + 3.1631499528884888e-01 -5.8796900510787964e-01 + <_> + + 0 -1 262 -3.9397999644279480e-02 + + -1.0491210222244263e+00 1.6822400689125061e-01 + <_> + + 0 -1 263 0. + + 1.6144199669361115e-01 -8.7876898050308228e-01 + <_> + + 0 -1 264 -2.2307999432086945e-02 + + -6.9053500890731812e-01 2.3607000708580017e-01 + <_> + + 0 -1 265 1.8919999711215496e-03 + + 2.4989199638366699e-01 -5.6583297252655029e-01 + <_> + + 0 -1 266 1.0730000212788582e-03 + + -5.0415802001953125e-01 3.8374501466751099e-01 + <_> + + 0 -1 267 3.9230998605489731e-02 + + 4.2619001120328903e-02 -1.3875889778137207e+00 + <_> + + 0 -1 268 6.2238000333309174e-02 + + 1.4119400084018707e-01 -1.0688860416412354e+00 + <_> + + 0 -1 269 2.1399999968707561e-03 + + -8.9622402191162109e-01 1.9796399772167206e-01 + <_> + + 0 -1 270 9.1800000518560410e-04 + + -4.5337298512458801e-01 4.3532699346542358e-01 + <_> + + 0 -1 271 -6.9169998168945312e-03 + + 3.3822798728942871e-01 -4.4793000817298889e-01 + <_> + + 0 -1 272 -2.3866999894380569e-02 + + -7.8908598423004150e-01 2.2511799633502960e-01 + <_> + + 0 -1 273 -1.0262800008058548e-01 + + -2.2831439971923828e+00 -5.3960001096129417e-03 + <_> + + 0 -1 274 -9.5239998772740364e-03 + + 3.9346700906753540e-01 -5.2242201566696167e-01 + <_> + + 0 -1 275 3.9877001196146011e-02 + + 3.2799001783132553e-02 -1.5079489946365356e+00 + <_> + + 0 -1 276 -1.3144999742507935e-02 + + -1.0839990377426147e+00 1.8482400476932526e-01 + <_> + + 0 -1 277 -5.0590999424457550e-02 + + -1.8822289705276489e+00 -2.2199999075382948e-03 + <_> + + 0 -1 278 2.4917000904679298e-02 + + 1.4593400061130524e-01 -2.2196519374847412e+00 + <_> + + 0 -1 279 -7.6370001770555973e-03 + + -1.0164569616317749e+00 5.8797001838684082e-02 + <_> + + 0 -1 280 4.2911998927593231e-02 + + 1.5443000197410583e-01 -1.1843889951705933e+00 + <_> + + 0 -1 281 2.3000000510364771e-04 + + -7.7305799722671509e-01 1.2189900130033493e-01 + <_> + + 0 -1 282 9.0929996222257614e-03 + + -1.1450099945068359e-01 7.1091300249099731e-01 + <_> + + 0 -1 283 1.1145000346004963e-02 + + 7.0000998675823212e-02 -1.0534820556640625e+00 + <_> + + 0 -1 284 -5.2453000098466873e-02 + + -1.7594360113143921e+00 1.9523799419403076e-01 + <_> + + 0 -1 285 -2.3020699620246887e-01 + + 9.5840299129486084e-01 -2.5045698881149292e-01 + <_> + + 0 -1 286 -1.6365999355912209e-02 + + 4.6731901168823242e-01 -2.1108399331569672e-01 + <_> + + 0 -1 287 -1.7208000645041466e-02 + + 7.0835697650909424e-01 -2.8018298745155334e-01 + <_> + + 0 -1 288 -3.6648001521825790e-02 + + -1.1013339757919312e+00 2.4341100454330444e-01 + <_> + + 0 -1 289 -1.0304999537765980e-02 + + -1.0933129787445068e+00 5.6258998811244965e-02 + <_> + + 0 -1 290 -1.3713000342249870e-02 + + -2.6438099145889282e-01 1.9821000099182129e-01 + <_> + + 0 -1 291 2.9308000579476357e-02 + + -2.2142399847507477e-01 1.0525950193405151e+00 + <_> + + 0 -1 292 2.4077000096440315e-02 + + 1.8485699594020844e-01 -1.7203969955444336e+00 + <_> + + 0 -1 293 6.1280000954866409e-03 + + -9.2721498012542725e-01 5.8752998709678650e-02 + <_> + + 0 -1 294 -2.2377999499440193e-02 + + 1.9646559953689575e+00 2.7785999700427055e-02 + <_> + + 0 -1 295 -7.0440000854432583e-03 + + 2.1427600085735321e-01 -4.8407599329948425e-01 + <_> + + 0 -1 296 -4.0603000670671463e-02 + + -1.1754349470138550e+00 1.6061200201511383e-01 + <_> + + 0 -1 297 -2.4466000497341156e-02 + + -1.1239900588989258e+00 4.1110001504421234e-02 + <_> + + 0 -1 298 2.5309999473392963e-03 + + -1.7169700562953949e-01 3.2178801298141479e-01 + <_> + + 0 -1 299 -1.9588999450206757e-02 + + 8.2720202207565308e-01 -2.6376700401306152e-01 + <_> + + 0 -1 300 -2.9635999351739883e-02 + + -1.1524770259857178e+00 1.4999300241470337e-01 + <_> + + 0 -1 301 -1.5030000358819962e-02 + + -1.0491830110549927e+00 4.0160998702049255e-02 + <_> + + 0 -1 302 -6.0715001076459885e-02 + + -1.0903840065002441e+00 1.5330800414085388e-01 + <_> + + 0 -1 303 -1.2790000066161156e-02 + + 4.2248600721359253e-01 -4.2399200797080994e-01 + <_> + + 0 -1 304 -2.0247999578714371e-02 + + -9.1866999864578247e-01 1.8485699594020844e-01 + <_> + + 0 -1 305 -3.0683999881148338e-02 + + -1.5958670377731323e+00 2.5760000571608543e-03 + <_> + + 0 -1 306 -2.0718000829219818e-02 + + -6.6299998760223389e-01 3.1037199497222900e-01 + <_> + + 0 -1 307 -1.7290000105276704e-03 + + 1.9183400273323059e-01 -6.5084999799728394e-01 + <_> + + 0 -1 308 -3.1394001096487045e-02 + + -6.3643002510070801e-01 1.5408399701118469e-01 + <_> + + 0 -1 309 1.9003000110387802e-02 + + -1.8919399380683899e-01 1.5294510126113892e+00 + <_> + + 0 -1 310 6.1769997701048851e-03 + + -1.0597900301218033e-01 6.4859598875045776e-01 + <_> + + 0 -1 311 -1.0165999643504620e-02 + + -1.0802700519561768e+00 3.7176001816987991e-02 + <_> + + 0 -1 312 -1.4169999631121755e-03 + + 3.4157499670982361e-01 -9.7737997770309448e-02 + <_> + + 0 -1 313 -4.0799998678267002e-03 + + 4.7624599933624268e-01 -3.4366300702095032e-01 + <_> + + 0 -1 314 -4.4096998870372772e-02 + + 9.7634297609329224e-01 -1.9173000007867813e-02 + <_> + + 0 -1 315 -6.0669999569654465e-02 + + -2.1752851009368896e+00 -2.8925999999046326e-02 + <_> + + 0 -1 316 -3.2931998372077942e-02 + + -6.4383101463317871e-01 1.6494099795818329e-01 + <_> + + 0 -1 317 -1.4722800254821777e-01 + + -1.4745830297470093e+00 2.5839998852461576e-03 + <_> + + 0 -1 318 -1.1930000036954880e-02 + + 4.2441400885581970e-01 -1.7712600529193878e-01 + <_> + + 0 -1 319 1.4517900347709656e-01 + + 2.5444999337196350e-02 -1.2779400348663330e+00 + <_> + + 0 -1 320 5.1447998732328415e-02 + + 1.5678399801254272e-01 -1.5188430547714233e+00 + <_> + + 0 -1 321 3.1479999888688326e-03 + + -4.0424400568008423e-01 3.2429701089859009e-01 + <_> + + 0 -1 322 -4.3600000441074371e-02 + + -1.9932260513305664e+00 1.5018600225448608e-01 + <_> + 83 + -3.8424909114837646e+00 + + <_> + + 0 -1 323 1.2899599969387054e-01 + + -6.2161999940872192e-01 1.1116520166397095e+00 + <_> + + 0 -1 324 -9.1261997818946838e-02 + + 1.0143059492111206e+00 -6.1335200071334839e-01 + <_> + + 0 -1 325 1.4271999709308147e-02 + + -1.0261659622192383e+00 3.9779999852180481e-01 + <_> + + 0 -1 326 3.2889999449253082e-02 + + -1.1386079788208008e+00 2.8690800070762634e-01 + <_> + + 0 -1 327 1.2590000405907631e-02 + + -5.6645601987838745e-01 4.5172399282455444e-01 + <_> + + 0 -1 328 1.4661000110208988e-02 + + 3.0505999922752380e-01 -6.8129599094390869e-01 + <_> + + 0 -1 329 -3.3555999398231506e-02 + + -1.7208939790725708e+00 6.1439000070095062e-02 + <_> + + 0 -1 330 1.4252699911594391e-01 + + 2.3192200064659119e-01 -1.7297149896621704e+00 + <_> + + 0 -1 331 -6.2079997733235359e-03 + + -1.2163300514221191e+00 1.2160199880599976e-01 + <_> + + 0 -1 332 1.8178999423980713e-02 + + 3.2553699612617493e-01 -8.1003999710083008e-01 + <_> + + 0 -1 333 2.5036999955773354e-02 + + -3.1698799133300781e-01 6.7361402511596680e-01 + <_> + + 0 -1 334 4.6560999006032944e-02 + + -1.1089800298213959e-01 8.4082502126693726e-01 + <_> + + 0 -1 335 -8.9999996125698090e-03 + + 3.9574500918388367e-01 -4.7624599933624268e-01 + <_> + + 0 -1 336 4.0805999189615250e-02 + + -1.8000000272877514e-04 9.4570702314376831e-01 + <_> + + 0 -1 337 -3.4221999347209930e-02 + + 7.5206297636032104e-01 -3.1531500816345215e-01 + <_> + + 0 -1 338 -3.9716001600027084e-02 + + -8.3139598369598389e-01 1.7744399607181549e-01 + <_> + + 0 -1 339 2.5170000735670328e-03 + + -5.9377998113632202e-01 2.4657000601291656e-01 + <_> + + 0 -1 340 2.7428999543190002e-02 + + 1.5998399257659912e-01 -4.2781999707221985e-01 + <_> + + 0 -1 341 3.4986000508069992e-02 + + 3.5055998712778091e-02 -1.5988600254058838e+00 + <_> + + 0 -1 342 4.4970000162720680e-03 + + -5.2034300565719604e-01 3.7828299403190613e-01 + <_> + + 0 -1 343 2.7699999045580626e-03 + + -5.3182601928710938e-01 2.4951000511646271e-01 + <_> + + 0 -1 344 3.5174001008272171e-02 + + 1.9983400404453278e-01 -1.4446129798889160e+00 + <_> + + 0 -1 345 2.5970999151468277e-02 + + 4.4426999986171722e-02 -1.3622980117797852e+00 + <_> + + 0 -1 346 -1.5783999115228653e-02 + + -9.1020399332046509e-01 2.7190300822257996e-01 + <_> + + 0 -1 347 -7.5880000367760658e-03 + + 9.2064999043941498e-02 -8.1628900766372681e-01 + <_> + + 0 -1 348 2.0754000172019005e-02 + + 2.1185700595378876e-01 -7.4729001522064209e-01 + <_> + + 0 -1 349 5.9829000383615494e-02 + + -2.7301099896430969e-01 8.0923300981521606e-01 + <_> + + 0 -1 350 3.9039000868797302e-02 + + -1.0432299971580505e-01 8.6226201057434082e-01 + <_> + + 0 -1 351 2.1665999665856361e-02 + + 6.2709003686904907e-02 -9.8894298076629639e-01 + <_> + + 0 -1 352 -2.7496999129652977e-02 + + -9.2690998315811157e-01 1.5586300194263458e-01 + <_> + + 0 -1 353 1.0462000034749508e-02 + + 1.3418099284172058e-01 -7.0386397838592529e-01 + <_> + + 0 -1 354 2.4870999157428741e-02 + + 1.9706700742244720e-01 -4.0263301134109497e-01 + <_> + + 0 -1 355 -1.6036000102758408e-02 + + -1.1409829854965210e+00 7.3997996747493744e-02 + <_> + + 0 -1 356 4.8627000302076340e-02 + + 1.6990399360656738e-01 -7.2152197360992432e-01 + <_> + + 0 -1 357 1.2619999470189214e-03 + + -4.7389799356460571e-01 2.6254999637603760e-01 + <_> + + 0 -1 358 -8.8035002350807190e-02 + + -2.1606519222259521e+00 1.4554800093173981e-01 + <_> + + 0 -1 359 1.8356999382376671e-02 + + 4.4750999659299850e-02 -1.0766370296478271e+00 + <_> + + 0 -1 360 3.5275001078844070e-02 + + -3.2919000834226608e-02 1.2153890132904053e+00 + <_> + + 0 -1 361 -2.0392900705337524e-01 + + -1.3187999725341797e+00 1.5503999777138233e-02 + <_> + + 0 -1 362 -1.6619000583887100e-02 + + 3.6850199103355408e-01 -1.5283699333667755e-01 + <_> + + 0 -1 363 3.7739001214504242e-02 + + -2.5727799534797668e-01 7.0655298233032227e-01 + <_> + + 0 -1 364 2.2720000706613064e-03 + + -7.7602997422218323e-02 3.3367800712585449e-01 + <_> + + 0 -1 365 -1.4802999794483185e-02 + + -7.8524798154830933e-01 7.6934002339839935e-02 + <_> + + 0 -1 366 -4.8319000750780106e-02 + + 1.7022320032119751e+00 4.9722000956535339e-02 + <_> + + 0 -1 367 -2.9539000242948532e-02 + + 7.7670699357986450e-01 -2.4534299969673157e-01 + <_> + + 0 -1 368 -4.6169001609086990e-02 + + -1.4922779798507690e+00 1.2340000271797180e-01 + <_> + + 0 -1 369 -2.8064999729394913e-02 + + -2.1345369815826416e+00 -2.5797000154852867e-02 + <_> + + 0 -1 370 -5.7339998893439770e-03 + + 5.6982600688934326e-01 -1.2056600302457809e-01 + <_> + + 0 -1 371 -1.0111000388860703e-02 + + 6.7911398410797119e-01 -2.6638001203536987e-01 + <_> + + 0 -1 372 1.1359999887645245e-02 + + 2.4789799749851227e-01 -6.4493000507354736e-01 + <_> + + 0 -1 373 5.1809001713991165e-02 + + 1.4716000296175480e-02 -1.2395579814910889e+00 + <_> + + 0 -1 374 3.3291999250650406e-02 + + -8.2559995353221893e-03 1.0168470144271851e+00 + <_> + + 0 -1 375 -1.4494000002741814e-02 + + 4.5066800713539124e-01 -3.6250999569892883e-01 + <_> + + 0 -1 376 -3.4221999347209930e-02 + + -9.5292502641677856e-01 2.0684599876403809e-01 + <_> + + 0 -1 377 -8.0654002726078033e-02 + + -2.0139501094818115e+00 -2.3084999993443489e-02 + <_> + + 0 -1 378 -8.9399999706074595e-04 + + 3.9572000503540039e-01 -2.9351300001144409e-01 + <_> + + 0 -1 379 9.7162000834941864e-02 + + -2.4980300664901733e-01 1.0859220027923584e+00 + <_> + + 0 -1 380 3.6614000797271729e-02 + + -5.7844001799821854e-02 1.2162159681320190e+00 + <_> + + 0 -1 381 5.1693998277187347e-02 + + 4.3062999844551086e-02 -1.0636160373687744e+00 + <_> + + 0 -1 382 -2.4557000026106834e-02 + + -4.8946800827980042e-01 1.7182900011539459e-01 + <_> + + 0 -1 383 3.2736799120903015e-01 + + -2.9688599705696106e-01 5.1798301935195923e-01 + <_> + + 0 -1 384 7.6959999278187752e-03 + + -5.9805899858474731e-01 2.4803200364112854e-01 + <_> + + 0 -1 385 1.6172200441360474e-01 + + -2.9613999649882317e-02 -2.3162529468536377e+00 + <_> + + 0 -1 386 -4.7889999113976955e-03 + + 3.7457901239395142e-01 -3.2779198884963989e-01 + <_> + + 0 -1 387 -1.8402999266982079e-02 + + -9.9692702293395996e-01 7.2948001325130463e-02 + <_> + + 0 -1 388 7.7665001153945923e-02 + + 1.4175699651241302e-01 -1.7238730192184448e+00 + <_> + + 0 -1 389 1.8921000882983208e-02 + + -2.1273100376129150e-01 1.0165189504623413e+00 + <_> + + 0 -1 390 -7.9397998750209808e-02 + + -1.3164349794387817e+00 1.4981999993324280e-01 + <_> + + 0 -1 391 -6.8037003278732300e-02 + + 4.9421998858451843e-01 -2.9091000556945801e-01 + <_> + + 0 -1 392 -6.1010001227259636e-03 + + 4.2430499196052551e-01 -3.3899301290512085e-01 + <_> + + 0 -1 393 3.1927000731229782e-02 + + -3.1046999618411064e-02 -2.3459999561309814e+00 + <_> + + 0 -1 394 -2.9843999072909355e-02 + + -7.8989601135253906e-01 1.5417699515819550e-01 + <_> + + 0 -1 395 -8.0541998147964478e-02 + + -2.2509229183197021e+00 -3.0906999483704567e-02 + <_> + + 0 -1 396 3.8109999150037766e-03 + + -2.5577300786972046e-01 2.3785500228404999e-01 + <_> + + 0 -1 397 3.3647000789642334e-02 + + -2.2541399300098419e-01 9.2307400703430176e-01 + <_> + + 0 -1 398 8.2809999585151672e-03 + + -2.8896200656890869e-01 3.1046199798583984e-01 + <_> + + 0 -1 399 1.0104399919509888e-01 + + -3.4864000976085663e-02 -2.7102620601654053e+00 + <_> + + 0 -1 400 -1.0009000077843666e-02 + + 5.9715402126312256e-01 -3.3831000328063965e-02 + <_> + + 0 -1 401 7.1919998154044151e-03 + + -4.7738000750541687e-01 2.2686000168323517e-01 + <_> + + 0 -1 402 2.4969000369310379e-02 + + 2.2877700626850128e-01 -1.0435529947280884e+00 + <_> + + 0 -1 403 2.7908000349998474e-01 + + -2.5818100571632385e-01 7.6780498027801514e-01 + <_> + + 0 -1 404 -4.4213000684976578e-02 + + -5.9798002243041992e-01 2.8039899468421936e-01 + <_> + + 0 -1 405 -1.4136999845504761e-02 + + 7.0987302064895630e-01 -2.5645199418067932e-01 + <_> + 91 + -3.6478610038757324e+00 + + <_> + + 0 -1 406 1.3771200180053711e-01 + + -5.5870598554611206e-01 1.0953769683837891e+00 + <_> + + 0 -1 407 3.4460999071598053e-02 + + -7.1171897649765015e-01 5.2899599075317383e-01 + <_> + + 0 -1 408 1.8580000847578049e-02 + + -1.1157519817352295e+00 4.0593999624252319e-01 + <_> + + 0 -1 409 2.5041999295353889e-02 + + -4.0892499685287476e-01 7.4129998683929443e-01 + <_> + + 0 -1 410 5.7179000228643417e-02 + + -3.8054299354553223e-01 7.3647701740264893e-01 + <_> + + 0 -1 411 1.4932000078260899e-02 + + -6.9945502281188965e-01 3.7950998544692993e-01 + <_> + + 0 -1 412 8.8900001719594002e-03 + + -5.4558598995208740e-01 3.6332499980926514e-01 + <_> + + 0 -1 413 3.0435999855399132e-02 + + -1.0124599933624268e-01 7.9585897922515869e-01 + <_> + + 0 -1 414 -4.4160000979900360e-02 + + 8.4410899877548218e-01 -3.2976400852203369e-01 + <_> + + 0 -1 415 1.8461000174283981e-02 + + 2.6326599717140198e-01 -9.6736502647399902e-01 + <_> + + 0 -1 416 1.0614999569952488e-02 + + 1.5251900255680084e-01 -1.0589870214462280e+00 + <_> + + 0 -1 417 -4.5974001288414001e-02 + + -1.9918340444564819e+00 1.3629099726676941e-01 + <_> + + 0 -1 418 8.2900002598762512e-02 + + -3.2037198543548584e-01 6.0304200649261475e-01 + <_> + + 0 -1 419 -8.9130001142621040e-03 + + 5.9586602449417114e-01 -2.1139599382877350e-01 + <_> + + 0 -1 420 4.2814001441001892e-02 + + 2.2925000637769699e-02 -1.4679330587387085e+00 + <_> + + 0 -1 421 -8.7139997631311417e-03 + + -4.3989500403404236e-01 2.0439699292182922e-01 + <_> + + 0 -1 422 -4.3390002101659775e-03 + + -8.9066797494888306e-01 1.0469999909400940e-01 + <_> + + 0 -1 423 8.0749997869133949e-03 + + 2.1164199709892273e-01 -4.0231600403785706e-01 + <_> + + 0 -1 424 9.6739001572132111e-02 + + 1.3319999910891056e-02 -1.6085360050201416e+00 + <_> + + 0 -1 425 -3.0536999925971031e-02 + + 1.0063740015029907e+00 -1.3413299620151520e-01 + <_> + + 0 -1 426 -6.0855999588966370e-02 + + -1.4689979553222656e+00 9.4240000471472740e-03 + <_> + + 0 -1 427 -3.8162000477313995e-02 + + -8.1636399030685425e-01 2.6171201467514038e-01 + <_> + + 0 -1 428 -9.6960002556443214e-03 + + 1.1561699956655502e-01 -7.1693199872970581e-01 + <_> + + 0 -1 429 4.8902999609708786e-02 + + 1.3050499558448792e-01 -1.6448370218276978e+00 + <_> + + 0 -1 430 -4.1611999273300171e-02 + + -1.1795840263366699e+00 2.5017000734806061e-02 + <_> + + 0 -1 431 -2.0188000053167343e-02 + + 6.3188201189041138e-01 -1.0490400344133377e-01 + <_> + + 0 -1 432 -9.7900000400841236e-04 + + 1.8507799506187439e-01 -5.3565901517868042e-01 + <_> + + 0 -1 433 -3.3622000366449356e-02 + + -9.3127602338790894e-01 2.0071500539779663e-01 + <_> + + 0 -1 434 1.9455999135971069e-02 + + 3.8029000163078308e-02 -1.0112210512161255e+00 + <_> + + 0 -1 435 -3.1800000579096377e-04 + + 3.6457699537277222e-01 -2.7610900998115540e-01 + <_> + + 0 -1 436 -3.8899999344721437e-04 + + 1.9665899872779846e-01 -5.3410500288009644e-01 + <_> + + 0 -1 437 -9.3496002256870270e-02 + + -1.6772350072860718e+00 2.0727099478244781e-01 + <_> + + 0 -1 438 -7.7877998352050781e-02 + + -3.0760629177093506e+00 -3.5803999751806259e-02 + <_> + + 0 -1 439 1.6947999596595764e-02 + + 2.1447399258613586e-01 -7.1376299858093262e-01 + <_> + + 0 -1 440 -2.1459000185132027e-02 + + -1.1468060016632080e+00 1.5855999663472176e-02 + <_> + + 0 -1 441 -1.2865999713540077e-02 + + 8.3812397718429565e-01 -6.5944001078605652e-02 + <_> + + 0 -1 442 7.8220004215836525e-03 + + -2.8026801347732544e-01 7.9376900196075439e-01 + <_> + + 0 -1 443 1.0294400155544281e-01 + + 1.7832300066947937e-01 -6.8412202596664429e-01 + <_> + + 0 -1 444 -3.7487998604774475e-02 + + 9.6189999580383301e-01 -2.1735599637031555e-01 + <_> + + 0 -1 445 2.5505999103188515e-02 + + 1.0103999637067318e-02 1.2461110353469849e+00 + <_> + + 0 -1 446 6.6700001480057836e-04 + + -5.3488200902938843e-01 1.4746299386024475e-01 + <_> + + 0 -1 447 -2.8867900371551514e-01 + + 8.2172799110412598e-01 -1.4948000200092793e-02 + <_> + + 0 -1 448 9.1294996440410614e-02 + + -1.9605399668216705e-01 1.0803170204162598e+00 + <_> + + 0 -1 449 1.2056600302457809e-01 + + -2.3848999291658401e-02 1.1392610073089600e+00 + <_> + + 0 -1 450 -7.3775000870227814e-02 + + -1.3583840131759644e+00 -4.2039998807013035e-03 + <_> + + 0 -1 451 -3.3128000795841217e-02 + + -6.4483201503753662e-01 2.4142199754714966e-01 + <_> + + 0 -1 452 -4.3937001377344131e-02 + + 8.4285402297973633e-01 -2.0624800026416779e-01 + <_> + + 0 -1 453 1.8110199272632599e-01 + + 1.9212099909782410e-01 -1.2222139835357666e+00 + <_> + + 0 -1 454 -1.1850999668240547e-02 + + -7.2677397727966309e-01 5.2687998861074448e-02 + <_> + + 0 -1 455 4.5920000411570072e-03 + + -3.6305201053619385e-01 2.9223799705505371e-01 + <_> + + 0 -1 456 7.0620002225041389e-03 + + 5.8116000145673752e-02 -6.7161601781845093e-01 + <_> + + 0 -1 457 -2.3715000599622726e-02 + + 4.7142100334167480e-01 1.8580000847578049e-02 + <_> + + 0 -1 458 -6.7171998322010040e-02 + + -1.1331889629364014e+00 2.3780999705195427e-02 + <_> + + 0 -1 459 -6.5310001373291016e-02 + + 9.8253500461578369e-01 2.8362000361084938e-02 + <_> + + 0 -1 460 2.2791000083088875e-02 + + -2.8213700652122498e-01 5.8993399143218994e-01 + <_> + + 0 -1 461 -1.9037999212741852e-02 + + -6.3711500167846680e-01 2.6514598727226257e-01 + <_> + + 0 -1 462 -6.8689999170601368e-03 + + 3.7487301230430603e-01 -3.3232098817825317e-01 + <_> + + 0 -1 463 -4.0146000683307648e-02 + + -1.3048729896545410e+00 1.5724299848079681e-01 + <_> + + 0 -1 464 -4.0530998259782791e-02 + + -2.0458049774169922e+00 -2.6925999671220779e-02 + <_> + + 0 -1 465 -1.2253999710083008e-02 + + 7.7649402618408203e-01 -4.2971000075340271e-02 + <_> + + 0 -1 466 -2.7219999581575394e-02 + + 1.7424400150775909e-01 -4.4600901007652283e-01 + <_> + + 0 -1 467 -8.8366001844406128e-02 + + -1.5036419630050659e+00 1.4289900660514832e-01 + <_> + + 0 -1 468 -7.9159997403621674e-03 + + 2.8666698932647705e-01 -3.7923699617385864e-01 + <_> + + 0 -1 469 -4.1960000991821289e-02 + + 1.3846950531005859e+00 6.5026998519897461e-02 + <_> + + 0 -1 470 4.5662999153137207e-02 + + -2.2452299296855927e-01 7.9521000385284424e-01 + <_> + + 0 -1 471 -1.4090600609779358e-01 + + -1.5879319906234741e+00 1.1359000205993652e-01 + <_> + + 0 -1 472 -5.9216000139713287e-02 + + -1.1945960521697998e+00 -7.1640000678598881e-03 + <_> + + 0 -1 473 4.3390002101659775e-03 + + -1.5528699755668640e-01 4.0664499998092651e-01 + <_> + + 0 -1 474 -2.0369999110698700e-03 + + 2.5927901268005371e-01 -3.8368299603462219e-01 + <_> + + 0 -1 475 2.7516499161720276e-01 + + -8.8497996330261230e-02 7.6787501573562622e-01 + <_> + + 0 -1 476 -2.6601999998092651e-02 + + 7.5024497509002686e-01 -2.2621999680995941e-01 + <_> + + 0 -1 477 4.0906000882387161e-02 + + 1.2158600240945816e-01 -1.4566910266876221e+00 + <_> + + 0 -1 478 5.5320002138614655e-03 + + -3.6611500382423401e-01 2.5968599319458008e-01 + <_> + + 0 -1 479 3.1879000365734100e-02 + + -7.5019001960754395e-02 4.8484799265861511e-01 + <_> + + 0 -1 480 -4.1482001543045044e-02 + + 7.8220397233963013e-01 -2.1992200613021851e-01 + <_> + + 0 -1 481 -9.6130996942520142e-02 + + -8.9456301927566528e-01 1.4680700004100800e-01 + <_> + + 0 -1 482 -1.1568999849259853e-02 + + 8.2714098691940308e-01 -2.0275600254535675e-01 + <_> + + 0 -1 483 1.8312999978661537e-02 + + 1.6367999836802483e-02 2.7306801080703735e-01 + <_> + + 0 -1 484 -3.4166000783443451e-02 + + 1.1307320594787598e+00 -1.8810899555683136e-01 + <_> + + 0 -1 485 -2.4476999416947365e-02 + + -5.7791298627853394e-01 1.5812499821186066e-01 + <_> + + 0 -1 486 4.8957001417875290e-02 + + -2.2564999759197235e-02 -1.6373280286788940e+00 + <_> + + 0 -1 487 -2.0702999085187912e-02 + + -5.4512101411819458e-01 2.4086999893188477e-01 + <_> + + 0 -1 488 -2.3002000525593758e-02 + + -1.2236540317535400e+00 -7.3440000414848328e-03 + <_> + + 0 -1 489 6.4585000276565552e-02 + + 1.4695599675178528e-01 -4.4967499375343323e-01 + <_> + + 0 -1 490 1.2666000053286552e-02 + + -2.7873900532722473e-01 4.3876600265502930e-01 + <_> + + 0 -1 491 -1.2002999894320965e-02 + + -2.4289099872112274e-01 2.5350099802017212e-01 + <_> + + 0 -1 492 -2.6443999260663986e-02 + + -8.5864800214767456e-01 2.6025999337434769e-02 + <_> + + 0 -1 493 -2.5547999888658524e-02 + + 6.9287902116775513e-01 -2.1160000469535589e-03 + <_> + + 0 -1 494 3.9115000516176224e-02 + + -1.6589100658893585e-01 1.5209139585494995e+00 + <_> + + 0 -1 495 -6.0330000706017017e-03 + + 4.3856900930404663e-01 -2.1613700687885284e-01 + <_> + + 0 -1 496 -3.3936999738216400e-02 + + -9.7998398542404175e-01 2.2133000195026398e-02 + <_> + 99 + -3.8700489997863770e+00 + + <_> + + 0 -1 497 4.0672998875379562e-02 + + -9.0474700927734375e-01 6.4410597085952759e-01 + <_> + + 0 -1 498 2.5609999895095825e-02 + + -7.9216998815536499e-01 5.7489997148513794e-01 + <_> + + 0 -1 499 1.9959500432014465e-01 + + -3.0099600553512573e-01 1.3143850564956665e+00 + <_> + + 0 -1 500 1.2404999695718288e-02 + + -8.9882999658584595e-01 2.9205799102783203e-01 + <_> + + 0 -1 501 3.9207998663187027e-02 + + -4.1955199837684631e-01 5.3463298082351685e-01 + <_> + + 0 -1 502 -3.0843999236822128e-02 + + 4.5793399214744568e-01 -4.4629099965095520e-01 + <_> + + 0 -1 503 -3.5523001104593277e-02 + + 9.1310501098632812e-01 -2.7373200654983521e-01 + <_> + + 0 -1 504 -6.1650000512599945e-02 + + -1.4697799682617188e+00 2.0364099740982056e-01 + <_> + + 0 -1 505 -1.1739999987185001e-02 + + -1.0482879877090454e+00 6.7801997065544128e-02 + <_> + + 0 -1 506 6.6933996975421906e-02 + + 2.9274499416351318e-01 -5.2282899618148804e-01 + <_> + + 0 -1 507 -2.0631000399589539e-02 + + -1.2855139970779419e+00 4.4550999999046326e-02 + <_> + + 0 -1 508 -2.2357000038027763e-02 + + -8.5753798484802246e-01 1.8434000015258789e-01 + <_> + + 0 -1 509 1.1500000255182385e-03 + + 1.6405500471591949e-01 -6.9125002622604370e-01 + <_> + + 0 -1 510 3.5872999578714371e-02 + + 1.5756499767303467e-01 -8.4262597560882568e-01 + <_> + + 0 -1 511 3.0659999698400497e-02 + + 2.1637000143527985e-02 -1.3634690046310425e+00 + <_> + + 0 -1 512 5.5559999309480190e-03 + + -1.6737000644207001e-01 2.5888401269912720e-01 + <_> + + 0 -1 513 -6.1160000041127205e-03 + + -9.7271800041198730e-01 6.6100001335144043e-02 + <_> + + 0 -1 514 -3.0316999182105064e-02 + + 9.8474198579788208e-01 -1.6448000445961952e-02 + <_> + + 0 -1 515 -9.7200004383921623e-03 + + 4.7604700922966003e-01 -3.2516700029373169e-01 + <_> + + 0 -1 516 -5.7126998901367188e-02 + + -9.5920699834823608e-01 1.9938200712203979e-01 + <_> + + 0 -1 517 4.0059997700154781e-03 + + -5.2612501382827759e-01 2.2428700327873230e-01 + <_> + + 0 -1 518 3.3734001219272614e-02 + + 1.7070099711418152e-01 -1.0737580060958862e+00 + <_> + + 0 -1 519 -3.4641999751329422e-02 + + -1.1343129873275757e+00 3.6540001630783081e-02 + <_> + + 0 -1 520 4.6923000365495682e-02 + + 2.5832301378250122e-01 -7.1535801887512207e-01 + <_> + + 0 -1 521 -8.7660001590847969e-03 + + 1.9640900194644928e-01 -5.3355097770690918e-01 + <_> + + 0 -1 522 6.5627999603748322e-02 + + -5.1194999366998672e-02 9.7610700130462646e-01 + <_> + + 0 -1 523 -4.4165000319480896e-02 + + 1.0631920099258423e+00 -2.3462599515914917e-01 + <_> + + 0 -1 524 1.7304999753832817e-02 + + -1.8582899868488312e-01 4.5889899134635925e-01 + <_> + + 0 -1 525 3.3135998994112015e-02 + + -2.9381999745965004e-02 -2.6651329994201660e+00 + <_> + + 0 -1 526 -2.1029999479651451e-02 + + 9.9979901313781738e-01 2.4937000125646591e-02 + <_> + + 0 -1 527 2.9783999547362328e-02 + + -2.9605999588966370e-02 -2.1695868968963623e+00 + <_> + + 0 -1 528 5.5291999131441116e-02 + + -7.5599999399855733e-04 7.4651998281478882e-01 + <_> + + 0 -1 529 -3.3597998321056366e-02 + + -1.5274159908294678e+00 1.1060000397264957e-02 + <_> + + 0 -1 530 1.9602999091148376e-02 + + 3.3574998378753662e-02 9.9526202678680420e-01 + <_> + + 0 -1 531 -2.0787000656127930e-02 + + 7.6612901687622070e-01 -2.4670800566673279e-01 + <_> + + 0 -1 532 3.2536000013351440e-02 + + 1.6263400018215179e-01 -6.1134302616119385e-01 + <_> + + 0 -1 533 -1.0788000188767910e-02 + + -9.7839701175689697e-01 2.8969999402761459e-02 + <_> + + 0 -1 534 -9.9560003727674484e-03 + + 4.6145799756050110e-01 -1.3510499894618988e-01 + <_> + + 0 -1 535 -3.7489999085664749e-03 + + 2.5458198785781860e-01 -5.1955598592758179e-01 + <_> + + 0 -1 536 -4.1779998689889908e-02 + + -8.0565100908279419e-01 1.5208500623703003e-01 + <_> + + 0 -1 537 -3.4221000969409943e-02 + + -1.3137799501419067e+00 -3.5800000187009573e-03 + <_> + + 0 -1 538 1.0130000300705433e-02 + + 2.0175799727439880e-01 -6.1339598894119263e-01 + <_> + + 0 -1 539 -8.9849002659320831e-02 + + 9.7632801532745361e-01 -2.0884799957275391e-01 + <_> + + 0 -1 540 2.6097999885678291e-02 + + -1.8807999789714813e-01 4.7705799341201782e-01 + <_> + + 0 -1 541 -3.7539999466389418e-03 + + -6.7980402708053589e-01 1.1288800090551376e-01 + <_> + + 0 -1 542 3.1973000615835190e-02 + + 1.8951700627803802e-01 -1.4967479705810547e+00 + <_> + + 0 -1 543 1.9332999363541603e-02 + + -2.3609900474548340e-01 8.1320500373840332e-01 + <_> + + 0 -1 544 1.9490000559017062e-03 + + 2.4830399453639984e-01 -6.9211997091770172e-02 + <_> + + 0 -1 545 -4.4146999716758728e-02 + + -1.0418920516967773e+00 4.8053000122308731e-02 + <_> + + 0 -1 546 -4.4681999832391739e-02 + + 5.1346302032470703e-01 -7.3799998499453068e-03 + <_> + + 0 -1 547 -1.0757499933242798e-01 + + 1.6202019453048706e+00 -1.8667599558830261e-01 + <_> + + 0 -1 548 -1.2846800684928894e-01 + + 2.9869480133056641e+00 9.5427997410297394e-02 + <_> + + 0 -1 549 -4.4757999479770660e-02 + + 6.0405302047729492e-01 -2.7058699727058411e-01 + <_> + + 0 -1 550 -4.3990999460220337e-02 + + -6.1790502071380615e-01 1.5997199714183807e-01 + <_> + + 0 -1 551 -1.2268999963998795e-01 + + 6.6327202320098877e-01 -2.3636999726295471e-01 + <_> + + 0 -1 552 -1.9982999190688133e-02 + + -1.1228660345077515e+00 1.9616700708866119e-01 + <_> + + 0 -1 553 -1.5527999959886074e-02 + + -1.0770269632339478e+00 2.0693000406026840e-02 + <_> + + 0 -1 554 -4.8971001058816910e-02 + + 8.1168299913406372e-01 -1.7252000048756599e-02 + <_> + + 0 -1 555 5.5975999683141708e-02 + + -2.2529000416398048e-02 -1.7356760501861572e+00 + <_> + + 0 -1 556 -9.8580000922083855e-03 + + 6.7881399393081665e-01 -5.8180000633001328e-02 + <_> + + 0 -1 557 1.3481000438332558e-02 + + 5.7847999036312103e-02 -7.7255302667617798e-01 + <_> + + 0 -1 558 6.5609999001026154e-03 + + -1.3146899640560150e-01 6.7055797576904297e-01 + <_> + + 0 -1 559 7.1149999275803566e-03 + + -3.7880599498748779e-01 3.0978998541831970e-01 + <_> + + 0 -1 560 4.8159998841583729e-03 + + -5.8470398187637329e-01 2.5602099299430847e-01 + <_> + + 0 -1 561 9.5319999381899834e-03 + + -3.0217000842094421e-01 4.1253298521041870e-01 + <_> + + 0 -1 562 -2.7474999427795410e-02 + + 5.9154701232910156e-01 1.7963999882340431e-02 + <_> + + 0 -1 563 -3.9519999176263809e-02 + + 9.6913498640060425e-01 -2.1020300686359406e-01 + <_> + + 0 -1 564 -3.0658999457955360e-02 + + 9.1155898571014404e-01 4.0550000965595245e-02 + <_> + + 0 -1 565 -1.4680000022053719e-03 + + -6.0489797592163086e-01 1.6960899531841278e-01 + <_> + + 0 -1 566 1.9077600538730621e-01 + + 4.3515000492334366e-02 8.1892901659011841e-01 + <_> + + 0 -1 567 5.1790000870823860e-03 + + -9.3617302179336548e-01 2.4937000125646591e-02 + <_> + + 0 -1 568 2.4126000702381134e-02 + + 1.8175500631332397e-01 -3.4185901284217834e-01 + <_> + + 0 -1 569 -2.6383999735116959e-02 + + -1.2912579774856567e+00 -3.4280000254511833e-03 + <_> + + 0 -1 570 5.4139997810125351e-03 + + -4.6291999518871307e-02 2.5269600749015808e-01 + <_> + + 0 -1 571 5.4216001182794571e-02 + + -1.2848000042140484e-02 -1.4304540157318115e+00 + <_> + + 0 -1 572 2.3799999326001853e-04 + + -2.6676699519157410e-01 3.3588299155235291e-01 + <_> + + 0 -1 573 1.5216999687254429e-02 + + -5.1367300748825073e-01 1.3005100190639496e-01 + <_> + + 0 -1 574 1.7007999122142792e-02 + + 4.1575899720191956e-01 -3.1241199374198914e-01 + <_> + + 0 -1 575 3.0496999621391296e-02 + + -2.4820999801158905e-01 7.0828497409820557e-01 + <_> + + 0 -1 576 6.5430002287030220e-03 + + -2.2637000679969788e-01 1.9184599816799164e-01 + <_> + + 0 -1 577 1.4163999259471893e-01 + + 6.5227001905441284e-02 -8.8809502124786377e-01 + <_> + + 0 -1 578 1.9338000565767288e-02 + + 1.8891200423240662e-01 -2.7397701144218445e-01 + <_> + + 0 -1 579 -1.7324000597000122e-02 + + -9.4866698980331421e-01 2.4196999147534370e-02 + <_> + + 0 -1 580 -6.2069999985396862e-03 + + 3.6938399076461792e-01 -1.7494900524616241e-01 + <_> + + 0 -1 581 -1.6109000891447067e-02 + + 9.6159499883651733e-01 -2.0005300641059875e-01 + <_> + + 0 -1 582 -1.0122500360012054e-01 + + -3.0699110031127930e+00 1.1363799870014191e-01 + <_> + + 0 -1 583 -7.5509999878704548e-03 + + 2.2921000421047211e-01 -4.5645099878311157e-01 + <_> + + 0 -1 584 4.4247999787330627e-02 + + -3.1599999056197703e-04 3.9225301146507263e-01 + <_> + + 0 -1 585 -1.1636000126600266e-01 + + 9.5233702659606934e-01 -2.0201599597930908e-01 + <_> + + 0 -1 586 4.7360002063214779e-03 + + -9.9177002906799316e-02 2.0370499789714813e-01 + <_> + + 0 -1 587 2.2459000349044800e-02 + + 8.7280003353953362e-03 -1.0217070579528809e+00 + <_> + + 0 -1 588 -1.2109000235795975e-02 + + 6.4812600612640381e-01 -9.0149000287055969e-02 + <_> + + 0 -1 589 5.6120000779628754e-02 + + -3.6759998649358749e-02 -1.9275590181350708e+00 + <_> + + 0 -1 590 -8.7379999458789825e-03 + + 6.9261300563812256e-01 -6.8374998867511749e-02 + <_> + + 0 -1 591 6.6399998031556606e-03 + + -4.0569800138473511e-01 1.8625700473785400e-01 + <_> + + 0 -1 592 -1.8131999298930168e-02 + + -6.4518201351165771e-01 2.1976399421691895e-01 + <_> + + 0 -1 593 -2.2718999534845352e-02 + + 9.7776198387145996e-01 -1.8654300272464752e-01 + <_> + + 0 -1 594 1.2705000117421150e-02 + + -1.0546600073575974e-01 3.7404099106788635e-01 + <_> + + 0 -1 595 -1.3682999648153782e-02 + + 6.1064100265502930e-01 -2.6881098747253418e-01 + <_> + 115 + -3.7160909175872803e+00 + + <_> + + 0 -1 596 3.1357999891042709e-02 + + -1.0183910131454468e+00 5.7528597116470337e-01 + <_> + + 0 -1 597 9.3050003051757812e-02 + + -4.1297501325607300e-01 1.0091199874877930e+00 + <_> + + 0 -1 598 2.5949999690055847e-02 + + -5.8587902784347534e-01 5.6606197357177734e-01 + <_> + + 0 -1 599 1.6472000628709793e-02 + + -9.2857497930526733e-01 3.0924499034881592e-01 + <_> + + 0 -1 600 -1.8779999809339643e-03 + + 1.1951000243425369e-01 -1.1180130243301392e+00 + <_> + + 0 -1 601 -9.0129999443888664e-03 + + -5.7849502563476562e-01 3.3154401183128357e-01 + <_> + + 0 -1 602 2.2547999396920204e-02 + + -3.8325101137161255e-01 5.2462202310562134e-01 + <_> + + 0 -1 603 -3.7780001759529114e-02 + + 1.1790670156478882e+00 -3.4166999161243439e-02 + <_> + + 0 -1 604 -5.3799999877810478e-03 + + -8.6265897750854492e-01 1.1867900192737579e-01 + <_> + + 0 -1 605 -2.3893000558018684e-02 + + -7.4950599670410156e-01 2.1011400222778320e-01 + <_> + + 0 -1 606 -2.6521999388933182e-02 + + 9.2128598690032959e-01 -2.8252801299095154e-01 + <_> + + 0 -1 607 1.2280000373721123e-02 + + 2.6662799715995789e-01 -7.0013600587844849e-01 + <_> + + 0 -1 608 9.6594996750354767e-02 + + -2.8453999757766724e-01 7.3168998956680298e-01 + <_> + + 0 -1 609 -2.7414999902248383e-02 + + -6.1492699384689331e-01 1.5576200187206268e-01 + <_> + + 0 -1 610 -1.5767000615596771e-02 + + 5.7551199197769165e-01 -3.4362199902534485e-01 + <_> + + 0 -1 611 -2.1100000012665987e-03 + + 3.2599699497222900e-01 -1.3008299469947815e-01 + <_> + + 0 -1 612 1.2006999924778938e-02 + + 8.9322999119758606e-02 -9.6025598049163818e-01 + <_> + + 0 -1 613 -1.5421999618411064e-02 + + 3.4449499845504761e-01 -4.6711999177932739e-01 + <_> + + 0 -1 614 -4.1579999960958958e-03 + + 2.3696300387382507e-01 -5.2563297748565674e-01 + <_> + + 0 -1 615 -2.1185999736189842e-02 + + -7.4267697334289551e-01 2.1702000498771667e-01 + <_> + + 0 -1 616 -1.7077000811696053e-02 + + -9.0471798181533813e-01 6.6012002527713776e-02 + <_> + + 0 -1 617 -4.0849998593330383e-02 + + -3.4446600079536438e-01 2.1503700315952301e-01 + <_> + + 0 -1 618 -8.1930002197623253e-03 + + -9.3388599157333374e-01 5.0471000373363495e-02 + <_> + + 0 -1 619 -1.9238000735640526e-02 + + -5.3203701972961426e-01 1.7240600287914276e-01 + <_> + + 0 -1 620 -4.4192001223564148e-02 + + 9.2075002193450928e-01 -2.2148500382900238e-01 + <_> + + 0 -1 621 -6.2392000108957291e-02 + + -7.1053802967071533e-01 1.8323899805545807e-01 + <_> + + 0 -1 622 -1.0079999919980764e-03 + + -8.7063097953796387e-01 5.5330000817775726e-02 + <_> + + 0 -1 623 2.3870000615715981e-02 + + -2.2854200005531311e-01 5.2415597438812256e-01 + <_> + + 0 -1 624 2.1391000598669052e-02 + + -3.0325898528099060e-01 5.5860602855682373e-01 + <_> + + 0 -1 625 2.0254999399185181e-02 + + 2.6901501417160034e-01 -7.0261800289154053e-01 + <_> + + 0 -1 626 -2.8772000223398209e-02 + + -1.1835030317306519e+00 4.6512000262737274e-02 + <_> + + 0 -1 627 3.4199999645352364e-03 + + -5.4652100801467896e-01 2.5962498784065247e-01 + <_> + + 0 -1 628 5.6983001530170441e-02 + + -2.6982900500297546e-01 5.8170700073242188e-01 + <_> + + 0 -1 629 -9.3892000615596771e-02 + + -9.1046398878097534e-01 1.9677700102329254e-01 + <_> + + 0 -1 630 1.7699999734759331e-02 + + -4.4003298878669739e-01 2.1349500119686127e-01 + <_> + + 0 -1 631 2.2844199836254120e-01 + + 2.3605000227689743e-02 7.7171599864959717e-01 + <_> + + 0 -1 632 -1.8287500739097595e-01 + + 7.9228597879409790e-01 -2.4644799530506134e-01 + <_> + + 0 -1 633 -6.9891996681690216e-02 + + 8.0267798900604248e-01 -3.6072000861167908e-02 + <_> + + 0 -1 634 1.5297000296413898e-02 + + -2.0072300732135773e-01 1.1030600070953369e+00 + <_> + + 0 -1 635 6.7500001750886440e-03 + + -4.5967999845743179e-02 7.2094500064849854e-01 + <_> + + 0 -1 636 -1.5983000397682190e-02 + + -9.0357202291488647e-01 4.4987998902797699e-02 + <_> + + 0 -1 637 1.3088000006973743e-02 + + 3.5297098755836487e-01 -3.7710601091384888e-01 + <_> + + 0 -1 638 1.3061000034213066e-02 + + -1.9583599269390106e-01 1.1198940277099609e+00 + <_> + + 0 -1 639 -3.9907000958919525e-02 + + -1.3998429775238037e+00 1.9145099818706512e-01 + <_> + + 0 -1 640 1.5026999637484550e-02 + + 2.3600000422447920e-03 -1.1611249446868896e+00 + <_> + + 0 -1 641 -2.0517999306321144e-02 + + -4.8908099532127380e-01 1.6743400692939758e-01 + <_> + + 0 -1 642 -2.2359000518918037e-02 + + -1.2202980518341064e+00 -1.1975999921560287e-02 + <_> + + 0 -1 643 -7.9150004312396049e-03 + + 3.7228098511695862e-01 -8.5063003003597260e-02 + <_> + + 0 -1 644 1.5258000232279301e-02 + + -2.9412600398063660e-01 5.9406399726867676e-01 + <_> + + 0 -1 645 -3.1665999442338943e-02 + + -1.4395569562911987e+00 1.3578799366950989e-01 + <_> + + 0 -1 646 -3.0773999169468880e-02 + + -2.2545371055603027e+00 -3.3971000462770462e-02 + <_> + + 0 -1 647 -1.5483000315725803e-02 + + 3.7700700759887695e-01 1.5847999602556229e-02 + <_> + + 0 -1 648 3.5167001187801361e-02 + + -2.9446101188659668e-01 5.3159099817276001e-01 + <_> + + 0 -1 649 -1.7906000837683678e-02 + + -9.9788200855255127e-01 1.6235999763011932e-01 + <_> + + 0 -1 650 -3.1799999997019768e-03 + + 4.7657001763582230e-02 -7.5249898433685303e-01 + <_> + + 0 -1 651 1.5720000490546227e-02 + + 1.4873799681663513e-01 -6.5375399589538574e-01 + <_> + + 0 -1 652 2.9864000156521797e-02 + + -1.4952000230550766e-02 -1.2275190353393555e+00 + <_> + + 0 -1 653 2.9899999499320984e-03 + + -1.4263699948787689e-01 4.3272799253463745e-01 + <_> + + 0 -1 654 8.4749996662139893e-02 + + -1.9280999898910522e-02 -1.1946409940719604e+00 + <_> + + 0 -1 655 -5.8724999427795410e-02 + + -1.7328219413757324e+00 1.4374700188636780e-01 + <_> + + 0 -1 656 4.4755998998880386e-02 + + -2.4140599370002747e-01 5.4019999504089355e-01 + <_> + + 0 -1 657 4.0369000285863876e-02 + + 5.7680001482367516e-03 5.6578099727630615e-01 + <_> + + 0 -1 658 3.7735998630523682e-02 + + 3.8180999457836151e-02 -7.9370397329330444e-01 + <_> + + 0 -1 659 6.0752999037504196e-02 + + 7.6453000307083130e-02 1.4813209772109985e+00 + <_> + + 0 -1 660 -1.9832000136375427e-02 + + -1.6971720457077026e+00 -2.7370000258088112e-02 + <_> + + 0 -1 661 -1.6592699289321899e-01 + + 6.2976002693176270e-01 3.1762998551130295e-02 + <_> + + 0 -1 662 6.9014996290206909e-02 + + -3.3463200926780701e-01 3.0076700448989868e-01 + <_> + + 0 -1 663 1.1358000338077545e-02 + + 2.2741499543190002e-01 -3.8224700093269348e-01 + <_> + + 0 -1 664 1.7000000225380063e-03 + + 1.9223800301551819e-01 -5.2735102176666260e-01 + <_> + + 0 -1 665 7.9769000411033630e-02 + + 9.1491997241973877e-02 2.1049048900604248e+00 + <_> + + 0 -1 666 -5.7144001126289368e-02 + + -1.7452130317687988e+00 -4.0910001844167709e-02 + <_> + + 0 -1 667 7.3830001056194305e-03 + + -2.4214799702167511e-01 3.5577800869941711e-01 + <_> + + 0 -1 668 -1.8040999770164490e-02 + + 1.1779999732971191e+00 -1.7676700651645660e-01 + <_> + + 0 -1 669 9.4503000378608704e-02 + + 1.3936099410057068e-01 -1.2993700504302979e+00 + <_> + + 0 -1 670 5.4210000671446323e-03 + + -5.4608601331710815e-01 1.3916400074958801e-01 + <_> + + 0 -1 671 7.0290002040565014e-03 + + -2.1597200632095337e-01 3.9258098602294922e-01 + <_> + + 0 -1 672 3.4515999257564545e-02 + + 6.3188999891281128e-02 -7.2108101844787598e-01 + <_> + + 0 -1 673 -5.1924999803304672e-02 + + 6.8667602539062500e-01 6.3272997736930847e-02 + <_> + + 0 -1 674 -6.9162003695964813e-02 + + 1.7411810159683228e+00 -1.6619299352169037e-01 + <_> + + 0 -1 675 -5.5229999125003815e-03 + + 3.0694699287414551e-01 -1.6662900149822235e-01 + <_> + + 0 -1 676 6.8599998950958252e-02 + + -2.1405400335788727e-01 7.3185002803802490e-01 + <_> + + 0 -1 677 -6.7038998007774353e-02 + + -7.9360598325729370e-01 2.0525799691677094e-01 + <_> + + 0 -1 678 -2.1005000919103622e-02 + + 3.7344399094581604e-01 -2.9618600010871887e-01 + <_> + + 0 -1 679 2.0278999581933022e-02 + + -1.5200000256299973e-02 4.0555301308631897e-01 + <_> + + 0 -1 680 -4.7107998281717300e-02 + + 1.2116849422454834e+00 -1.7464299499988556e-01 + <_> + + 0 -1 681 1.8768499791622162e-01 + + -2.2909000515937805e-02 6.9645798206329346e-01 + <_> + + 0 -1 682 -4.3228998780250549e-02 + + -1.0602480173110962e+00 -5.5599998449906707e-04 + <_> + + 0 -1 683 2.0004000514745712e-02 + + -3.2751001417636871e-02 5.3805100917816162e-01 + <_> + + 0 -1 684 8.0880001187324524e-03 + + 3.7548001855611801e-02 -7.4768900871276855e-01 + <_> + + 0 -1 685 2.7101000770926476e-02 + + -8.1790000200271606e-02 3.3387100696563721e-01 + <_> + + 0 -1 686 -9.1746002435684204e-02 + + -1.9213509559631348e+00 -3.8952998816967010e-02 + <_> + + 0 -1 687 -1.2454999610781670e-02 + + 4.8360601067543030e-01 1.8168000504374504e-02 + <_> + + 0 -1 688 1.4649000018835068e-02 + + -1.9906699657440186e-01 7.2815400362014771e-01 + <_> + + 0 -1 689 2.9101999476552010e-02 + + 1.9871099293231964e-01 -4.9216800928115845e-01 + <_> + + 0 -1 690 8.7799998000264168e-03 + + -1.9499599933624268e-01 7.7317398786544800e-01 + <_> + + 0 -1 691 -5.4740000516176224e-02 + + 1.8087190389633179e+00 6.8323001265525818e-02 + <_> + + 0 -1 692 -1.4798000454902649e-02 + + 7.8064900636672974e-01 -1.8709599971771240e-01 + <_> + + 0 -1 693 2.5012999773025513e-02 + + 1.5285299718379974e-01 -1.6021020412445068e+00 + <_> + + 0 -1 694 4.6548001468181610e-02 + + -1.6738200187683105e-01 1.1902060508728027e+00 + <_> + + 0 -1 695 1.7624000087380409e-02 + + -1.0285499691963196e-01 3.9175900816917419e-01 + <_> + + 0 -1 696 1.6319599747657776e-01 + + -3.5624001175165176e-02 -1.6098170280456543e+00 + <_> + + 0 -1 697 1.3137999922037125e-02 + + -5.6359000504016876e-02 5.4158902168273926e-01 + <_> + + 0 -1 698 -1.5665000304579735e-02 + + 2.8063100576400757e-01 -3.1708601117134094e-01 + <_> + + 0 -1 699 8.0554001033306122e-02 + + 1.2640400230884552e-01 -1.0297529697418213e+00 + <_> + + 0 -1 700 3.5363998264074326e-02 + + 2.0752999931573868e-02 -7.9105597734451294e-01 + <_> + + 0 -1 701 3.2986998558044434e-02 + + 1.9057099521160126e-01 -8.3839899301528931e-01 + <_> + + 0 -1 702 1.2195000424981117e-02 + + 7.3729000985622406e-02 -6.2780702114105225e-01 + <_> + + 0 -1 703 4.3065998703241348e-02 + + 4.7384999692440033e-02 1.5712939500808716e+00 + <_> + + 0 -1 704 3.0326999723911285e-02 + + -2.7314600348472595e-01 3.8572001457214355e-01 + <_> + + 0 -1 705 3.5493001341819763e-02 + + 5.4593998938798904e-02 5.2583402395248413e-01 + <_> + + 0 -1 706 -1.4596999622881413e-02 + + 3.8152599334716797e-01 -2.8332400321960449e-01 + <_> + + 0 -1 707 1.2606999836862087e-02 + + 1.5455099940299988e-01 -3.0501499772071838e-01 + <_> + + 0 -1 708 1.0172000154852867e-02 + + 2.3637000471353531e-02 -8.7217897176742554e-01 + <_> + + 0 -1 709 2.8843000531196594e-02 + + 1.6090999543666840e-01 -2.0277599990367889e-01 + <_> + + 0 -1 710 5.5100000463426113e-04 + + -6.1545401811599731e-01 8.0935999751091003e-02 + <_> + 127 + -3.5645289421081543e+00 + + <_> + + 0 -1 711 4.8344001173973083e-02 + + -8.4904599189758301e-01 5.6974399089813232e-01 + <_> + + 0 -1 712 3.2460000365972519e-02 + + -8.1417298316955566e-01 4.4781699776649475e-01 + <_> + + 0 -1 713 3.3339999616146088e-02 + + -3.6423799395561218e-01 6.7937397956848145e-01 + <_> + + 0 -1 714 6.4019998535513878e-03 + + -1.1885459423065186e+00 1.9238699972629547e-01 + <_> + + 0 -1 715 -5.6889997795224190e-03 + + 3.3085298538208008e-01 -7.1334099769592285e-01 + <_> + + 0 -1 716 1.2698000296950340e-02 + + -5.0990802049636841e-01 1.1376299709081650e-01 + <_> + + 0 -1 717 6.0549997724592686e-03 + + -1.0470550060272217e+00 2.0222599804401398e-01 + <_> + + 0 -1 718 2.6420000940561295e-03 + + -5.0559401512145996e-01 3.6441200971603394e-01 + <_> + + 0 -1 719 -1.6925999894738197e-02 + + -9.9541902542114258e-01 1.2602199614048004e-01 + <_> + + 0 -1 720 2.8235999867320061e-02 + + -9.4137996435165405e-02 5.7780402898788452e-01 + <_> + + 0 -1 721 1.0428999550640583e-02 + + 2.3272900283336639e-01 -5.2569699287414551e-01 + <_> + + 0 -1 722 9.8860003054141998e-03 + + -1.0316299647092819e-01 4.7657600045204163e-01 + <_> + + 0 -1 723 2.6015000417828560e-02 + + -1.0920000495389104e-03 -1.5581729412078857e+00 + <_> + + 0 -1 724 -2.5537999346852303e-02 + + -6.5451401472091675e-01 1.8843199312686920e-01 + <_> + + 0 -1 725 -3.5310001112520695e-03 + + 2.8140598535537720e-01 -4.4575300812721252e-01 + <_> + + 0 -1 726 9.2449998483061790e-03 + + 1.5612000226974487e-01 -2.1370999515056610e-01 + <_> + + 0 -1 727 2.1030999720096588e-02 + + -2.9170298576354980e-01 5.2234101295471191e-01 + <_> + + 0 -1 728 -5.1063001155853271e-02 + + 1.3661290407180786e+00 3.0465999618172646e-02 + <_> + + 0 -1 729 -6.2330000102519989e-02 + + 1.2207020521163940e+00 -2.2434400022029877e-01 + <_> + + 0 -1 730 -3.2963000237941742e-02 + + -8.2016801834106445e-01 1.4531899988651276e-01 + <_> + + 0 -1 731 -3.7418000400066376e-02 + + -1.2218099832534790e+00 1.9448999315500259e-02 + <_> + + 0 -1 732 1.2402799725532532e-01 + + 1.2082300335168839e-01 -9.8729300498962402e-01 + <_> + + 0 -1 733 -8.9229997247457504e-03 + + -1.1688489913940430e+00 2.1105000749230385e-02 + <_> + + 0 -1 734 -5.9879999607801437e-02 + + -1.0689330101013184e+00 1.9860200583934784e-01 + <_> + + 0 -1 735 6.2620001845061779e-03 + + -3.6229598522186279e-01 3.8000801205635071e-01 + <_> + + 0 -1 736 -1.7673000693321228e-02 + + 4.9094098806381226e-01 -1.4606699347496033e-01 + <_> + + 0 -1 737 1.7579000443220139e-02 + + 5.8728098869323730e-01 -2.7774399518966675e-01 + <_> + + 0 -1 738 5.1560001447796822e-03 + + -7.5194999575614929e-02 6.0193097591400146e-01 + <_> + + 0 -1 739 -1.0599999688565731e-02 + + 2.7637401223182678e-01 -3.7794300913810730e-01 + <_> + + 0 -1 740 2.0884099602699280e-01 + + -5.3599998354911804e-03 1.0317809581756592e+00 + <_> + + 0 -1 741 -2.6412999257445335e-02 + + 8.2336401939392090e-01 -2.2480599582195282e-01 + <_> + + 0 -1 742 5.8892000466585159e-02 + + 1.3098299503326416e-01 -1.1853699684143066e+00 + <_> + + 0 -1 743 -1.1579000391066074e-02 + + -9.0667802095413208e-01 4.4126998633146286e-02 + <_> + + 0 -1 744 4.5988000929355621e-02 + + 1.0143999941647053e-02 1.0740900039672852e+00 + <_> + + 0 -1 745 -2.2838000208139420e-02 + + 1.7791990041732788e+00 -1.7315499484539032e-01 + <_> + + 0 -1 746 -8.1709995865821838e-03 + + 5.7386302947998047e-01 -7.4106000363826752e-02 + <_> + + 0 -1 747 3.5359999164938927e-03 + + -3.2072898745536804e-01 4.0182501077651978e-01 + <_> + + 0 -1 748 4.9444999545812607e-02 + + 1.9288000464439392e-01 -1.2166700363159180e+00 + <_> + + 0 -1 749 3.5139999818056822e-03 + + 6.9568000733852386e-02 -7.1323698759078979e-01 + <_> + + 0 -1 750 -3.0996000394225121e-02 + + -3.8862198591232300e-01 1.8098799884319305e-01 + <_> + + 0 -1 751 8.6452998220920563e-02 + + -2.5792999193072319e-02 -1.5453219413757324e+00 + <_> + + 0 -1 752 -1.3652600347995758e-01 + + -1.9199420213699341e+00 1.6613300144672394e-01 + <_> + + 0 -1 753 -5.7689999230206013e-03 + + -1.2822589874267578e+00 -1.5907999128103256e-02 + <_> + + 0 -1 754 -1.7899999395012856e-02 + + -4.0409898757934570e-01 2.3591600358486176e-01 + <_> + + 0 -1 755 -1.9969999790191650e-02 + + -7.2891902923583984e-01 5.6235000491142273e-02 + <_> + + 0 -1 756 -5.7493001222610474e-02 + + 5.7830798625946045e-01 -1.5796000137925148e-02 + <_> + + 0 -1 757 -8.3056002855300903e-02 + + 9.1511601209640503e-01 -2.1121400594711304e-01 + <_> + + 0 -1 758 -5.3771000355482101e-02 + + -5.1931297779083252e-01 1.8576000630855560e-01 + <_> + + 0 -1 759 -8.3670001477003098e-03 + + 2.4109700322151184e-01 -3.9648601412773132e-01 + <_> + + 0 -1 760 5.5406998842954636e-02 + + 1.6771200299263000e-01 -2.5664970874786377e+00 + <_> + + 0 -1 761 -6.7180998623371124e-02 + + -1.3658570051193237e+00 -1.4232000336050987e-02 + <_> + + 0 -1 762 -2.3900000378489494e-02 + + -1.7084569931030273e+00 1.6507799923419952e-01 + <_> + + 0 -1 763 5.5949999950826168e-03 + + -3.1373998522758484e-01 3.2837900519371033e-01 + <_> + + 0 -1 764 2.1294999867677689e-02 + + 1.4953400194644928e-01 -4.8579800128936768e-01 + <_> + + 0 -1 765 -2.4613000452518463e-02 + + 7.4346399307250977e-01 -2.2305199503898621e-01 + <_> + + 0 -1 766 -1.9626000896096230e-02 + + -4.0918299555778503e-01 1.8893200159072876e-01 + <_> + + 0 -1 767 -5.3266000002622604e-02 + + 8.1381601095199585e-01 -2.0853699743747711e-01 + <_> + + 0 -1 768 7.1290000341832638e-03 + + 3.2996100187301636e-01 -5.9937399625778198e-01 + <_> + + 0 -1 769 -2.2486999630928040e-02 + + -1.2551610469818115e+00 -2.0413000136613846e-02 + <_> + + 0 -1 770 -8.2310996949672699e-02 + + 1.3821430206298828e+00 5.9308998286724091e-02 + <_> + + 0 -1 771 1.3097000122070312e-01 + + -3.5843998193740845e-02 -1.5396369695663452e+00 + <_> + + 0 -1 772 1.4293000102043152e-02 + + -1.8475200235843658e-01 3.7455001473426819e-01 + <_> + + 0 -1 773 6.3479999080300331e-03 + + -4.4901099801063538e-01 1.3876999914646149e-01 + <_> + + 0 -1 774 -4.6055000275373459e-02 + + 6.7832601070404053e-01 -1.7071999609470367e-02 + <_> + + 0 -1 775 5.7693999260663986e-02 + + -1.1955999769270420e-02 -1.2261159420013428e+00 + <_> + + 0 -1 776 -6.0609998181462288e-03 + + 3.3958598971366882e-01 6.2800000887364149e-04 + <_> + + 0 -1 777 -5.2163001149892807e-02 + + -1.0621069669723511e+00 -1.3779999688267708e-02 + <_> + + 0 -1 778 4.6572998166084290e-02 + + 1.4538800716400146e-01 -1.2384550571441650e+00 + <_> + + 0 -1 779 7.5309998355805874e-03 + + -2.4467700719833374e-01 5.1377099752426147e-01 + <_> + + 0 -1 780 2.1615000441670418e-02 + + 1.3072599470615387e-01 -7.0996797084808350e-01 + <_> + + 0 -1 781 -1.7864000052213669e-02 + + -1.0474660396575928e+00 4.9599999329075217e-04 + <_> + + 0 -1 782 -3.7195000797510147e-02 + + -1.5126730203628540e+00 1.4801399409770966e-01 + <_> + + 0 -1 783 -3.1100001069717109e-04 + + 1.3971500098705292e-01 -4.6867498755455017e-01 + <_> + + 0 -1 784 2.5042999535799026e-02 + + 2.8632000088691711e-01 -4.1794699430465698e-01 + <_> + + 0 -1 785 9.3449996784329414e-03 + + -2.7336201071739197e-01 4.3444699048995972e-01 + <_> + + 0 -1 786 3.2363999634981155e-02 + + 1.8438899517059326e-01 -9.5019298791885376e-01 + <_> + + 0 -1 787 -6.2299999408423901e-03 + + 3.2581999897956848e-01 -3.0815601348876953e-01 + <_> + + 0 -1 788 5.1488999277353287e-02 + + 1.1416000127792358e-01 -1.9795479774475098e+00 + <_> + + 0 -1 789 -2.6449000462889671e-02 + + -1.1067299842834473e+00 -8.5519999265670776e-03 + <_> + + 0 -1 790 -1.5420000068843365e-02 + + 8.0138701200485229e-01 -3.2035000622272491e-02 + <_> + + 0 -1 791 1.9456999376416206e-02 + + -2.6449498534202576e-01 3.8753899931907654e-01 + <_> + + 0 -1 792 3.3620998263359070e-02 + + 1.6052000224590302e-02 5.8840900659561157e-01 + <_> + + 0 -1 793 2.8906000778079033e-02 + + 1.5216000378131866e-02 -9.4723600149154663e-01 + <_> + + 0 -1 794 2.0300000323913991e-04 + + -3.0766001343727112e-01 2.1235899627208710e-01 + <_> + + 0 -1 795 -4.9141999334096909e-02 + + -1.6058609485626221e+00 -3.1094999983906746e-02 + <_> + + 0 -1 796 7.6425999402999878e-02 + + 7.4758999049663544e-02 1.1639410257339478e+00 + <_> + + 0 -1 797 2.3897999897599220e-02 + + -6.4320000819861889e-03 -1.1150749921798706e+00 + <_> + + 0 -1 798 3.8970001041889191e-03 + + -2.4105699360370636e-01 2.0858900249004364e-01 + <_> + + 0 -1 799 -8.9445002377033234e-02 + + 1.9157789945602417e+00 -1.5721100568771362e-01 + <_> + + 0 -1 800 -1.5008999966084957e-02 + + -2.5174099206924438e-01 1.8179899454116821e-01 + <_> + + 0 -1 801 -1.1145999655127525e-02 + + -6.9349497556686401e-01 4.4927999377250671e-02 + <_> + + 0 -1 802 9.4578996300697327e-02 + + 1.8102100491523743e-01 -7.4978601932525635e-01 + <_> + + 0 -1 803 5.5038899183273315e-01 + + -3.0974000692367554e-02 -1.6746139526367188e+00 + <_> + + 0 -1 804 4.1381001472473145e-02 + + 6.3910000026226044e-02 7.6561200618743896e-01 + <_> + + 0 -1 805 2.4771999567747116e-02 + + 1.1380000039935112e-02 -8.8559401035308838e-01 + <_> + + 0 -1 806 5.0999000668525696e-02 + + 1.4890299737453461e-01 -2.4634211063385010e+00 + <_> + + 0 -1 807 -1.6893999651074409e-02 + + 3.8870999217033386e-01 -2.9880300164222717e-01 + <_> + + 0 -1 808 -1.2162300199270248e-01 + + -1.5542800426483154e+00 1.6300800442695618e-01 + <_> + + 0 -1 809 -3.6049999762326479e-03 + + 2.1842800080776215e-01 -3.7312099337577820e-01 + <_> + + 0 -1 810 1.1575400084257126e-01 + + -4.7061000019311905e-02 5.9403699636459351e-01 + <_> + + 0 -1 811 3.6903999745845795e-02 + + -2.5508600473403931e-01 5.5397301912307739e-01 + <_> + + 0 -1 812 1.1483999900519848e-02 + + -1.8129499256610870e-01 4.0682798624038696e-01 + <_> + + 0 -1 813 -2.0233999937772751e-02 + + 5.4311197996139526e-01 -2.3822399973869324e-01 + <_> + + 0 -1 814 -2.8765000402927399e-02 + + -6.9172298908233643e-01 1.5943300724029541e-01 + <_> + + 0 -1 815 -5.8320001699030399e-03 + + 2.9447799921035767e-01 -3.4005999565124512e-01 + <_> + + 0 -1 816 -5.5468998849391937e-02 + + 9.2200797796249390e-01 9.4093002378940582e-02 + <_> + + 0 -1 817 -1.4801000244915485e-02 + + -7.9539698362350464e-01 3.1521998345851898e-02 + <_> + + 0 -1 818 -7.0940000005066395e-03 + + 3.3096000552177429e-01 -5.0886999815702438e-02 + <_> + + 0 -1 819 -4.5124001801013947e-02 + + -1.3719749450683594e+00 -2.1408999338746071e-02 + <_> + + 0 -1 820 6.4377002418041229e-02 + + 6.3901998102664948e-02 9.1478300094604492e-01 + <_> + + 0 -1 821 -1.4727000147104263e-02 + + 3.6050599813461304e-01 -2.8614500164985657e-01 + <_> + + 0 -1 822 4.5007001608610153e-02 + + -1.5619699656963348e-01 5.3160297870635986e-01 + <_> + + 0 -1 823 -1.1330000124871731e-03 + + 1.3422900438308716e-01 -4.4358900189399719e-01 + <_> + + 0 -1 824 4.9451000988483429e-02 + + 1.0571800172328949e-01 -2.5589139461517334e+00 + <_> + + 0 -1 825 2.9102999716997147e-02 + + -1.0088000446557999e-02 -1.1073939800262451e+00 + <_> + + 0 -1 826 3.4786000847816467e-02 + + -2.7719999197870493e-03 5.6700998544692993e-01 + <_> + + 0 -1 827 -6.1309998854994774e-03 + + -4.6889400482177734e-01 1.2636399269104004e-01 + <_> + + 0 -1 828 1.5525000169873238e-02 + + -8.4279999136924744e-03 8.7469202280044556e-01 + <_> + + 0 -1 829 2.9249999206513166e-03 + + -3.4434300661087036e-01 2.0851600170135498e-01 + <_> + + 0 -1 830 -5.3571000695228577e-02 + + 1.4982949495315552e+00 5.7328000664710999e-02 + <_> + + 0 -1 831 -1.9217999652028084e-02 + + -9.9234098196029663e-01 -9.3919998034834862e-03 + <_> + + 0 -1 832 -5.5282998830080032e-02 + + -5.7682299613952637e-01 1.6860599815845490e-01 + <_> + + 0 -1 833 5.6336000561714172e-02 + + -3.3775001764297485e-02 -1.3889650106430054e+00 + <_> + + 0 -1 834 -2.3824000731110573e-02 + + 4.0182098746299744e-01 1.8360000103712082e-03 + <_> + + 0 -1 835 1.7810000572353601e-03 + + 1.8145999312400818e-01 -4.1743400692939758e-01 + <_> + + 0 -1 836 -3.7689000368118286e-02 + + 5.4683101177215576e-01 1.8219999969005585e-02 + <_> + + 0 -1 837 -2.4144999682903290e-02 + + 6.8352097272872925e-01 -1.9650200009346008e-01 + <_> + 135 + -3.7025990486145020e+00 + + <_> + + 0 -1 838 2.7444999665021896e-02 + + -8.9984202384948730e-01 5.1876497268676758e-01 + <_> + + 0 -1 839 1.1554100364446640e-01 + + -5.6524401903152466e-01 7.0551300048828125e-01 + <_> + + 0 -1 840 -2.2297000512480736e-02 + + 3.6079999804496765e-01 -6.6864597797393799e-01 + <_> + + 0 -1 841 1.3325000181794167e-02 + + -5.5573397874832153e-01 3.5789999365806580e-01 + <_> + + 0 -1 842 -3.8060001097619534e-03 + + -1.0713000297546387e+00 1.8850000202655792e-01 + <_> + + 0 -1 843 -2.6819999329745770e-03 + + -7.1584302186965942e-01 2.6344498991966248e-01 + <_> + + 0 -1 844 3.3819999080151320e-03 + + -4.6930798888206482e-01 2.6658400893211365e-01 + <_> + + 0 -1 845 3.7643000483512878e-02 + + 2.1098700165748596e-01 -1.0804339647293091e+00 + <_> + + 0 -1 846 -1.3861999846994877e-02 + + 6.6912001371383667e-01 -2.7942800521850586e-01 + <_> + + 0 -1 847 -2.7350001037120819e-03 + + -9.5332300662994385e-01 2.4051299691200256e-01 + <_> + + 0 -1 848 -3.8336999714374542e-02 + + 8.1432801485061646e-01 -2.4919399619102478e-01 + <_> + + 0 -1 849 -3.4697998315095901e-02 + + 1.2330100536346436e+00 6.8600000813603401e-03 + <_> + + 0 -1 850 2.3360999301075935e-02 + + -3.0794700980186462e-01 7.0714497566223145e-01 + <_> + + 0 -1 851 3.5057999193668365e-02 + + 2.1205900609493256e-01 -1.4399830102920532e+00 + <_> + + 0 -1 852 -1.3256999664008617e-02 + + -9.0260702371597290e-01 4.8610001802444458e-02 + <_> + + 0 -1 853 1.2740000151097775e-02 + + 2.2655199468135834e-01 -4.4643801450729370e-01 + <_> + + 0 -1 854 3.6400000099092722e-03 + + -3.9817899465560913e-01 3.4665399789810181e-01 + <_> + + 0 -1 855 1.0064700245857239e-01 + + 1.8383599817752838e-01 -1.3410769701004028e+00 + <_> + + 0 -1 856 0. + + 1.5536400675773621e-01 -5.1582497358322144e-01 + <_> + + 0 -1 857 1.1708999983966351e-02 + + 2.1651400625705719e-01 -7.2705197334289551e-01 + <_> + + 0 -1 858 -3.5964999347925186e-02 + + -1.4789500236511230e+00 -2.4317000061273575e-02 + <_> + + 0 -1 859 -2.1236000582575798e-02 + + -1.6844099760055542e-01 1.9526599347591400e-01 + <_> + + 0 -1 860 1.4874000102281570e-02 + + 3.7335999310016632e-02 -8.7557297945022583e-01 + <_> + + 0 -1 861 -5.1409997977316380e-03 + + 3.3466500043869019e-01 -2.4109700322151184e-01 + <_> + + 0 -1 862 2.3450000211596489e-02 + + 5.5320002138614655e-03 -1.2509720325469971e+00 + <_> + + 0 -1 863 -2.5062000378966331e-02 + + 4.5212399959564209e-01 -8.4469996392726898e-02 + <_> + + 0 -1 864 -7.7400001464411616e-04 + + 1.5249900519847870e-01 -4.8486500978469849e-01 + <_> + + 0 -1 865 -4.0483999997377396e-02 + + -1.3024920225143433e+00 1.7983500659465790e-01 + <_> + + 0 -1 866 2.8170999139547348e-02 + + -2.4410900473594666e-01 6.2271100282669067e-01 + <_> + + 0 -1 867 4.5692998915910721e-02 + + 2.8122000396251678e-02 9.2394399642944336e-01 + <_> + + 0 -1 868 3.9707001298666000e-02 + + -2.2332799434661865e-01 7.7674001455307007e-01 + <_> + + 0 -1 869 5.0517000257968903e-02 + + 2.0319999754428864e-01 -1.0895930528640747e+00 + <_> + + 0 -1 870 -1.7266999930143356e-02 + + 6.8598401546478271e-01 -2.3304499685764313e-01 + <_> + + 0 -1 871 8.0186001956462860e-02 + + -1.0292000137269497e-02 6.1881101131439209e-01 + <_> + + 0 -1 872 9.7676001489162445e-02 + + -2.0070299506187439e-01 1.0088349580764771e+00 + <_> + + 0 -1 873 -1.5572000294923782e-02 + + 4.7615298628807068e-01 4.5623999089002609e-02 + <_> + + 0 -1 874 -1.5305000357329845e-02 + + -1.1077369451522827e+00 4.5239999890327454e-03 + <_> + + 0 -1 875 -1.6485000029206276e-02 + + 1.0152939558029175e+00 1.6327999532222748e-02 + <_> + + 0 -1 876 -2.6141999289393425e-02 + + 4.1723299026489258e-01 -2.8645500540733337e-01 + <_> + + 0 -1 877 8.8679995387792587e-03 + + 2.1404999494552612e-01 -1.6772800683975220e-01 + <_> + + 0 -1 878 -2.6886999607086182e-02 + + -1.1564220190048218e+00 -1.0324000380933285e-02 + <_> + + 0 -1 879 7.7789998613297939e-03 + + 3.5359498858451843e-01 -2.9611301422119141e-01 + <_> + + 0 -1 880 -1.5974000096321106e-02 + + -1.5374109745025635e+00 -2.9958000406622887e-02 + <_> + + 0 -1 881 2.0866999402642250e-02 + + 2.0244100689888000e-01 -7.1270197629928589e-01 + <_> + + 0 -1 882 8.5482001304626465e-02 + + -2.5932999327778816e-02 -1.5156569480895996e+00 + <_> + + 0 -1 883 2.3872999474406242e-02 + + 1.6803400218486786e-01 -3.8806200027465820e-01 + <_> + + 0 -1 884 -3.9105001837015152e-02 + + -1.1958349943161011e+00 -2.0361000671982765e-02 + <_> + + 0 -1 885 -7.7946998178958893e-02 + + -1.0898950099945068e+00 1.4530299603939056e-01 + <_> + + 0 -1 886 -1.6876000910997391e-02 + + 2.8049701452255249e-01 -4.1336300969123840e-01 + <_> + + 0 -1 887 1.1875600367784500e-01 + + -4.3490998446941376e-02 4.1263699531555176e-01 + <_> + + 0 -1 888 1.5624199807643890e-01 + + -2.6429599523544312e-01 5.5127799510955811e-01 + <_> + + 0 -1 889 -4.5908000320196152e-02 + + 6.0189199447631836e-01 1.8921000882983208e-02 + <_> + + 0 -1 890 -1.0309999808669090e-02 + + 3.8152998685836792e-01 -2.9507899284362793e-01 + <_> + + 0 -1 891 9.5769003033638000e-02 + + 1.3246500492095947e-01 -4.6266800165176392e-01 + <_> + + 0 -1 892 1.3686999678611755e-02 + + 1.1738699674606323e-01 -5.1664102077484131e-01 + <_> + + 0 -1 893 2.3990001063793898e-03 + + -3.4007599949836731e-01 2.0953500270843506e-01 + <_> + + 0 -1 894 3.3264998346567154e-02 + + -1.7052799463272095e-01 1.4366799592971802e+00 + <_> + + 0 -1 895 -3.3206000924110413e-02 + + 6.1295700073242188e-01 -4.1549999266862869e-02 + <_> + + 0 -1 896 2.7979998849332333e-03 + + -4.8554301261901855e-01 1.3372699916362762e-01 + <_> + + 0 -1 897 -6.5792001783847809e-02 + + -4.0257668495178223e+00 1.0876700282096863e-01 + <_> + + 0 -1 898 2.1430000197142363e-03 + + -3.9179998636245728e-01 2.2427099943161011e-01 + <_> + + 0 -1 899 2.2363999858498573e-02 + + -8.6429998278617859e-02 3.7785199284553528e-01 + <_> + + 0 -1 900 -5.7410001754760742e-02 + + 1.1454069614410400e+00 -1.9736599922180176e-01 + <_> + + 0 -1 901 6.6550001502037048e-03 + + -2.1105000749230385e-02 5.8453398942947388e-01 + <_> + + 0 -1 902 1.2326999567449093e-02 + + 3.7817001342773438e-02 -6.6987001895904541e-01 + <_> + + 0 -1 903 -8.1869997084140778e-03 + + 5.6366002559661865e-01 -7.6877996325492859e-02 + <_> + + 0 -1 904 3.6681000143289566e-02 + + -1.7343300580978394e-01 1.1670149564743042e+00 + <_> + + 0 -1 905 -4.0220400691032410e-01 + + 1.2640819549560547e+00 4.3398998677730560e-02 + <_> + + 0 -1 906 -2.2126000374555588e-02 + + 6.6978102922439575e-01 -2.1605299413204193e-01 + <_> + + 0 -1 907 -1.3156999833881855e-02 + + -4.1198599338531494e-01 2.0215000212192535e-01 + <_> + + 0 -1 908 -1.2860000133514404e-02 + + -9.1582697629928589e-01 3.9232999086380005e-02 + <_> + + 0 -1 909 2.1627999842166901e-02 + + 3.8719999138265848e-03 3.5668200254440308e-01 + <_> + + 0 -1 910 1.1896000243723392e-02 + + -3.7303900718688965e-01 1.9235099852085114e-01 + <_> + + 0 -1 911 -1.9548999145627022e-02 + + -4.2374899983406067e-01 2.4429599940776825e-01 + <_> + + 0 -1 912 6.4444996416568756e-02 + + -1.6558900475502014e-01 1.2697030305862427e+00 + <_> + + 0 -1 913 1.0898499935865402e-01 + + 1.4894300699234009e-01 -2.1534640789031982e+00 + <_> + + 0 -1 914 -3.4077998250722885e-02 + + 1.3779460191726685e+00 -1.6198499500751495e-01 + <_> + + 0 -1 915 -3.7489999085664749e-03 + + -3.3828601241111755e-01 2.1152900159358978e-01 + <_> + + 0 -1 916 -1.0971999727189541e-02 + + 7.6517897844314575e-01 -1.9692599773406982e-01 + <_> + + 0 -1 917 -1.1485000140964985e-02 + + -6.9271200895309448e-01 2.1657100319862366e-01 + <_> + + 0 -1 918 2.5984000414609909e-02 + + -1.1983999982476234e-02 -9.9697297811508179e-01 + <_> + + 0 -1 919 4.2159999720752239e-03 + + -1.0205700248479843e-01 4.8884400725364685e-01 + <_> + + 0 -1 920 -4.7697000205516815e-02 + + 1.0666010379791260e+00 -1.7576299607753754e-01 + <_> + + 0 -1 921 4.0300001273863018e-04 + + 1.8524800240993500e-01 -7.4790000915527344e-01 + <_> + + 0 -1 922 1.1539600044488907e-01 + + -2.2019700706005096e-01 5.4509997367858887e-01 + <_> + + 0 -1 923 1.6021000221371651e-02 + + 2.5487500429153442e-01 -5.0740098953247070e-01 + <_> + + 0 -1 924 5.6632000952959061e-02 + + -1.1256000027060509e-02 -9.5968097448348999e-01 + <_> + + 0 -1 925 -1.0726000182330608e-02 + + -2.8544700145721436e-01 1.6994799673557281e-01 + <_> + + 0 -1 926 1.2420000135898590e-01 + + -3.6139998584985733e-02 -1.3132710456848145e+00 + <_> + + 0 -1 927 -5.3799999877810478e-03 + + 3.3092701435089111e-01 1.3307999819517136e-02 + <_> + + 0 -1 928 1.1908000335097313e-02 + + -3.4830299019813538e-01 2.4041900038719177e-01 + <_> + + 0 -1 929 -4.3007999658584595e-02 + + -1.4390469789505005e+00 1.5599599480628967e-01 + <_> + + 0 -1 930 -3.3149998635053635e-02 + + -1.1805850267410278e+00 -1.2347999960184097e-02 + <_> + + 0 -1 931 -2.1341999992728233e-02 + + 2.2119441032409668e+00 6.2737002968788147e-02 + <_> + + 0 -1 932 -1.2218999676406384e-02 + + -1.8709750175476074e+00 -4.5499999076128006e-02 + <_> + + 0 -1 933 -1.6860999166965485e-02 + + -7.6912701129913330e-01 1.5330000221729279e-01 + <_> + + 0 -1 934 -2.4999999441206455e-03 + + -6.2987399101257324e-01 5.1600001752376556e-02 + <_> + + 0 -1 935 -4.5037999749183655e-02 + + 8.5428899526596069e-01 6.2600001692771912e-03 + <_> + + 0 -1 936 3.9057999849319458e-02 + + -3.2458998262882233e-02 -1.3325669765472412e+00 + <_> + + 0 -1 937 6.6720000468194485e-03 + + -1.9423599541187286e-01 3.7328699231147766e-01 + <_> + + 0 -1 938 -1.6361000016331673e-02 + + 2.0605869293212891e+00 -1.5042699873447418e-01 + <_> + + 0 -1 939 6.1719999648630619e-03 + + -1.1610999703407288e-01 2.5455400347709656e-01 + <_> + + 0 -1 940 4.5722000300884247e-02 + + -1.6340000554919243e-02 -1.0449140071868896e+00 + <_> + + 0 -1 941 4.1209999471902847e-03 + + -4.1997998952865601e-02 3.9680999517440796e-01 + <_> + + 0 -1 942 -1.7800000205170363e-04 + + -6.6422599554061890e-01 3.3443000167608261e-02 + <_> + + 0 -1 943 7.1109998971223831e-03 + + -5.8231998234987259e-02 3.7857300043106079e-01 + <_> + + 0 -1 944 -4.9864001572132111e-02 + + 6.1019402742385864e-01 -2.1005700528621674e-01 + <_> + + 0 -1 945 -2.5011999532580376e-02 + + -5.7100099325180054e-01 1.7848399281501770e-01 + <_> + + 0 -1 946 3.0939999967813492e-02 + + 5.6363001465797424e-02 -6.4731001853942871e-01 + <_> + + 0 -1 947 4.6271000057458878e-02 + + 1.7482399940490723e-01 -9.8909401893615723e-01 + <_> + + 0 -1 948 -3.1870000530034304e-03 + + -6.6804802417755127e-01 3.2267000526189804e-02 + <_> + + 0 -1 949 -2.4351999163627625e-02 + + 2.9444900155067444e-01 -1.3599999947473407e-03 + <_> + + 0 -1 950 1.1974000371992588e-02 + + -2.8345099091529846e-01 4.7171199321746826e-01 + <_> + + 0 -1 951 1.3070000335574150e-02 + + -1.0834600031375885e-01 5.7193297147750854e-01 + <_> + + 0 -1 952 5.9163000434637070e-02 + + -5.0939001142978668e-02 -1.9059720039367676e+00 + <_> + + 0 -1 953 -4.1094999760389328e-02 + + 4.5104598999023438e-01 -9.7599998116493225e-03 + <_> + + 0 -1 954 -8.3989001810550690e-02 + + -2.0349199771881104e+00 -5.1019001752138138e-02 + <_> + + 0 -1 955 4.4619001448154449e-02 + + 1.7041100561618805e-01 -1.2278720140457153e+00 + <_> + + 0 -1 956 2.4419000372290611e-02 + + -2.1796999499201775e-02 -1.0822949409484863e+00 + <_> + + 0 -1 957 -4.3870001100003719e-03 + + 3.0466699600219727e-01 -3.7066599726676941e-01 + <_> + + 0 -1 958 2.4607999250292778e-02 + + -3.1169500946998596e-01 2.3657299578189850e-01 + <_> + + 0 -1 959 -8.5182003676891327e-02 + + -1.7982350587844849e+00 1.5254299342632294e-01 + <_> + + 0 -1 960 2.1844999864697456e-02 + + -5.1888000220060349e-02 -1.9017189741134644e+00 + <_> + + 0 -1 961 -1.6829000785946846e-02 + + 2.1025900542736053e-01 2.1656999364495277e-02 + <_> + + 0 -1 962 3.2547999173402786e-02 + + -2.0292599499225616e-01 6.0944002866744995e-01 + <_> + + 0 -1 963 2.4709999561309814e-03 + + -9.5371198654174805e-01 1.8568399548530579e-01 + <_> + + 0 -1 964 5.5415999144315720e-02 + + -1.4405299723148346e-01 2.1506340503692627e+00 + <_> + + 0 -1 965 -1.0635499656200409e-01 + + -1.0911970138549805e+00 1.3228000700473785e-01 + <_> + + 0 -1 966 -7.9889995977282524e-03 + + 1.0253400355577469e-01 -5.1744902133941650e-01 + <_> + + 0 -1 967 7.5567997992038727e-02 + + 5.8965001255273819e-02 1.2354209423065186e+00 + <_> + + 0 -1 968 -9.2805996537208557e-02 + + -1.3431650400161743e+00 -3.4462999552488327e-02 + <_> + + 0 -1 969 4.9431998282670975e-02 + + 4.9601998180150986e-02 1.6054730415344238e+00 + <_> + + 0 -1 970 -1.1772999539971352e-02 + + -1.0261050462722778e+00 -4.1559999808669090e-03 + <_> + + 0 -1 971 8.5886001586914062e-02 + + 8.4642998874187469e-02 9.5220798254013062e-01 + <_> + + 0 -1 972 8.1031002104282379e-02 + + -1.4687100052833557e-01 1.9359990358352661e+00 + <_> + 136 + -3.4265899658203125e+00 + + <_> + + 0 -1 973 -3.3840999007225037e-02 + + 6.5889501571655273e-01 -6.9755297899246216e-01 + <_> + + 0 -1 974 1.5410000458359718e-02 + + -9.0728402137756348e-01 3.0478599667549133e-01 + <_> + + 0 -1 975 5.4905999451875687e-02 + + -4.9774798750877380e-01 5.7132601737976074e-01 + <_> + + 0 -1 976 2.1390000358223915e-02 + + -4.2565199732780457e-01 5.8096802234649658e-01 + <_> + + 0 -1 977 7.8849997371435165e-03 + + -4.7905999422073364e-01 4.3016499280929565e-01 + <_> + + 0 -1 978 -3.7544999271631241e-02 + + 5.0861597061157227e-01 -1.9985899329185486e-01 + <_> + + 0 -1 979 1.5925799310207367e-01 + + -2.3263600468635559e-01 1.0993319749832153e+00 + <_> + + 0 -1 980 -6.8939998745918274e-02 + + 4.0569001436233521e-01 5.6855000555515289e-02 + <_> + + 0 -1 981 -3.3695001155138016e-02 + + 4.5132800936698914e-01 -3.3332800865173340e-01 + <_> + + 0 -1 982 -6.3314996659755707e-02 + + -8.5015702247619629e-01 2.2341699898242950e-01 + <_> + + 0 -1 983 7.3699997738003731e-03 + + -9.3082201480865479e-01 5.9216998517513275e-02 + <_> + + 0 -1 984 -9.5969997346401215e-03 + + -1.2794899940490723e+00 1.8447299301624298e-01 + <_> + + 0 -1 985 -1.3067999482154846e-01 + + 5.8426898717880249e-01 -2.6007199287414551e-01 + <_> + + 0 -1 986 5.7402998208999634e-02 + + -5.3789000958204269e-02 7.1175599098205566e-01 + <_> + + 0 -1 987 -7.2340001352131367e-03 + + -8.6962199211120605e-01 7.5214996933937073e-02 + <_> + + 0 -1 988 3.1098999083042145e-02 + + -7.5006999075412750e-02 9.0781599283218384e-01 + <_> + + 0 -1 989 3.5854000598192215e-02 + + -2.4795499444007874e-01 7.2272098064422607e-01 + <_> + + 0 -1 990 -3.1534999608993530e-02 + + -1.1238329410552979e+00 2.0988300442695618e-01 + <_> + + 0 -1 991 -1.9437000155448914e-02 + + -1.4499390125274658e+00 -1.5100000426173210e-02 + <_> + + 0 -1 992 -7.2420001961290836e-03 + + 5.3864902257919312e-01 -1.1375399678945541e-01 + <_> + + 0 -1 993 8.1639997661113739e-03 + + 6.6889002919197083e-02 -7.6872897148132324e-01 + <_> + + 0 -1 994 -4.3653000146150589e-02 + + 1.1413530111312866e+00 4.0217000991106033e-02 + <_> + + 0 -1 995 2.6569999754428864e-02 + + -2.4719099700450897e-01 5.9295099973678589e-01 + <_> + + 0 -1 996 3.2216999679803848e-02 + + -4.0024999529123306e-02 3.2688000798225403e-01 + <_> + + 0 -1 997 -7.2236001491546631e-02 + + 5.8729398250579834e-01 -2.5396001338958740e-01 + <_> + + 0 -1 998 3.1424999237060547e-02 + + 1.5315100550651550e-01 -5.6042098999023438e-01 + <_> + + 0 -1 999 -4.7699999413453043e-04 + + 1.6958899796009064e-01 -5.2626699209213257e-01 + <_> + + 0 -1 1000 2.7189999818801880e-03 + + -1.4944599568843842e-01 2.9658699035644531e-01 + <_> + + 0 -1 1001 3.2875001430511475e-02 + + -3.9943501353263855e-01 2.5156599283218384e-01 + <_> + + 0 -1 1002 -1.4553000219166279e-02 + + 2.7972599864006042e-01 -4.7203800082206726e-01 + <_> + + 0 -1 1003 3.8017999380826950e-02 + + -2.9200001154094934e-03 -1.1300059556961060e+00 + <_> + + 0 -1 1004 2.8659999370574951e-03 + + 4.1111800074577332e-01 -2.6220801472663879e-01 + <_> + + 0 -1 1005 -4.1606999933719635e-02 + + -1.4293819665908813e+00 -1.9132999703288078e-02 + <_> + + 0 -1 1006 -2.4802999570965767e-02 + + -2.5013598799705505e-01 1.5978699922561646e-01 + <_> + + 0 -1 1007 1.0098000057041645e-02 + + 4.3738998472690582e-02 -6.9986099004745483e-01 + <_> + + 0 -1 1008 -2.0947000011801720e-02 + + -9.4137799739837646e-01 2.3204000294208527e-01 + <_> + + 0 -1 1009 2.2458000108599663e-02 + + -2.7185800671577454e-01 4.5319199562072754e-01 + <_> + + 0 -1 1010 -3.7110999226570129e-02 + + -1.0314660072326660e+00 1.4421799778938293e-01 + <_> + + 0 -1 1011 -1.0648000054061413e-02 + + 6.3107001781463623e-01 -2.5520798563957214e-01 + <_> + + 0 -1 1012 5.5422998964786530e-02 + + 1.6206599771976471e-01 -1.7722640037536621e+00 + <_> + + 0 -1 1013 2.1601999178528786e-02 + + -2.5016099214553833e-01 5.4119801521301270e-01 + <_> + + 0 -1 1014 8.7000000348780304e-05 + + -2.9008901119232178e-01 3.3507999777793884e-01 + <_> + + 0 -1 1015 1.4406000263988972e-02 + + -7.8840004280209541e-03 -1.1677219867706299e+00 + <_> + + 0 -1 1016 1.0777399688959122e-01 + + 1.1292000114917755e-01 -2.4940319061279297e+00 + <_> + + 0 -1 1017 3.5943999886512756e-02 + + -1.9480599462985992e-01 9.5757502317428589e-01 + <_> + + 0 -1 1018 -3.9510000497102737e-03 + + 3.0927801132202148e-01 -2.5530201196670532e-01 + <_> + + 0 -1 1019 2.0942000672221184e-02 + + -7.6319999061524868e-03 -1.0086350440979004e+00 + <_> + + 0 -1 1020 -2.9877999797463417e-02 + + -4.6027699112892151e-01 1.9507199525833130e-01 + <_> + + 0 -1 1021 2.5971999391913414e-02 + + -1.2187999673187733e-02 -1.0035500526428223e+00 + <_> + + 0 -1 1022 1.0603000409901142e-02 + + -7.5969003140926361e-02 4.1669899225234985e-01 + <_> + + 0 -1 1023 8.5819996893405914e-03 + + -2.6648598909378052e-01 3.9111500978469849e-01 + <_> + + 0 -1 1024 2.1270999684929848e-02 + + 1.8273900449275970e-01 -3.6052298545837402e-01 + <_> + + 0 -1 1025 7.4518002569675446e-02 + + -1.8938399851322174e-01 9.2658001184463501e-01 + <_> + + 0 -1 1026 4.6569998376071453e-03 + + -1.4506199955940247e-01 3.3294600248336792e-01 + <_> + + 0 -1 1027 1.7119999974966049e-03 + + -5.2464002370834351e-01 8.9879997074604034e-02 + <_> + + 0 -1 1028 9.8500004969537258e-04 + + -3.8381999731063843e-01 2.4392999708652496e-01 + <_> + + 0 -1 1029 2.8233999386429787e-02 + + -5.7879998348653316e-03 -1.2617139816284180e+00 + <_> + + 0 -1 1030 -3.2678000628948212e-02 + + -5.7953298091888428e-01 1.6955299675464630e-01 + <_> + + 0 -1 1031 2.2536000236868858e-02 + + 2.2281000390648842e-02 -8.7869602441787720e-01 + <_> + + 0 -1 1032 -2.1657999604940414e-02 + + -6.5108501911163330e-01 1.2966899573802948e-01 + <_> + + 0 -1 1033 7.6799998059868813e-03 + + -3.3965200185775757e-01 2.2013300657272339e-01 + <_> + + 0 -1 1034 1.4592000283300877e-02 + + 1.5077300369739532e-01 -5.0452399253845215e-01 + <_> + + 0 -1 1035 2.7868000790476799e-02 + + -2.5045299530029297e-01 4.5741999149322510e-01 + <_> + + 0 -1 1036 5.6940000504255295e-03 + + -1.0948500037193298e-01 5.5757802724838257e-01 + <_> + + 0 -1 1037 -1.0002999566495419e-02 + + -9.7366297245025635e-01 1.8467999994754791e-02 + <_> + + 0 -1 1038 -4.0719998069107533e-03 + + 3.8222199678421021e-01 -1.6921100020408630e-01 + <_> + + 0 -1 1039 -2.2593999281525612e-02 + + -1.0391089916229248e+00 5.1839998923242092e-03 + <_> + + 0 -1 1040 -3.9579998701810837e-02 + + -5.5109229087829590e+00 1.1163999885320663e-01 + <_> + + 0 -1 1041 -1.7537999898195267e-02 + + 9.5485800504684448e-01 -1.8584500253200531e-01 + <_> + + 0 -1 1042 9.0300003066658974e-03 + + 1.0436000302433968e-02 8.2114797830581665e-01 + <_> + + 0 -1 1043 -7.9539995640516281e-03 + + 2.2632899880409241e-01 -3.4568199515342712e-01 + <_> + + 0 -1 1044 2.7091000229120255e-02 + + 1.6430099308490753e-01 -1.3926379680633545e+00 + <_> + + 0 -1 1045 -2.0625999197363853e-02 + + -8.6366099119186401e-01 2.3880000226199627e-03 + <_> + + 0 -1 1046 -7.1989998221397400e-02 + + -2.8192629814147949e+00 1.1570499837398529e-01 + <_> + + 0 -1 1047 -2.6964999735355377e-02 + + -1.2946130037307739e+00 -2.4661000818014145e-02 + <_> + + 0 -1 1048 -4.7377999871969223e-02 + + -8.1306397914886475e-01 1.1831399798393250e-01 + <_> + + 0 -1 1049 -1.0895600169897079e-01 + + 6.5937900543212891e-01 -2.0843900740146637e-01 + <_> + + 0 -1 1050 1.3574000447988510e-02 + + 7.4240001849830151e-03 5.3152197599411011e-01 + <_> + + 0 -1 1051 -6.6920001991093159e-03 + + 3.0655801296234131e-01 -3.1084299087524414e-01 + <_> + + 0 -1 1052 -3.9070001803338528e-03 + + 2.5576499104499817e-01 -5.2932001650333405e-02 + <_> + + 0 -1 1053 -3.7613000720739365e-02 + + -1.4350049495697021e+00 -1.5448000282049179e-02 + <_> + + 0 -1 1054 8.6329998448491096e-03 + + -1.6884399950504303e-01 4.2124900221824646e-01 + <_> + + 0 -1 1055 -3.2097000628709793e-02 + + -6.4979398250579834e-01 4.1110001504421234e-02 + <_> + + 0 -1 1056 5.8495998382568359e-02 + + -5.2963998168706894e-02 6.3368302583694458e-01 + <_> + + 0 -1 1057 -4.0901999920606613e-02 + + -9.2101097106933594e-01 9.0640000998973846e-03 + <_> + + 0 -1 1058 -1.9925000146031380e-02 + + 5.3759998083114624e-01 -6.2996998429298401e-02 + <_> + + 0 -1 1059 -4.6020001173019409e-03 + + -5.4333502054214478e-01 8.4104999899864197e-02 + <_> + + 0 -1 1060 1.6824999824166298e-02 + + 1.5563699603080750e-01 -4.0171200037002563e-01 + <_> + + 0 -1 1061 9.4790002331137657e-03 + + -2.4245299398899078e-01 5.1509499549865723e-01 + <_> + + 0 -1 1062 -1.9534999504685402e-02 + + -5.1118397712707520e-01 1.3831999897956848e-01 + <_> + + 0 -1 1063 1.0746000334620476e-02 + + -2.1854999661445618e-01 6.2828701734542847e-01 + <_> + + 0 -1 1064 3.7927001714706421e-02 + + 1.1640299856662750e-01 -2.7301959991455078e+00 + <_> + + 0 -1 1065 1.6390999779105186e-02 + + -1.4635999687016010e-02 -1.0797250270843506e+00 + <_> + + 0 -1 1066 -1.9785000011324883e-02 + + 1.2166420221328735e+00 3.3275000751018524e-02 + <_> + + 0 -1 1067 1.1067000217735767e-02 + + -2.5388300418853760e-01 4.4038599729537964e-01 + <_> + + 0 -1 1068 5.2479999139904976e-03 + + 2.2496800124645233e-01 -2.4216499924659729e-01 + <_> + + 0 -1 1069 -1.1141999624669552e-02 + + 2.5018098950386047e-01 -3.0811500549316406e-01 + <_> + + 0 -1 1070 -1.0666999965906143e-02 + + -3.2729101181030273e-01 2.6168298721313477e-01 + <_> + + 0 -1 1071 1.0545299947261810e-01 + + -5.5750001221895218e-02 -1.9605729579925537e+00 + <_> + + 0 -1 1072 5.4827999323606491e-02 + + -1.9519999623298645e-03 7.3866099119186401e-01 + <_> + + 0 -1 1073 1.7760999500751495e-02 + + -3.0647200345993042e-01 2.6346999406814575e-01 + <_> + + 0 -1 1074 -3.1185999512672424e-02 + + -2.4600900709629059e-01 1.7082199454307556e-01 + <_> + + 0 -1 1075 -5.7296000421047211e-02 + + 4.7033500671386719e-01 -2.6048299670219421e-01 + <_> + + 0 -1 1076 -1.1312000453472137e-02 + + 3.8628900051116943e-01 -2.8817000985145569e-01 + <_> + + 0 -1 1077 3.0592000111937523e-02 + + -4.8826001584529877e-02 -1.7638969421386719e+00 + <_> + + 0 -1 1078 1.8489999929443002e-03 + + 2.1099899709224701e-01 -2.5940999388694763e-02 + <_> + + 0 -1 1079 1.1419000104069710e-02 + + -1.6829599440097809e-01 1.0278660058975220e+00 + <_> + + 0 -1 1080 8.1403002142906189e-02 + + 1.1531999707221985e-01 -1.2482399940490723e+00 + <_> + + 0 -1 1081 5.3495999425649643e-02 + + -4.6303998678922653e-02 -1.7165969610214233e+00 + <_> + + 0 -1 1082 -2.3948000743985176e-02 + + -4.0246599912643433e-01 2.0562100410461426e-01 + <_> + + 0 -1 1083 6.7690000869333744e-03 + + -3.3152300119400024e-01 2.0683400332927704e-01 + <_> + + 0 -1 1084 -3.2343998551368713e-02 + + -7.2632801532745361e-01 2.0073500275611877e-01 + <_> + + 0 -1 1085 3.7863001227378845e-02 + + -1.5631000697612762e-01 1.6697460412979126e+00 + <_> + + 0 -1 1086 1.5440000221133232e-02 + + 1.9487400352954865e-01 -3.5384199023246765e-01 + <_> + + 0 -1 1087 -4.4376000761985779e-02 + + 8.2093602418899536e-01 -1.8193599581718445e-01 + <_> + + 0 -1 1088 -2.3102000355720520e-02 + + -4.3044099211692810e-01 1.2375400215387344e-01 + <_> + + 0 -1 1089 1.9400000572204590e-02 + + -2.9726000502705574e-02 -1.1597590446472168e+00 + <_> + + 0 -1 1090 1.0385700315237045e-01 + + 1.1149899661540985e-01 -4.6835222244262695e+00 + <_> + + 0 -1 1091 -1.8964000046253204e-02 + + 2.1773819923400879e+00 -1.4544400572776794e-01 + <_> + + 0 -1 1092 3.8750998675823212e-02 + + -4.9446001648902893e-02 3.4018298983573914e-01 + <_> + + 0 -1 1093 2.2766999900341034e-02 + + -3.2802999019622803e-01 3.0531400442123413e-01 + <_> + + 0 -1 1094 -3.1357001513242722e-02 + + 1.1520819664001465e+00 2.7305999770760536e-02 + <_> + + 0 -1 1095 9.6909999847412109e-03 + + -3.8799500465393066e-01 2.1512599289417267e-01 + <_> + + 0 -1 1096 -4.9284998327493668e-02 + + -1.6774909496307373e+00 1.5774199366569519e-01 + <_> + + 0 -1 1097 -3.9510998874902725e-02 + + -9.7647899389266968e-01 -1.0552000254392624e-02 + <_> + + 0 -1 1098 4.7997999936342239e-02 + + 2.0843900740146637e-01 -6.8992799520492554e-01 + <_> + + 0 -1 1099 5.1422998309135437e-02 + + -1.6665300726890564e-01 1.2149239778518677e+00 + <_> + + 0 -1 1100 1.4279999770224094e-02 + + 2.3627699911594391e-01 -4.1396799683570862e-01 + <_> + + 0 -1 1101 -9.1611996293067932e-02 + + -9.2830902338027954e-01 -1.8345000222325325e-02 + <_> + + 0 -1 1102 6.5080001950263977e-03 + + -7.3647201061248779e-01 1.9497099518775940e-01 + <_> + + 0 -1 1103 3.5723000764846802e-02 + + 1.4197799563407898e-01 -4.2089301347732544e-01 + <_> + + 0 -1 1104 5.0638001412153244e-02 + + 1.1644000187516212e-02 7.8486597537994385e-01 + <_> + + 0 -1 1105 -1.4613999985158443e-02 + + -1.1909500360488892e+00 -3.5128001123666763e-02 + <_> + + 0 -1 1106 -3.8662999868392944e-02 + + 2.4314730167388916e+00 6.5647996962070465e-02 + <_> + + 0 -1 1107 -4.0346998721361160e-02 + + 7.1755301952362061e-01 -1.9108299911022186e-01 + <_> + + 0 -1 1108 2.3902000859379768e-02 + + 1.5646199882030487e-01 -7.9294800758361816e-01 + <_> + 137 + -3.5125269889831543e+00 + + <_> + + 0 -1 1109 8.5640000179409981e-03 + + -8.1450700759887695e-01 5.8875298500061035e-01 + <_> + + 0 -1 1110 -1.3292600214481354e-01 + + 9.3213397264480591e-01 -2.9367300868034363e-01 + <_> + + 0 -1 1111 9.8400004208087921e-03 + + -5.6462901830673218e-01 4.1647699475288391e-01 + <_> + + 0 -1 1112 5.0889998674392700e-03 + + -7.9232800006866455e-01 1.6975000500679016e-01 + <_> + + 0 -1 1113 -6.1039000749588013e-02 + + -1.4169000387191772e+00 2.5020999833941460e-02 + <_> + + 0 -1 1114 -4.6599999768659472e-04 + + 3.7982499599456787e-01 -4.1567099094390869e-01 + <_> + + 0 -1 1115 3.3889999613165855e-03 + + -4.0768599510192871e-01 3.5548499226570129e-01 + <_> + + 0 -1 1116 2.1006999537348747e-02 + + -2.4080100655555725e-01 8.6112701892852783e-01 + <_> + + 0 -1 1117 7.5559997931122780e-03 + + -8.7467199563980103e-01 9.8572000861167908e-02 + <_> + + 0 -1 1118 2.4779999628663063e-02 + + 1.5566200017929077e-01 -6.9229799509048462e-01 + <_> + + 0 -1 1119 -3.5620000213384628e-02 + + -1.1472270488739014e+00 3.6359999328851700e-02 + <_> + + 0 -1 1120 1.9810000434517860e-02 + + 1.5516200661659241e-01 -6.9520097970962524e-01 + <_> + + 0 -1 1121 1.5019999817013741e-02 + + 4.1990000754594803e-02 -9.6622800827026367e-01 + <_> + + 0 -1 1122 -2.3137999698519707e-02 + + 4.3396899104118347e-01 2.4160000029951334e-03 + <_> + + 0 -1 1123 -1.8743000924587250e-02 + + 4.3481099605560303e-01 -3.2522499561309814e-01 + <_> + + 0 -1 1124 4.5080000162124634e-01 + + -9.4573996961116791e-02 7.2421300411224365e-01 + <_> + + 0 -1 1125 1.1854999698698521e-02 + + -3.8133099675178528e-01 3.0098399519920349e-01 + <_> + + 0 -1 1126 -2.4830000475049019e-02 + + 8.9300602674484253e-01 -1.0295899957418442e-01 + <_> + + 0 -1 1127 -4.4743001461029053e-02 + + 8.6280298233032227e-01 -2.1716499328613281e-01 + <_> + + 0 -1 1128 -1.4600000344216824e-02 + + 6.0069400072097778e-01 -1.5906299650669098e-01 + <_> + + 0 -1 1129 -2.4527000263333321e-02 + + -1.5872869491577148e+00 -2.1817000582814217e-02 + <_> + + 0 -1 1130 2.3024000227451324e-02 + + 1.6853399574756622e-01 -3.8106900453567505e-01 + <_> + + 0 -1 1131 -2.4917000904679298e-02 + + 5.0810897350311279e-01 -2.7279898524284363e-01 + <_> + + 0 -1 1132 1.0130000300705433e-03 + + -4.3138799071311951e-01 2.6438099145889282e-01 + <_> + + 0 -1 1133 1.5603000298142433e-02 + + -3.1624200940132141e-01 5.5715900659561157e-01 + <_> + + 0 -1 1134 -2.6685999706387520e-02 + + 1.0553920269012451e+00 2.9074000194668770e-02 + <_> + + 0 -1 1135 1.3940000208094716e-03 + + -7.1873801946640015e-01 6.5390996634960175e-02 + <_> + + 0 -1 1136 -6.4799998654052615e-04 + + 2.4884399771690369e-01 -2.0978200435638428e-01 + <_> + + 0 -1 1137 -3.1888000667095184e-02 + + -6.8844497203826904e-01 6.3589997589588165e-02 + <_> + + 0 -1 1138 -4.9290000461041927e-03 + + -5.9152501821517944e-01 2.7943599224090576e-01 + <_> + + 0 -1 1139 3.1168000772595406e-02 + + 4.5223999768495560e-02 -8.8639199733734131e-01 + <_> + + 0 -1 1140 -3.3663000911474228e-02 + + -6.1590200662612915e-01 1.5749299526214600e-01 + <_> + + 0 -1 1141 1.1966999620199203e-02 + + -3.0606698989868164e-01 4.2293301224708557e-01 + <_> + + 0 -1 1142 -3.4680001437664032e-02 + + -1.3734940290451050e+00 1.5908700227737427e-01 + <_> + + 0 -1 1143 9.9290004000067711e-03 + + -5.5860197544097900e-01 1.2119200080633163e-01 + <_> + + 0 -1 1144 5.9574998915195465e-02 + + 4.9720001406967640e-03 8.2055401802062988e-01 + <_> + + 0 -1 1145 -6.5428003668785095e-02 + + 1.5651429891586304e+00 -1.6817499697208405e-01 + <_> + + 0 -1 1146 -9.2895999550819397e-02 + + -1.5794529914855957e+00 1.4661799371242523e-01 + <_> + + 0 -1 1147 -4.1184000670909882e-02 + + -1.5518720149993896e+00 -2.9969999566674232e-02 + <_> + + 0 -1 1148 2.1447999402880669e-02 + + 1.7196300625801086e-01 -6.9343197345733643e-01 + <_> + + 0 -1 1149 -2.5569999590516090e-02 + + -1.3061310052871704e+00 -2.4336999282240868e-02 + <_> + + 0 -1 1150 -4.1200999170541763e-02 + + -1.3821059465408325e+00 1.4801800251007080e-01 + <_> + + 0 -1 1151 -1.7668999731540680e-02 + + -7.0889997482299805e-01 3.6524001508951187e-02 + <_> + + 0 -1 1152 9.0060001239180565e-03 + + -4.0913999080657959e-02 8.0373102426528931e-01 + <_> + + 0 -1 1153 -1.1652999557554722e-02 + + 5.7546800374984741e-01 -2.4991700053215027e-01 + <_> + + 0 -1 1154 -7.4780001305043697e-03 + + -4.9280899763107300e-01 1.9810900092124939e-01 + <_> + + 0 -1 1155 8.5499999113380909e-04 + + -4.8858100175857544e-01 1.3563099503517151e-01 + <_> + + 0 -1 1156 -3.0538000166416168e-02 + + -6.0278397798538208e-01 1.8522000312805176e-01 + <_> + + 0 -1 1157 -1.8846999853849411e-02 + + 2.3565599322319031e-01 -3.5136300325393677e-01 + <_> + + 0 -1 1158 -8.1129996106028557e-03 + + -8.1304997205734253e-02 2.1069599688053131e-01 + <_> + + 0 -1 1159 -3.4830000251531601e-02 + + -1.2065670490264893e+00 -1.4251999557018280e-02 + <_> + + 0 -1 1160 1.9021000713109970e-02 + + 2.3349900543689728e-01 -4.5664900541305542e-01 + <_> + + 0 -1 1161 -1.9004000350832939e-02 + + -8.1075799465179443e-01 1.3140000402927399e-02 + <_> + + 0 -1 1162 -8.9057996869087219e-02 + + 6.1542397737503052e-01 3.2983001321554184e-02 + <_> + + 0 -1 1163 6.8620000965893269e-03 + + -2.9583099484443665e-01 2.7003699541091919e-01 + <_> + + 0 -1 1164 -2.8240999206900597e-02 + + -6.1102700233459473e-01 1.7357499897480011e-01 + <_> + + 0 -1 1165 -3.2099999953061342e-04 + + -5.3322899341583252e-01 6.8539001047611237e-02 + <_> + + 0 -1 1166 -1.0829100012779236e-01 + + -1.2879559993743896e+00 1.1801700294017792e-01 + <_> + + 0 -1 1167 1.5878999605774879e-02 + + -1.7072600126266479e-01 1.1103910207748413e+00 + <_> + + 0 -1 1168 8.6859995499253273e-03 + + -1.0995099693536758e-01 4.6010500192642212e-01 + <_> + + 0 -1 1169 -2.5234999135136604e-02 + + 1.0220669507980347e+00 -1.8694299459457397e-01 + <_> + + 0 -1 1170 -1.3508999720215797e-02 + + -7.8316599130630493e-01 1.4202600717544556e-01 + <_> + + 0 -1 1171 -7.7149998396635056e-03 + + -8.8060700893402100e-01 1.1060000397264957e-02 + <_> + + 0 -1 1172 7.1580000221729279e-02 + + 1.1369399726390839e-01 -1.1032789945602417e+00 + <_> + + 0 -1 1173 -1.3554000295698643e-02 + + -8.1096500158309937e-01 3.4080001059919596e-03 + <_> + + 0 -1 1174 2.9450000729411840e-03 + + -7.2879999876022339e-02 3.4998100996017456e-01 + <_> + + 0 -1 1175 -5.0833001732826233e-02 + + -1.2868590354919434e+00 -2.8842000290751457e-02 + <_> + + 0 -1 1176 -8.7989997118711472e-03 + + 4.7613599896430969e-01 -1.4690400660037994e-01 + <_> + + 0 -1 1177 2.1424399316310883e-01 + + -5.9702001512050629e-02 -2.4802260398864746e+00 + <_> + + 0 -1 1178 1.3962999917566776e-02 + + 1.7420299351215363e-01 -4.3911001086235046e-01 + <_> + + 0 -1 1179 4.2502000927925110e-02 + + -1.9965299963951111e-01 7.0654797554016113e-01 + <_> + + 0 -1 1180 1.9827999174594879e-02 + + -6.9136001169681549e-02 6.1643397808074951e-01 + <_> + + 0 -1 1181 -3.3560000360012054e-02 + + -1.2740780115127563e+00 -2.5673000141978264e-02 + <_> + + 0 -1 1182 6.3542999327182770e-02 + + 1.2403500080108643e-01 -1.0776289701461792e+00 + <_> + + 0 -1 1183 2.1933000534772873e-02 + + 1.4952000230550766e-02 -7.1023499965667725e-01 + <_> + + 0 -1 1184 -7.8424997627735138e-02 + + 6.2033998966217041e-01 3.3610999584197998e-02 + <_> + + 0 -1 1185 1.4390000142157078e-02 + + -3.6324599385261536e-01 1.7308300733566284e-01 + <_> + + 0 -1 1186 -6.7309997975826263e-02 + + 5.2374100685119629e-01 1.2799999676644802e-02 + <_> + + 0 -1 1187 1.3047499954700470e-01 + + -1.7122499644756317e-01 1.1235200166702271e+00 + <_> + + 0 -1 1188 -4.6245999634265900e-02 + + -1.1908329725265503e+00 1.7425599694252014e-01 + <_> + + 0 -1 1189 -2.9842000454664230e-02 + + 8.3930599689483643e-01 -1.8064199388027191e-01 + <_> + + 0 -1 1190 -3.8099999073892832e-04 + + 3.5532799363136292e-01 -2.3842300474643707e-01 + <_> + + 0 -1 1191 -2.2378999739885330e-02 + + -8.7943899631500244e-01 -7.8399997437372804e-04 + <_> + + 0 -1 1192 -1.5569999814033508e-03 + + -1.4253300428390503e-01 2.5876200199127197e-01 + <_> + + 0 -1 1193 1.2013000436127186e-02 + + -2.9015499353408813e-01 2.6051101088523865e-01 + <_> + + 0 -1 1194 2.4384999647736549e-02 + + -3.1438998878002167e-02 5.8695900440216064e-01 + <_> + + 0 -1 1195 -4.7180999070405960e-02 + + 6.9430100917816162e-01 -2.1816100180149078e-01 + <_> + + 0 -1 1196 -2.4893999099731445e-02 + + -6.4599299430847168e-01 1.5611599385738373e-01 + <_> + + 0 -1 1197 2.1944999694824219e-02 + + -2.7742000296711922e-02 -1.1346880197525024e+00 + <_> + + 0 -1 1198 1.8809899687767029e-01 + + -1.0076000355184078e-02 1.2429029941558838e+00 + <_> + + 0 -1 1199 -7.7872000634670258e-02 + + 8.5008001327514648e-01 -1.9015499949455261e-01 + <_> + + 0 -1 1200 -4.8769000917673111e-02 + + -2.0763080120086670e+00 1.2179400026798248e-01 + <_> + + 0 -1 1201 -1.7115000635385513e-02 + + -8.5687297582626343e-01 7.8760003671050072e-03 + <_> + + 0 -1 1202 -2.7499999850988388e-03 + + 3.8645499944686890e-01 -1.1391499638557434e-01 + <_> + + 0 -1 1203 -9.8793998360633850e-02 + + -1.7233899831771851e+00 -5.6063000112771988e-02 + <_> + + 0 -1 1204 -2.1936999633908272e-02 + + 5.4749399423599243e-01 -4.2481999844312668e-02 + <_> + + 0 -1 1205 6.1096999794244766e-02 + + -3.8945000618696213e-02 -1.0807880163192749e+00 + <_> + + 0 -1 1206 -2.4563999846577644e-02 + + 5.8311098814010620e-01 -9.7599998116493225e-04 + <_> + + 0 -1 1207 3.3752001821994781e-02 + + -1.3795999810099602e-02 -8.4730297327041626e-01 + <_> + + 0 -1 1208 3.8199000060558319e-02 + + 1.5114299952983856e-01 -7.9473400115966797e-01 + <_> + + 0 -1 1209 -2.0117999985814095e-02 + + 5.1579099893569946e-01 -2.1445399522781372e-01 + <_> + + 0 -1 1210 2.4734999984502792e-02 + + -2.2105000913143158e-02 4.2917698621749878e-01 + <_> + + 0 -1 1211 -2.4357000365853310e-02 + + -8.6201298236846924e-01 -3.6760000512003899e-03 + <_> + + 0 -1 1212 -2.6442000642418861e-02 + + -4.5397499203681946e-01 2.2462800145149231e-01 + <_> + + 0 -1 1213 -3.4429999068379402e-03 + + 1.3073000311851501e-01 -3.8622701168060303e-01 + <_> + + 0 -1 1214 1.0701700299978256e-01 + + 1.3158600032329559e-01 -7.9306900501251221e-01 + <_> + + 0 -1 1215 4.5152999460697174e-02 + + -2.5296801328659058e-01 4.0672400593757629e-01 + <_> + + 0 -1 1216 4.4349998235702515e-02 + + 2.2613000124692917e-02 7.9618102312088013e-01 + <_> + + 0 -1 1217 1.0839999886229634e-03 + + -3.9158400893211365e-01 1.1639100313186646e-01 + <_> + + 0 -1 1218 7.1433000266551971e-02 + + 8.2466997206211090e-02 1.2530590295791626e+00 + <_> + + 0 -1 1219 3.5838000476360321e-02 + + -1.8203300237655640e-01 7.7078700065612793e-01 + <_> + + 0 -1 1220 -2.0839000120759010e-02 + + -6.1744397878646851e-01 1.5891399979591370e-01 + <_> + + 0 -1 1221 4.2525801062583923e-01 + + -4.8978000879287720e-02 -1.8422030210494995e+00 + <_> + + 0 -1 1222 1.1408000253140926e-02 + + 1.7918199300765991e-01 -1.5383499860763550e-01 + <_> + + 0 -1 1223 -1.5364999882876873e-02 + + -8.4016501903533936e-01 -1.0280000278726220e-03 + <_> + + 0 -1 1224 -1.5212000347673893e-02 + + -1.8995699286460876e-01 1.7130999267101288e-01 + <_> + + 0 -1 1225 -1.8972000107169151e-02 + + -7.9541999101638794e-01 6.6800001077353954e-03 + <_> + + 0 -1 1226 -3.3330000005662441e-03 + + -2.3530800640583038e-01 2.4730099737644196e-01 + <_> + + 0 -1 1227 9.3248002231121063e-02 + + -5.4758001118898392e-02 -1.8324300050735474e+00 + <_> + + 0 -1 1228 -1.2555000372231007e-02 + + 2.6385200023651123e-01 -3.8526400923728943e-01 + <_> + + 0 -1 1229 -2.7070000767707825e-02 + + -6.6929799318313599e-01 2.0340999588370323e-02 + <_> + + 0 -1 1230 -2.3677000775933266e-02 + + 6.7265301942825317e-01 -1.4344000257551670e-02 + <_> + + 0 -1 1231 -1.4275000430643559e-02 + + 3.0186399817466736e-01 -2.8514400124549866e-01 + <_> + + 0 -1 1232 2.8096999973058701e-02 + + 1.4766000211238861e-01 -1.4078520536422729e+00 + <_> + + 0 -1 1233 5.0840001553297043e-02 + + -1.8613600730895996e-01 7.9953002929687500e-01 + <_> + + 0 -1 1234 1.1505999602377415e-02 + + 1.9118399918079376e-01 -8.5035003721714020e-02 + <_> + + 0 -1 1235 -1.4661000110208988e-02 + + 4.5239299535751343e-01 -2.2205199301242828e-01 + <_> + + 0 -1 1236 2.2842499613761902e-01 + + 1.3488399982452393e-01 -1.2894610166549683e+00 + <_> + + 0 -1 1237 1.1106900125741959e-01 + + -2.0753799378871918e-01 5.4561597108840942e-01 + <_> + + 0 -1 1238 3.2450000289827585e-03 + + 3.2053700089454651e-01 -1.6403500735759735e-01 + <_> + + 0 -1 1239 8.5309997200965881e-02 + + -2.0210500061511993e-01 5.3296798467636108e-01 + <_> + + 0 -1 1240 2.2048000246286392e-02 + + 1.5698599815368652e-01 -1.7014099657535553e-01 + <_> + + 0 -1 1241 -1.5676999464631081e-02 + + -6.2863498926162720e-01 4.0761999785900116e-02 + <_> + + 0 -1 1242 3.3112901449203491e-01 + + 1.6609300673007965e-01 -1.0326379537582397e+00 + <_> + + 0 -1 1243 8.8470000773668289e-03 + + -2.5076198577880859e-01 3.1660598516464233e-01 + <_> + + 0 -1 1244 4.6080000698566437e-02 + + 1.5352100133895874e-01 -1.6333500146865845e+00 + <_> + + 0 -1 1245 -3.7703000009059906e-02 + + 5.6873798370361328e-01 -2.0102599263191223e-01 + <_> + 159 + -3.5939640998840332e+00 + + <_> + + 0 -1 1246 -8.1808999180793762e-02 + + 5.7124799489974976e-01 -6.7438799142837524e-01 + <_> + + 0 -1 1247 2.1761199831962585e-01 + + -3.8610199093818665e-01 9.0343999862670898e-01 + <_> + + 0 -1 1248 1.4878000132739544e-02 + + 2.2241599857807159e-01 -1.2779350280761719e+00 + <_> + + 0 -1 1249 5.2434999495744705e-02 + + -2.8690400719642639e-01 7.5742298364639282e-01 + <_> + + 0 -1 1250 9.1429995372891426e-03 + + -6.4880400896072388e-01 2.2268800437450409e-01 + <_> + + 0 -1 1251 7.9169999808073044e-03 + + -2.9253599047660828e-01 3.1030198931694031e-01 + <_> + + 0 -1 1252 -2.6084000244736671e-02 + + 4.5532700419425964e-01 -3.8500601053237915e-01 + <_> + + 0 -1 1253 -2.9400000348687172e-03 + + -5.1264399290084839e-01 2.7432298660278320e-01 + <_> + + 0 -1 1254 5.7130001485347748e-02 + + 1.5788000077009201e-02 -1.2133100032806396e+00 + <_> + + 0 -1 1255 -6.1309998854994774e-03 + + 3.9174601435661316e-01 -3.0866798758506775e-01 + <_> + + 0 -1 1256 -4.0405001491308212e-02 + + 1.1901949644088745e+00 -2.0347100496292114e-01 + <_> + + 0 -1 1257 -2.0297000184655190e-02 + + -6.8239498138427734e-01 2.0458699762821198e-01 + <_> + + 0 -1 1258 -1.7188999801874161e-02 + + -8.4939897060394287e-01 3.8433000445365906e-02 + <_> + + 0 -1 1259 -2.4215999990701675e-02 + + -1.1039420366287231e+00 1.5975099802017212e-01 + <_> + + 0 -1 1260 5.6869000196456909e-02 + + -1.9595299661159515e-01 1.1806850433349609e+00 + <_> + + 0 -1 1261 3.6199999158270657e-04 + + -4.0847799181938171e-01 3.2938599586486816e-01 + <_> + + 0 -1 1262 9.9790003150701523e-03 + + -2.9673001170158386e-01 4.1547900438308716e-01 + <_> + + 0 -1 1263 -5.2625000476837158e-02 + + -1.3069299459457397e+00 1.7862600088119507e-01 + <_> + + 0 -1 1264 -1.3748999685049057e-02 + + 2.3665800690650940e-01 -4.4536599516868591e-01 + <_> + + 0 -1 1265 -3.0517000705003738e-02 + + 2.9018300771713257e-01 -1.1210100352764130e-01 + <_> + + 0 -1 1266 -3.0037501454353333e-01 + + -2.4237680435180664e+00 -4.2830999940633774e-02 + <_> + + 0 -1 1267 -3.5990998148918152e-02 + + 8.8206499814987183e-01 -4.7012999653816223e-02 + <_> + + 0 -1 1268 -5.5112000554800034e-02 + + 8.0119001865386963e-01 -2.0490999519824982e-01 + <_> + + 0 -1 1269 3.3762000501155853e-02 + + 1.4617599546909332e-01 -1.1349489688873291e+00 + <_> + + 0 -1 1270 -8.2710003480315208e-03 + + -8.1604897975921631e-01 1.8988000229001045e-02 + <_> + + 0 -1 1271 -5.4399999789893627e-03 + + -7.0980900526046753e-01 2.2343699634075165e-01 + <_> + + 0 -1 1272 3.1059999018907547e-03 + + -7.2808599472045898e-01 4.0224999189376831e-02 + <_> + + 0 -1 1273 5.3651999682188034e-02 + + 1.7170900106430054e-01 -1.1163710355758667e+00 + <_> + + 0 -1 1274 -1.2541399896144867e-01 + + 2.7680370807647705e+00 -1.4611500501632690e-01 + <_> + + 0 -1 1275 9.2542000114917755e-02 + + 1.1609800159931183e-01 -3.9635529518127441e+00 + <_> + + 0 -1 1276 3.8513999432325363e-02 + + -7.6399999670684338e-03 -9.8780900239944458e-01 + <_> + + 0 -1 1277 -2.0200000144541264e-03 + + 2.3059999942779541e-01 -7.4970299005508423e-01 + <_> + + 0 -1 1278 9.7599998116493225e-03 + + -3.1137999892234802e-01 3.0287799239158630e-01 + <_> + + 0 -1 1279 2.4095000699162483e-02 + + -4.9529999494552612e-02 5.2690100669860840e-01 + <_> + + 0 -1 1280 -1.7982000485062599e-02 + + -1.1610640287399292e+00 -5.7000000961124897e-03 + <_> + + 0 -1 1281 -1.0555000044405460e-02 + + -2.7189099788665771e-01 2.3597699403762817e-01 + <_> + + 0 -1 1282 -7.2889998555183411e-03 + + -5.4219102859497070e-01 8.1914000213146210e-02 + <_> + + 0 -1 1283 2.3939000442624092e-02 + + 1.7975799739360809e-01 -6.7049497365951538e-01 + <_> + + 0 -1 1284 -1.8365999683737755e-02 + + 6.2664300203323364e-01 -2.0970100164413452e-01 + <_> + + 0 -1 1285 1.5715999528765678e-02 + + 2.4193699657917023e-01 -1.0444309711456299e+00 + <_> + + 0 -1 1286 -4.8804000020027161e-02 + + -9.4060599803924561e-01 -3.7519999314099550e-03 + <_> + + 0 -1 1287 6.7130001261830330e-03 + + -7.5432002544403076e-02 6.1575299501419067e-01 + <_> + + 0 -1 1288 9.7770001739263535e-03 + + 3.9285000413656235e-02 -8.4810298681259155e-01 + <_> + + 0 -1 1289 1.4744999818503857e-02 + + 1.6968999803066254e-01 -5.0906401872634888e-01 + <_> + + 0 -1 1290 9.7079001367092133e-02 + + -3.3103000372648239e-02 -1.2706379890441895e+00 + <_> + + 0 -1 1291 4.8285998404026031e-02 + + 9.4329997897148132e-02 2.7203190326690674e+00 + <_> + + 0 -1 1292 9.7810002043843269e-03 + + -3.9533400535583496e-01 1.5363800525665283e-01 + <_> + + 0 -1 1293 -3.9893999695777893e-02 + + -2.2767400741577148e-01 1.3913999497890472e-01 + <_> + + 0 -1 1294 2.2848000749945641e-02 + + -2.7391999959945679e-01 3.4199500083923340e-01 + <_> + + 0 -1 1295 6.7179999314248562e-03 + + -1.0874299705028534e-01 4.8125401139259338e-01 + <_> + + 0 -1 1296 5.9599999338388443e-02 + + -4.9522001296281815e-02 -2.0117089748382568e+00 + <_> + + 0 -1 1297 6.9340001791715622e-03 + + 1.5037499368190765e-01 -1.1271899938583374e-01 + <_> + + 0 -1 1298 1.5757000073790550e-02 + + -2.0885000005364418e-02 -1.1651979684829712e+00 + <_> + + 0 -1 1299 -4.9690000712871552e-02 + + -8.0213499069213867e-01 1.4372299611568451e-01 + <_> + + 0 -1 1300 5.2347000688314438e-02 + + -2.0836700499057770e-01 6.1677598953247070e-01 + <_> + + 0 -1 1301 2.2430999204516411e-02 + + 2.0305900275707245e-01 -7.5326198339462280e-01 + <_> + + 0 -1 1302 4.1142001748085022e-02 + + -1.8118199706077576e-01 1.0033359527587891e+00 + <_> + + 0 -1 1303 -2.1632000803947449e-02 + + 4.9998998641967773e-01 -3.4662999212741852e-02 + <_> + + 0 -1 1304 -8.2808002829551697e-02 + + 1.1711900234222412e+00 -1.8433600664138794e-01 + <_> + + 0 -1 1305 8.5060000419616699e-03 + + -6.3225001096725464e-02 2.9024899005889893e-01 + <_> + + 0 -1 1306 7.8905001282691956e-02 + + -2.3274500668048859e-01 5.9695798158645630e-01 + <_> + + 0 -1 1307 -9.0207003057003021e-02 + + -8.2211899757385254e-01 1.7772200703620911e-01 + <_> + + 0 -1 1308 -2.9269000515341759e-02 + + 6.0860699415206909e-01 -2.1468900144100189e-01 + <_> + + 0 -1 1309 6.9499998353421688e-03 + + -4.2665999382734299e-02 6.0512101650238037e-01 + <_> + + 0 -1 1310 -8.0629996955394745e-03 + + -1.1508270502090454e+00 -2.7286000549793243e-02 + <_> + + 0 -1 1311 1.9595999270677567e-02 + + -9.1880001127719879e-03 5.6857800483703613e-01 + <_> + + 0 -1 1312 -1.4884999953210354e-02 + + 3.7658798694610596e-01 -2.7149501442909241e-01 + <_> + + 0 -1 1313 2.5217000395059586e-02 + + -9.9991001188755035e-02 2.4664700031280518e-01 + <_> + + 0 -1 1314 -1.5855999663472176e-02 + + 6.6826701164245605e-01 -2.0614700019359589e-01 + <_> + + 0 -1 1315 2.9441000893712044e-02 + + 1.5832200646400452e-01 -7.6060897111892700e-01 + <_> + + 0 -1 1316 -8.5279997438192368e-03 + + 3.8212299346923828e-01 -2.5407800078392029e-01 + <_> + + 0 -1 1317 2.4421999230980873e-02 + + 1.5105099976062775e-01 -2.8752899169921875e-01 + <_> + + 0 -1 1318 -3.3886998891830444e-02 + + -6.8002802133560181e-01 3.4327000379562378e-02 + <_> + + 0 -1 1319 -2.0810000132769346e-03 + + 2.5413900613784790e-01 -2.6859098672866821e-01 + <_> + + 0 -1 1320 3.0358999967575073e-02 + + -3.0842000618577003e-02 -1.1476809978485107e+00 + <_> + + 0 -1 1321 4.0210001170635223e-03 + + -3.5253798961639404e-01 2.9868099093437195e-01 + <_> + + 0 -1 1322 2.7681000530719757e-02 + + -3.8148999214172363e-02 -1.3262039422988892e+00 + <_> + + 0 -1 1323 7.9039996489882469e-03 + + -2.3737000301480293e-02 7.0503002405166626e-01 + <_> + + 0 -1 1324 4.4031001627445221e-02 + + 1.0674899816513062e-01 -4.5261201262474060e-01 + <_> + + 0 -1 1325 -3.2370999455451965e-02 + + 4.6674901247024536e-01 -6.1546999961137772e-02 + <_> + + 0 -1 1326 2.0933000370860100e-02 + + -2.8447899222373962e-01 4.3845599889755249e-01 + <_> + + 0 -1 1327 2.5227999314665794e-02 + + -2.2537000477313995e-02 7.0389097929000854e-01 + <_> + + 0 -1 1328 6.5520000644028187e-03 + + -3.2554900646209717e-01 2.4023699760437012e-01 + <_> + + 0 -1 1329 -5.8557998389005661e-02 + + -1.2227720022201538e+00 1.1668799817562103e-01 + <_> + + 0 -1 1330 3.1899999827146530e-02 + + -1.9305000081658363e-02 -1.0973169803619385e+00 + <_> + + 0 -1 1331 -3.0445000156760216e-02 + + 6.5582501888275146e-01 7.5090996921062469e-02 + <_> + + 0 -1 1332 1.4933000318706036e-02 + + -5.2155798673629761e-01 1.1523099988698959e-01 + <_> + + 0 -1 1333 -4.9008000642061234e-02 + + -7.8303998708724976e-01 1.6657200455665588e-01 + <_> + + 0 -1 1334 8.3158999681472778e-02 + + -2.6879999786615372e-03 -8.5282301902770996e-01 + <_> + + 0 -1 1335 2.3902999237179756e-02 + + -5.1010999828577042e-02 4.1999098658561707e-01 + <_> + + 0 -1 1336 1.6428999602794647e-02 + + 1.9232999533414841e-02 -6.5049099922180176e-01 + <_> + + 0 -1 1337 -1.1838000267744064e-02 + + -6.2409800291061401e-01 1.5411199629306793e-01 + <_> + + 0 -1 1338 -1.6799999866634607e-04 + + 1.7589199542999268e-01 -3.4338700771331787e-01 + <_> + + 0 -1 1339 1.9193999469280243e-02 + + 4.3418999761343002e-02 7.9069197177886963e-01 + <_> + + 0 -1 1340 -1.0032000020146370e-02 + + 4.5648899674415588e-01 -2.2494800388813019e-01 + <_> + + 0 -1 1341 -1.4004000462591648e-02 + + 3.3570998907089233e-01 -4.8799999058246613e-03 + <_> + + 0 -1 1342 -1.0319899767637253e-01 + + -2.3378000259399414e+00 -5.8933001011610031e-02 + <_> + + 0 -1 1343 -9.5697000622749329e-02 + + -6.6153901815414429e-01 2.0098599791526794e-01 + <_> + + 0 -1 1344 -4.1480999439954758e-02 + + 4.5939201116561890e-01 -2.2314099967479706e-01 + <_> + + 0 -1 1345 2.4099999573081732e-03 + + -2.6898598670959473e-01 2.4922999739646912e-01 + <_> + + 0 -1 1346 1.0724999755620956e-01 + + -1.8640199303627014e-01 7.2769802808761597e-01 + <_> + + 0 -1 1347 3.1870000530034304e-03 + + -2.4608999490737915e-02 2.8643900156021118e-01 + <_> + + 0 -1 1348 2.9167000204324722e-02 + + -3.4683000296354294e-02 -1.1162580251693726e+00 + <_> + + 0 -1 1349 1.1287000030279160e-02 + + 6.3760001212358475e-03 6.6632097959518433e-01 + <_> + + 0 -1 1350 -1.2001000344753265e-02 + + 4.2420101165771484e-01 -2.6279801130294800e-01 + <_> + + 0 -1 1351 -1.2695999816060066e-02 + + -2.1957000717520714e-02 1.8936799466609955e-01 + <_> + + 0 -1 1352 2.4597000330686569e-02 + + -3.4963998943567276e-02 -1.0989320278167725e+00 + <_> + + 0 -1 1353 4.5953001827001572e-02 + + 1.1109799891710281e-01 -2.9306049346923828e+00 + <_> + + 0 -1 1354 -2.7241000905632973e-02 + + 2.9101699590682983e-01 -2.7407899498939514e-01 + <_> + + 0 -1 1355 4.0063999593257904e-02 + + 1.1877900362014771e-01 -6.2801802158355713e-01 + <_> + + 0 -1 1356 2.3055000230669975e-02 + + 1.4813800156116486e-01 -3.7007498741149902e-01 + <_> + + 0 -1 1357 -2.3737000301480293e-02 + + -5.3724801540374756e-01 1.9358199834823608e-01 + <_> + + 0 -1 1358 7.7522002160549164e-02 + + -6.0194000601768494e-02 -1.9489669799804688e+00 + <_> + + 0 -1 1359 -1.3345000334084034e-02 + + -4.5229598879814148e-01 1.8741500377655029e-01 + <_> + + 0 -1 1360 -2.1719999611377716e-02 + + 1.2144249677658081e+00 -1.5365800261497498e-01 + <_> + + 0 -1 1361 -7.1474999189376831e-02 + + -2.3047130107879639e+00 1.0999900102615356e-01 + <_> + + 0 -1 1362 -5.4999999701976776e-03 + + -7.1855199337005615e-01 2.0100999623537064e-02 + <_> + + 0 -1 1363 2.6740999892354012e-02 + + 7.3545001447200775e-02 9.8786002397537231e-01 + <_> + + 0 -1 1364 -3.9407998323440552e-02 + + -1.2227380275726318e+00 -4.3506998568773270e-02 + <_> + + 0 -1 1365 2.5888999924063683e-02 + + 1.3409300148487091e-01 -1.1770780086517334e+00 + <_> + + 0 -1 1366 4.8925001174211502e-02 + + -3.0810000374913216e-02 -9.3479502201080322e-01 + <_> + + 0 -1 1367 3.6892998963594437e-02 + + 1.3333700597286224e-01 -1.4998290538787842e+00 + <_> + + 0 -1 1368 7.8929997980594635e-02 + + -1.4538800716400146e-01 1.5631790161132812e+00 + <_> + + 0 -1 1369 2.9006000608205795e-02 + + 1.9383700191974640e-01 -6.7642802000045776e-01 + <_> + + 0 -1 1370 6.3089998438954353e-03 + + -3.7465399503707886e-01 1.0857500135898590e-01 + <_> + + 0 -1 1371 -6.5830998122692108e-02 + + 8.1059402227401733e-01 3.0201999470591545e-02 + <_> + + 0 -1 1372 -6.8965002894401550e-02 + + 8.3772599697113037e-01 -1.7140999436378479e-01 + <_> + + 0 -1 1373 -1.1669100075960159e-01 + + -9.4647198915481567e-01 1.3123199343681335e-01 + <_> + + 0 -1 1374 -1.3060000492259860e-03 + + 4.6007998287677765e-02 -5.2011597156524658e-01 + <_> + + 0 -1 1375 -4.4558998197317123e-02 + + -1.9423669576644897e+00 1.3200700283050537e-01 + <_> + + 0 -1 1376 5.1033001393079758e-02 + + -2.1480999886989594e-01 4.8673900961875916e-01 + <_> + + 0 -1 1377 -3.1578000634908676e-02 + + 5.9989798069000244e-01 7.9159997403621674e-03 + <_> + + 0 -1 1378 2.1020000800490379e-02 + + -2.2069500386714935e-01 5.4046201705932617e-01 + <_> + + 0 -1 1379 -1.3824200630187988e-01 + + 6.2957501411437988e-01 -2.1712999790906906e-02 + <_> + + 0 -1 1380 5.2228998392820358e-02 + + -2.3360900580883026e-01 4.9760800600051880e-01 + <_> + + 0 -1 1381 2.5884000584483147e-02 + + 1.8041999638080597e-01 -2.2039200365543365e-01 + <_> + + 0 -1 1382 -1.2138999998569489e-02 + + -6.9731897115707397e-01 1.5712000429630280e-02 + <_> + + 0 -1 1383 -2.4237999692559242e-02 + + 3.4593299031257629e-01 7.1469999849796295e-02 + <_> + + 0 -1 1384 -2.5272000581026077e-02 + + -8.7583297491073608e-01 -9.8240002989768982e-03 + <_> + + 0 -1 1385 1.2597000226378441e-02 + + 2.3649999499320984e-01 -2.8731200098991394e-01 + <_> + + 0 -1 1386 5.7330999523401260e-02 + + -6.1530999839305878e-02 -2.2326040267944336e+00 + <_> + + 0 -1 1387 1.6671000048518181e-02 + + -1.9850100576877594e-01 4.0810701251029968e-01 + <_> + + 0 -1 1388 -2.2818999364972115e-02 + + 9.6487599611282349e-01 -2.0245699584484100e-01 + <_> + + 0 -1 1389 3.7000001611886546e-05 + + -5.8908998966217041e-02 2.7055400609970093e-01 + <_> + + 0 -1 1390 -7.6700001955032349e-03 + + -4.5317101478576660e-01 8.9628003537654877e-02 + <_> + + 0 -1 1391 9.4085998833179474e-02 + + 1.1604599654674530e-01 -1.0951169729232788e+00 + <_> + + 0 -1 1392 -6.2267001718282700e-02 + + 1.8096530437469482e+00 -1.4773200452327728e-01 + <_> + + 0 -1 1393 1.7416000366210938e-02 + + 2.3068200051784515e-01 -4.2417600750923157e-01 + <_> + + 0 -1 1394 -2.2066000849008560e-02 + + 4.9270299077033997e-01 -2.0630900561809540e-01 + <_> + + 0 -1 1395 -1.0404000058770180e-02 + + 6.0924297571182251e-01 2.8130000457167625e-02 + <_> + + 0 -1 1396 -9.3670003116130829e-03 + + 4.0171200037002563e-01 -2.1681700646877289e-01 + <_> + + 0 -1 1397 -2.9039999470114708e-02 + + -8.4876501560211182e-01 1.4246800541877747e-01 + <_> + + 0 -1 1398 -2.1061999723315239e-02 + + -7.9198300838470459e-01 -1.2595999985933304e-02 + <_> + + 0 -1 1399 -3.7000998854637146e-02 + + -6.7488902807235718e-01 1.2830400466918945e-01 + <_> + + 0 -1 1400 1.0735999792814255e-02 + + 3.6779999732971191e-02 -6.3393002748489380e-01 + <_> + + 0 -1 1401 1.6367599368095398e-01 + + 1.3803899288177490e-01 -4.7189000248908997e-01 + <_> + + 0 -1 1402 9.4917997717857361e-02 + + -1.3855700194835663e-01 1.9492419958114624e+00 + <_> + + 0 -1 1403 3.5261999815702438e-02 + + 1.3721899688243866e-01 -2.1186530590057373e+00 + <_> + + 0 -1 1404 1.2811000458896160e-02 + + -2.0008100569248199e-01 4.9507799744606018e-01 + <_> + 155 + -3.3933560848236084e+00 + + <_> + + 0 -1 1405 1.3904400169849396e-01 + + -4.6581199765205383e-01 7.6431602239608765e-01 + <_> + + 0 -1 1406 1.1916999705135822e-02 + + -9.4398999214172363e-01 3.9726299047470093e-01 + <_> + + 0 -1 1407 -1.0006999596953392e-02 + + 3.2718798518180847e-01 -6.3367402553558350e-01 + <_> + + 0 -1 1408 -6.0479999519884586e-03 + + 2.7427899837493896e-01 -5.7446998357772827e-01 + <_> + + 0 -1 1409 -1.2489999644458294e-03 + + 2.3629300296306610e-01 -6.8593502044677734e-01 + <_> + + 0 -1 1410 3.2382000237703323e-02 + + -5.7630199193954468e-01 2.7492699027061462e-01 + <_> + + 0 -1 1411 -1.3957999646663666e-02 + + -6.1061501502990723e-01 2.4541600048542023e-01 + <_> + + 0 -1 1412 1.1159999994561076e-03 + + -5.6539100408554077e-01 2.7179300785064697e-01 + <_> + + 0 -1 1413 2.7000000045518391e-05 + + -8.0235999822616577e-01 1.1509100347757339e-01 + <_> + + 0 -1 1414 -2.5700000696815550e-04 + + -8.1205898523330688e-01 2.3844699561595917e-01 + <_> + + 0 -1 1415 4.0460000745952129e-03 + + 1.3909600675106049e-01 -6.6163200139999390e-01 + <_> + + 0 -1 1416 1.4356000348925591e-02 + + -1.6485199332237244e-01 4.1901698708534241e-01 + <_> + + 0 -1 1417 -5.5374998599290848e-02 + + 1.4425870180130005e+00 -1.8820199370384216e-01 + <_> + + 0 -1 1418 9.3594998121261597e-02 + + 1.3548299670219421e-01 -9.1636097431182861e-01 + <_> + + 0 -1 1419 2.6624999940395355e-02 + + -3.3748298883438110e-01 3.9233601093292236e-01 + <_> + + 0 -1 1420 3.7469998933374882e-03 + + -1.1615400016307831e-01 4.4399300217628479e-01 + <_> + + 0 -1 1421 -3.1886000186204910e-02 + + -9.9498301744461060e-01 1.6120000509545207e-03 + <_> + + 0 -1 1422 -2.2600000724196434e-02 + + -4.8067399859428406e-01 1.7007300257682800e-01 + <_> + + 0 -1 1423 2.5202000513672829e-02 + + 3.5580001771450043e-02 -8.0215400457382202e-01 + <_> + + 0 -1 1424 -3.1036999076604843e-02 + + -1.0895340442657471e+00 1.8081900477409363e-01 + <_> + + 0 -1 1425 -2.6475999504327774e-02 + + 9.5671200752258301e-01 -2.1049399673938751e-01 + <_> + + 0 -1 1426 -1.3853999786078930e-02 + + -1.0370320081710815e+00 2.2166700661182404e-01 + <_> + + 0 -1 1427 -6.2925003468990326e-02 + + 9.0199398994445801e-01 -1.9085299968719482e-01 + <_> + + 0 -1 1428 -4.4750999659299850e-02 + + -1.0119110345840454e+00 1.4691199362277985e-01 + <_> + + 0 -1 1429 -2.0428000018000603e-02 + + 6.1624497175216675e-01 -2.3552699387073517e-01 + <_> + + 0 -1 1430 -8.0329999327659607e-03 + + -8.3279997110366821e-02 2.1728700399398804e-01 + <_> + + 0 -1 1431 8.7280003353953362e-03 + + 6.5458998084068298e-02 -6.0318702459335327e-01 + <_> + + 0 -1 1432 -2.7202000841498375e-02 + + -9.3447399139404297e-01 1.5270000696182251e-01 + <_> + + 0 -1 1433 -1.6471000388264656e-02 + + -8.4177100658416748e-01 1.3332000002264977e-02 + <_> + + 0 -1 1434 -1.3744000345468521e-02 + + 6.0567200183868408e-01 -9.2021003365516663e-02 + <_> + + 0 -1 1435 2.9164999723434448e-02 + + -2.8114000335335732e-02 -1.4014569520950317e+00 + <_> + + 0 -1 1436 3.7457000464200974e-02 + + 1.3080599904060364e-01 -4.9382498860359192e-01 + <_> + + 0 -1 1437 -2.5070000439882278e-02 + + -1.1289390325546265e+00 -1.4600000344216824e-02 + <_> + + 0 -1 1438 -6.3812002539634705e-02 + + 7.5871598720550537e-01 -1.8200000049546361e-03 + <_> + + 0 -1 1439 -9.3900002539157867e-03 + + 2.9936400055885315e-01 -2.9487800598144531e-01 + <_> + + 0 -1 1440 -7.6000002445653081e-04 + + 1.9725000485777855e-02 1.9993899762630463e-01 + <_> + + 0 -1 1441 -2.1740999072790146e-02 + + -8.5247898101806641e-01 4.9169998615980148e-02 + <_> + + 0 -1 1442 -1.7869999632239342e-02 + + -5.9985999017953873e-02 1.5222500264644623e-01 + <_> + + 0 -1 1443 -2.4831000715494156e-02 + + 3.5603401064872742e-01 -2.6259899139404297e-01 + <_> + + 0 -1 1444 1.5715500712394714e-01 + + 1.5599999460391700e-04 1.0428730249404907e+00 + <_> + + 0 -1 1445 6.9026999175548553e-02 + + -3.3006999641656876e-02 -1.1796669960021973e+00 + <_> + + 0 -1 1446 -1.1021999642252922e-02 + + 5.8987700939178467e-01 -5.7647999376058578e-02 + <_> + + 0 -1 1447 -1.3834999874234200e-02 + + 5.9502798318862915e-01 -2.4418599903583527e-01 + <_> + + 0 -1 1448 -3.0941000208258629e-02 + + -1.1723799705505371e+00 1.6907000541687012e-01 + <_> + + 0 -1 1449 2.1258000284433365e-02 + + -1.8900999799370766e-02 -1.0684759616851807e+00 + <_> + + 0 -1 1450 9.3079999089241028e-02 + + 1.6305600106716156e-01 -1.3375270366668701e+00 + <_> + + 0 -1 1451 2.9635999351739883e-02 + + -2.2524799406528473e-01 4.5400100946426392e-01 + <_> + + 0 -1 1452 -1.2199999764561653e-04 + + 2.7409100532531738e-01 -3.7371399998664856e-01 + <_> + + 0 -1 1453 -4.2098000645637512e-02 + + -7.5828802585601807e-01 1.7137000337243080e-02 + <_> + + 0 -1 1454 -2.2505000233650208e-02 + + -2.2759300470352173e-01 2.3698699474334717e-01 + <_> + + 0 -1 1455 -1.2862999923527241e-02 + + 1.9252400100231171e-01 -3.2127100229263306e-01 + <_> + + 0 -1 1456 2.7860000729560852e-02 + + 1.6723699867725372e-01 -1.0209059715270996e+00 + <_> + + 0 -1 1457 -2.7807999402284622e-02 + + 1.2824759483337402e+00 -1.7225299775600433e-01 + <_> + + 0 -1 1458 -6.1630001291632652e-03 + + -5.4072898626327515e-01 2.3885700106620789e-01 + <_> + + 0 -1 1459 -2.0436000078916550e-02 + + 6.3355398178100586e-01 -2.1090599894523621e-01 + <_> + + 0 -1 1460 -1.2307999655604362e-02 + + -4.9778199195861816e-01 1.7402599751949310e-01 + <_> + + 0 -1 1461 -4.0493998676538467e-02 + + -1.1848740577697754e+00 -3.3890999853610992e-02 + <_> + + 0 -1 1462 2.9657000675797462e-02 + + 2.1740999072790146e-02 1.0069919824600220e+00 + <_> + + 0 -1 1463 6.8379999138414860e-03 + + 2.9217999428510666e-02 -5.9906297922134399e-01 + <_> + + 0 -1 1464 1.6164999455213547e-02 + + -2.1000799536705017e-01 3.7637299299240112e-01 + <_> + + 0 -1 1465 5.0193000584840775e-02 + + 2.5319999549537897e-03 -7.1668201684951782e-01 + <_> + + 0 -1 1466 1.9680000841617584e-03 + + -2.1921400725841522e-01 3.2298699021339417e-01 + <_> + + 0 -1 1467 2.4979999288916588e-02 + + -9.6840001642704010e-03 -7.7572900056838989e-01 + <_> + + 0 -1 1468 -1.5809999778866768e-02 + + 4.4637501239776611e-01 -6.1760000884532928e-02 + <_> + + 0 -1 1469 3.7206999957561493e-02 + + -2.0495399832725525e-01 5.7722198963165283e-01 + <_> + + 0 -1 1470 -7.9264998435974121e-02 + + -7.6745402812957764e-01 1.2550400197505951e-01 + <_> + + 0 -1 1471 -1.7152000218629837e-02 + + -1.4121830463409424e+00 -5.1704000681638718e-02 + <_> + + 0 -1 1472 3.2740000635385513e-02 + + 1.9334000349044800e-01 -6.3633698225021362e-01 + <_> + + 0 -1 1473 -1.1756999790668488e-01 + + 8.4325402975082397e-01 -1.8018600344657898e-01 + <_> + + 0 -1 1474 1.2057200074195862e-01 + + 1.2530000507831573e-01 -2.1213600635528564e+00 + <_> + + 0 -1 1475 4.2779999785125256e-03 + + -4.6604400873184204e-01 8.9643999934196472e-02 + <_> + + 0 -1 1476 -7.2544999420642853e-02 + + 5.1826500892639160e-01 1.6823999583721161e-02 + <_> + + 0 -1 1477 1.7710599303245544e-01 + + -3.0910000205039978e-02 -1.1046639680862427e+00 + <_> + + 0 -1 1478 8.4229996427893639e-03 + + 2.4445800483226776e-01 -3.8613098859786987e-01 + <_> + + 0 -1 1479 -1.3035000301897526e-02 + + 9.8004400730133057e-01 -1.7016500234603882e-01 + <_> + + 0 -1 1480 1.8912000581622124e-02 + + 2.0248499512672424e-01 -3.8545900583267212e-01 + <_> + + 0 -1 1481 2.1447999402880669e-02 + + -2.5717198848724365e-01 3.5181200504302979e-01 + <_> + + 0 -1 1482 6.3357003033161163e-02 + + 1.6994799673557281e-01 -9.1383802890777588e-01 + <_> + + 0 -1 1483 -3.2435998320579529e-02 + + -8.5681599378585815e-01 -2.1680999547243118e-02 + <_> + + 0 -1 1484 -2.3564999923110008e-02 + + 5.6115597486495972e-01 -2.2400000307243317e-04 + <_> + + 0 -1 1485 1.8789000809192657e-02 + + -2.5459799170494080e-01 3.4512901306152344e-01 + <_> + + 0 -1 1486 3.1042000278830528e-02 + + 7.5719999149441719e-03 3.4800198674201965e-01 + <_> + + 0 -1 1487 -1.1226999573409557e-02 + + -6.0219800472259521e-01 4.2814999818801880e-02 + <_> + + 0 -1 1488 -1.2845999561250210e-02 + + 4.2020401358604431e-01 -5.3801000118255615e-02 + <_> + + 0 -1 1489 -1.2791999615728855e-02 + + 2.2724500298500061e-01 -3.2398000359535217e-01 + <_> + + 0 -1 1490 6.8651996552944183e-02 + + 9.3532003462314606e-02 10. + <_> + + 0 -1 1491 5.2789999172091484e-03 + + -2.6926299929618835e-01 3.3303201198577881e-01 + <_> + + 0 -1 1492 -3.8779001682996750e-02 + + -7.2365301847457886e-01 1.7806500196456909e-01 + <_> + + 0 -1 1493 6.1820000410079956e-03 + + -3.5119399428367615e-01 1.6586300730705261e-01 + <_> + + 0 -1 1494 1.7515200376510620e-01 + + 1.1623100191354752e-01 -1.5419290065765381e+00 + <_> + + 0 -1 1495 1.1627999693155289e-01 + + -9.1479998081922531e-03 -9.9842602014541626e-01 + <_> + + 0 -1 1496 -2.2964000701904297e-02 + + 2.0565399527549744e-01 1.5432000160217285e-02 + <_> + + 0 -1 1497 -5.1410000771284103e-02 + + 5.8072400093078613e-01 -2.0118400454521179e-01 + <_> + + 0 -1 1498 2.2474199533462524e-01 + + 1.8728999421000481e-02 1.0829299688339233e+00 + <_> + + 0 -1 1499 9.4860000535845757e-03 + + -3.3171299099922180e-01 1.9902999699115753e-01 + <_> + + 0 -1 1500 -1.1846300214529037e-01 + + 1.3711010217666626e+00 6.8926997482776642e-02 + <_> + + 0 -1 1501 3.7810999900102615e-02 + + -9.3600002583116293e-04 -8.3996999263763428e-01 + <_> + + 0 -1 1502 2.2202000021934509e-02 + + -1.1963999830186367e-02 3.6673998832702637e-01 + <_> + + 0 -1 1503 -3.6366000771522522e-02 + + 3.7866500020027161e-01 -2.7714800834655762e-01 + <_> + + 0 -1 1504 -1.3184699416160583e-01 + + -2.7481179237365723e+00 1.0666900128126144e-01 + <_> + + 0 -1 1505 -4.1655998677015305e-02 + + 4.7524300217628479e-01 -2.3249800503253937e-01 + <_> + + 0 -1 1506 -3.3151999115943909e-02 + + -5.7929402589797974e-01 1.7434400320053101e-01 + <_> + + 0 -1 1507 1.5769999474287033e-02 + + -1.1284000240266323e-02 -8.3701401948928833e-01 + <_> + + 0 -1 1508 -3.9363000541925430e-02 + + 3.4821599721908569e-01 -1.7455400526523590e-01 + <_> + + 0 -1 1509 -6.7849002778530121e-02 + + 1.4225699901580811e+00 -1.4765599370002747e-01 + <_> + + 0 -1 1510 -2.6775000616908073e-02 + + 2.3947000503540039e-01 1.3271999545395374e-02 + <_> + + 0 -1 1511 3.9919000118970871e-02 + + -8.9999996125698090e-03 -7.5938898324966431e-01 + <_> + + 0 -1 1512 1.0065600275993347e-01 + + -1.8685000017285347e-02 7.6245301961898804e-01 + <_> + + 0 -1 1513 -8.1022001802921295e-02 + + -9.0439099073410034e-01 -8.5880002006888390e-03 + <_> + + 0 -1 1514 -2.1258000284433365e-02 + + -2.1319599449634552e-01 2.1919700503349304e-01 + <_> + + 0 -1 1515 -1.0630999691784382e-02 + + 1.9598099589347839e-01 -3.5768100619316101e-01 + <_> + + 0 -1 1516 8.1300002057105303e-04 + + -9.2794999480247498e-02 2.6145899295806885e-01 + <_> + + 0 -1 1517 3.4650000743567944e-03 + + -5.5336099863052368e-01 2.7386000379920006e-02 + <_> + + 0 -1 1518 1.8835999071598053e-02 + + 1.8446099758148193e-01 -6.6934299468994141e-01 + <_> + + 0 -1 1519 -2.5631999596953392e-02 + + 1.9382879734039307e+00 -1.4708900451660156e-01 + <_> + + 0 -1 1520 -4.0939999744296074e-03 + + -2.6451599597930908e-01 2.0733200013637543e-01 + <_> + + 0 -1 1521 -8.9199998183175921e-04 + + -5.5031597614288330e-01 5.0374999642372131e-02 + <_> + + 0 -1 1522 -4.9518000334501266e-02 + + -2.5615389347076416e+00 1.3141700625419617e-01 + <_> + + 0 -1 1523 1.1680999770760536e-02 + + -2.4819800257682800e-01 3.9982700347900391e-01 + <_> + + 0 -1 1524 3.4563999623060226e-02 + + 1.6178800165653229e-01 -7.1418899297714233e-01 + <_> + + 0 -1 1525 -8.2909995689988136e-03 + + 2.2180099785327911e-01 -2.9181700944900513e-01 + <_> + + 0 -1 1526 -2.2358000278472900e-02 + + 3.1044098734855652e-01 -2.7280000504106283e-03 + <_> + + 0 -1 1527 -3.0801000073552132e-02 + + -9.5672702789306641e-01 -8.3400001749396324e-03 + <_> + + 0 -1 1528 4.3779000639915466e-02 + + 1.2556900084018707e-01 -1.1759619712829590e+00 + <_> + + 0 -1 1529 4.3046001344919205e-02 + + -5.8876998722553253e-02 -1.8568470478057861e+00 + <_> + + 0 -1 1530 2.7188999578356743e-02 + + 4.2858000844717026e-02 3.9036700129508972e-01 + <_> + + 0 -1 1531 9.4149997457861900e-03 + + -4.3567001819610596e-02 -1.1094470024108887e+00 + <_> + + 0 -1 1532 9.4311997294425964e-02 + + 4.0256999433040619e-02 9.8442298173904419e-01 + <_> + + 0 -1 1533 1.7025099694728851e-01 + + 2.9510000720620155e-02 -6.9509297609329224e-01 + <_> + + 0 -1 1534 -4.7148000448942184e-02 + + 1.0338569879531860e+00 6.7602001130580902e-02 + <_> + + 0 -1 1535 1.1186300218105316e-01 + + -6.8682998418807983e-02 -2.4985830783843994e+00 + <_> + + 0 -1 1536 -1.4353999868035316e-02 + + -5.9481900930404663e-01 1.5001699328422546e-01 + <_> + + 0 -1 1537 3.4024000167846680e-02 + + -6.4823001623153687e-02 -2.1382639408111572e+00 + <_> + + 0 -1 1538 2.1601999178528786e-02 + + 5.5309999734163284e-02 7.8292900323867798e-01 + <_> + + 0 -1 1539 2.1771999076008797e-02 + + -7.1279997937381268e-03 -7.2148102521896362e-01 + <_> + + 0 -1 1540 8.2416996359825134e-02 + + 1.4609499275684357e-01 -1.3636670112609863e+00 + <_> + + 0 -1 1541 8.4671996533870697e-02 + + -1.7784699797630310e-01 7.2857701778411865e-01 + <_> + + 0 -1 1542 -5.5128000676631927e-02 + + -5.9402400255203247e-01 1.9357800483703613e-01 + <_> + + 0 -1 1543 -6.4823001623153687e-02 + + -1.0783840417861938e+00 -4.0734000504016876e-02 + <_> + + 0 -1 1544 -2.2769000381231308e-02 + + 7.7900201082229614e-01 3.4960000775754452e-03 + <_> + + 0 -1 1545 5.4756000638008118e-02 + + -6.5683998167514801e-02 -1.8188409805297852e+00 + <_> + + 0 -1 1546 -8.9000001025851816e-05 + + -1.7891999334096909e-02 2.0768299698829651e-01 + <_> + + 0 -1 1547 9.8361998796463013e-02 + + -5.5946998298168182e-02 -1.4153920412063599e+00 + <_> + + 0 -1 1548 -7.0930002257227898e-03 + + 3.4135299921035767e-01 -1.2089899927377701e-01 + <_> + + 0 -1 1549 5.0278000533580780e-02 + + -2.6286700367927551e-01 2.5797298550605774e-01 + <_> + + 0 -1 1550 -5.7870000600814819e-03 + + -1.3178600370883942e-01 1.7350199818611145e-01 + <_> + + 0 -1 1551 1.3973999768495560e-02 + + 2.8518000617623329e-02 -6.1152201890945435e-01 + <_> + + 0 -1 1552 2.1449999883770943e-02 + + 2.6181999593973160e-02 3.0306598544120789e-01 + <_> + + 0 -1 1553 -2.9214000329375267e-02 + + 4.4940599799156189e-01 -2.2803099453449249e-01 + <_> + + 0 -1 1554 4.8099999548867345e-04 + + -1.9879999756813049e-01 2.0744499564170837e-01 + <_> + + 0 -1 1555 1.7109999898821115e-03 + + -5.4037201404571533e-01 6.7865997552871704e-02 + <_> + + 0 -1 1556 8.6660003289580345e-03 + + -1.3128000311553478e-02 5.2297902107238770e-01 + <_> + + 0 -1 1557 6.3657999038696289e-02 + + 6.8299002945423126e-02 -4.9235099554061890e-01 + <_> + + 0 -1 1558 -2.7968000620603561e-02 + + 6.8183898925781250e-01 7.8781001269817352e-02 + <_> + + 0 -1 1559 4.8953998833894730e-02 + + -2.0622399449348450e-01 5.0388097763061523e-01 + <_> + 169 + -3.2396929264068604e+00 + + <_> + + 0 -1 1560 -2.9312999919056892e-02 + + 7.1284699440002441e-01 -5.8230698108673096e-01 + <_> + + 0 -1 1561 1.2415099889039993e-01 + + -3.6863499879837036e-01 6.0067200660705566e-01 + <_> + + 0 -1 1562 7.9349996522068977e-03 + + -8.6008298397064209e-01 2.1724699437618256e-01 + <_> + + 0 -1 1563 3.0365999788045883e-02 + + -2.7186998724937439e-01 6.1247897148132324e-01 + <_> + + 0 -1 1564 2.5218000635504723e-02 + + -3.4748300909996033e-01 5.0427699089050293e-01 + <_> + + 0 -1 1565 1.0014000348746777e-02 + + -3.1898999214172363e-01 4.1376799345016479e-01 + <_> + + 0 -1 1566 -1.6775000840425491e-02 + + -6.9048100709915161e-01 9.4830997288227081e-02 + <_> + + 0 -1 1567 -2.6950000319629908e-03 + + -2.0829799771308899e-01 2.3737199604511261e-01 + <_> + + 0 -1 1568 4.2257998138666153e-02 + + -4.9366700649261475e-01 1.8170599639415741e-01 + <_> + + 0 -1 1569 -4.8505000770092010e-02 + + 1.3429640531539917e+00 3.9769001305103302e-02 + <_> + + 0 -1 1570 2.8992999345064163e-02 + + 4.6496000140905380e-02 -8.1643497943878174e-01 + <_> + + 0 -1 1571 -4.0089000016450882e-02 + + -7.1197801828384399e-01 2.2553899884223938e-01 + <_> + + 0 -1 1572 -4.1021998971700668e-02 + + 1.0057929754257202e+00 -1.9690200686454773e-01 + <_> + + 0 -1 1573 1.1838000267744064e-02 + + -1.2600000016391277e-02 8.0767101049423218e-01 + <_> + + 0 -1 1574 -2.1328000351786613e-02 + + -8.2023900747299194e-01 2.0524999126791954e-02 + <_> + + 0 -1 1575 -2.3904999718070030e-02 + + 5.4210501909255981e-01 -7.4767000973224640e-02 + <_> + + 0 -1 1576 1.8008999526500702e-02 + + -3.3827701210975647e-01 4.2358601093292236e-01 + <_> + + 0 -1 1577 -4.3614000082015991e-02 + + -1.1983489990234375e+00 1.5566200017929077e-01 + <_> + + 0 -1 1578 -9.2449998483061790e-03 + + -8.9029997587203979e-01 1.1003999970853329e-02 + <_> + + 0 -1 1579 4.7485001385211945e-02 + + 1.6664099693298340e-01 -9.0764498710632324e-01 + <_> + + 0 -1 1580 -1.4233999885618687e-02 + + 6.2695199251174927e-01 -2.5791200995445251e-01 + <_> + + 0 -1 1581 3.8010000716894865e-03 + + -2.8229999542236328e-01 2.6624599099159241e-01 + <_> + + 0 -1 1582 3.4330000635236502e-03 + + -6.3771998882293701e-01 9.8422996699810028e-02 + <_> + + 0 -1 1583 -2.9221000149846077e-02 + + -7.6769900321960449e-01 2.2634500265121460e-01 + <_> + + 0 -1 1584 -6.4949998632073402e-03 + + 4.5600101351737976e-01 -2.6528900861740112e-01 + <_> + + 0 -1 1585 -3.0034000054001808e-02 + + -7.6551097631454468e-01 1.4009299874305725e-01 + <_> + + 0 -1 1586 7.8360000625252724e-03 + + 4.6755999326705933e-02 -7.2356200218200684e-01 + <_> + + 0 -1 1587 8.8550001382827759e-03 + + -4.9141999334096909e-02 5.1472699642181396e-01 + <_> + + 0 -1 1588 9.5973998308181763e-02 + + -2.0068999379873276e-02 -1.0850950479507446e+00 + <_> + + 0 -1 1589 -3.2876998186111450e-02 + + -9.5875298976898193e-01 1.4543600380420685e-01 + <_> + + 0 -1 1590 -1.3384000398218632e-02 + + -7.0013600587844849e-01 2.9157999902963638e-02 + <_> + + 0 -1 1591 1.5235999599099159e-02 + + -2.8235700726509094e-01 2.5367999076843262e-01 + <_> + + 0 -1 1592 1.2054000049829483e-02 + + -2.5303399562835693e-01 4.6526700258255005e-01 + <_> + + 0 -1 1593 -7.6295003294944763e-02 + + -6.9915801286697388e-01 1.3217200338840485e-01 + <_> + + 0 -1 1594 -1.2040000408887863e-02 + + 4.5894598960876465e-01 -2.3856499791145325e-01 + <_> + + 0 -1 1595 2.1916000172495842e-02 + + 1.8268600106239319e-01 -6.1629700660705566e-01 + <_> + + 0 -1 1596 -2.7330000884830952e-03 + + -6.3257902860641479e-01 3.4219000488519669e-02 + <_> + + 0 -1 1597 -4.8652000725269318e-02 + + -1.0297729969024658e+00 1.7386500537395477e-01 + <_> + + 0 -1 1598 -1.0463999584317207e-02 + + 3.4757301211357117e-01 -2.7464100718498230e-01 + <_> + + 0 -1 1599 -6.6550001502037048e-03 + + -2.8980299830436707e-01 2.4037900567054749e-01 + <_> + + 0 -1 1600 8.5469996556639671e-03 + + -4.4340500235557556e-01 1.4267399907112122e-01 + <_> + + 0 -1 1601 1.9913999363780022e-02 + + 1.7740400135517120e-01 -2.4096299707889557e-01 + <_> + + 0 -1 1602 2.2012999281287193e-02 + + -1.0812000371515751e-02 -9.4690799713134766e-01 + <_> + + 0 -1 1603 -5.2179001271724701e-02 + + 1.6547499895095825e+00 9.6487000584602356e-02 + <_> + + 0 -1 1604 1.9698999822139740e-02 + + -6.7560002207756042e-03 -8.6311501264572144e-01 + <_> + + 0 -1 1605 2.3040000349283218e-02 + + -2.3519999813288450e-03 3.8531300425529480e-01 + <_> + + 0 -1 1606 -1.5038000419735909e-02 + + -6.1905699968338013e-01 3.1077999621629715e-02 + <_> + + 0 -1 1607 -4.9956001341342926e-02 + + 7.0657497644424438e-01 4.7880999743938446e-02 + <_> + + 0 -1 1608 -6.9269999861717224e-02 + + 3.9212900400161743e-01 -2.3848000168800354e-01 + <_> + + 0 -1 1609 4.7399997711181641e-03 + + -2.4309000000357628e-02 2.5386300683021545e-01 + <_> + + 0 -1 1610 -3.3923998475074768e-02 + + 4.6930399537086487e-01 -2.3321899771690369e-01 + <_> + + 0 -1 1611 -1.6231000423431396e-02 + + 3.2319200038909912e-01 -2.0545600354671478e-01 + <_> + + 0 -1 1612 -5.0193000584840775e-02 + + -1.2277870178222656e+00 -4.0798000991344452e-02 + <_> + + 0 -1 1613 5.6944001466035843e-02 + + 4.5184001326560974e-02 6.0197502374649048e-01 + <_> + + 0 -1 1614 4.0936999022960663e-02 + + -1.6772800683975220e-01 8.9819300174713135e-01 + <_> + + 0 -1 1615 -3.0839999672025442e-03 + + 3.3716198801994324e-01 -2.7240800857543945e-01 + <_> + + 0 -1 1616 -3.2600000500679016e-02 + + -8.5446500778198242e-01 1.9664999097585678e-02 + <_> + + 0 -1 1617 9.8480999469757080e-02 + + 5.4742000997066498e-02 6.3827300071716309e-01 + <_> + + 0 -1 1618 -3.8185000419616699e-02 + + 5.2274698019027710e-01 -2.3384800553321838e-01 + <_> + + 0 -1 1619 -4.5917000621557236e-02 + + 6.2829202413558960e-01 3.2859001308679581e-02 + <_> + + 0 -1 1620 -1.1955499649047852e-01 + + -6.1572700738906860e-01 3.4680001437664032e-02 + <_> + + 0 -1 1621 -1.2044399976730347e-01 + + -8.4380000829696655e-01 1.6530700027942657e-01 + <_> + + 0 -1 1622 7.0619001984596252e-02 + + -6.3261002302169800e-02 -1.9863929748535156e+00 + <_> + + 0 -1 1623 8.4889996796846390e-03 + + -1.7663399875164032e-01 3.8011199235916138e-01 + <_> + + 0 -1 1624 2.2710999473929405e-02 + + -2.7605999261140823e-02 -9.1921401023864746e-01 + <_> + + 0 -1 1625 4.9700000090524554e-04 + + -2.4293200671672821e-01 2.2878900170326233e-01 + <_> + + 0 -1 1626 3.4651998430490494e-02 + + -2.3705999553203583e-01 5.4010999202728271e-01 + <_> + + 0 -1 1627 -4.4700000435113907e-03 + + 3.9078998565673828e-01 -1.2693800032138824e-01 + <_> + + 0 -1 1628 2.3643000051379204e-02 + + -2.6663699746131897e-01 3.2312598824501038e-01 + <_> + + 0 -1 1629 1.2813000008463860e-02 + + 1.7540800571441650e-01 -6.0787999629974365e-01 + <_> + + 0 -1 1630 -1.1250999756157398e-02 + + -1.0852589607238770e+00 -2.8046000748872757e-02 + <_> + + 0 -1 1631 -4.1535001248121262e-02 + + 7.1887397766113281e-01 2.7982000261545181e-02 + <_> + + 0 -1 1632 -9.3470998108386993e-02 + + -1.1906319856643677e+00 -4.4810999184846878e-02 + <_> + + 0 -1 1633 -2.7249999344348907e-02 + + 6.2942498922348022e-01 9.5039997249841690e-03 + <_> + + 0 -1 1634 -2.1759999915957451e-02 + + 1.3233649730682373e+00 -1.5027000010013580e-01 + <_> + + 0 -1 1635 -9.6890004351735115e-03 + + -3.3947101235389709e-01 1.7085799574851990e-01 + <_> + + 0 -1 1636 6.9395996630191803e-02 + + -2.5657799839973450e-01 4.7652098536491394e-01 + <_> + + 0 -1 1637 3.1208999454975128e-02 + + 1.4154000580310822e-01 -3.4942001104354858e-01 + <_> + + 0 -1 1638 -4.9727000296115875e-02 + + -1.1675560474395752e+00 -4.0757998824119568e-02 + <_> + + 0 -1 1639 -2.0301999524235725e-02 + + -3.9486399292945862e-01 1.5814900398254395e-01 + <_> + + 0 -1 1640 -1.5367000363767147e-02 + + 4.9300000071525574e-01 -2.0092099905014038e-01 + <_> + + 0 -1 1641 -5.0735000520944595e-02 + + 1.8736059665679932e+00 8.6730003356933594e-02 + <_> + + 0 -1 1642 -2.0726000890135765e-02 + + -8.8938397169113159e-01 -7.3199998587369919e-03 + <_> + + 0 -1 1643 -3.0993999913334846e-02 + + -1.1664899587631226e+00 1.4274600148200989e-01 + <_> + + 0 -1 1644 -4.4269999489188194e-03 + + -6.6815102100372314e-01 4.4120000675320625e-03 + <_> + + 0 -1 1645 -4.5743998140096664e-02 + + -4.7955200076103210e-01 1.5121999382972717e-01 + <_> + + 0 -1 1646 1.6698999330401421e-02 + + 1.2048599869012833e-01 -4.5235899090766907e-01 + <_> + + 0 -1 1647 3.2210000790655613e-03 + + -7.7615000307559967e-02 2.7846598625183105e-01 + <_> + + 0 -1 1648 2.4434000253677368e-02 + + -1.9987100362777710e-01 6.7253702878952026e-01 + <_> + + 0 -1 1649 -7.9677999019622803e-02 + + 9.2222398519515991e-01 9.2557996511459351e-02 + <_> + + 0 -1 1650 4.4530000537633896e-02 + + -2.6690500974655151e-01 3.3320501446723938e-01 + <_> + + 0 -1 1651 -1.2528300285339355e-01 + + -5.4253101348876953e-01 1.3976299762725830e-01 + <_> + + 0 -1 1652 1.7971999943256378e-02 + + 1.8219999969005585e-02 -6.8048501014709473e-01 + <_> + + 0 -1 1653 1.9184000790119171e-02 + + -1.2583999894559383e-02 5.4126697778701782e-01 + <_> + + 0 -1 1654 4.0024001151323318e-02 + + -1.7638799548149109e-01 7.8810399770736694e-01 + <_> + + 0 -1 1655 1.3558999635279179e-02 + + 2.0737600326538086e-01 -4.7744300961494446e-01 + <_> + + 0 -1 1656 1.6220999881625175e-02 + + 2.3076999932527542e-02 -6.1182099580764771e-01 + <_> + + 0 -1 1657 1.1229000054299831e-02 + + -1.7728000879287720e-02 4.1764199733734131e-01 + <_> + + 0 -1 1658 3.9193000644445419e-02 + + -1.8948499858379364e-01 7.4019300937652588e-01 + <_> + + 0 -1 1659 -9.5539996400475502e-03 + + 4.0947100520133972e-01 -1.3508899509906769e-01 + <_> + + 0 -1 1660 2.7878999710083008e-02 + + -2.0350700616836548e-01 6.1625397205352783e-01 + <_> + + 0 -1 1661 -2.3600999265909195e-02 + + -1.6967060565948486e+00 1.4633199572563171e-01 + <_> + + 0 -1 1662 2.6930000633001328e-02 + + -3.0401999130845070e-02 -1.0909470319747925e+00 + <_> + + 0 -1 1663 2.8999999631196260e-04 + + -2.0076000690460205e-01 2.2314099967479706e-01 + <_> + + 0 -1 1664 -4.1124999523162842e-02 + + -4.5242199301719666e-01 5.7392001152038574e-02 + <_> + + 0 -1 1665 6.6789998672902584e-03 + + 2.3824900388717651e-01 -2.1262100338935852e-01 + <_> + + 0 -1 1666 4.7864999622106552e-02 + + -1.8194800615310669e-01 6.1918401718139648e-01 + <_> + + 0 -1 1667 -3.1679999083280563e-03 + + -2.7393200993537903e-01 2.5017300248146057e-01 + <_> + + 0 -1 1668 -8.6230002343654633e-03 + + -4.6280300617218018e-01 4.2397998273372650e-02 + <_> + + 0 -1 1669 -7.4350000359117985e-03 + + 4.1796800494194031e-01 -1.7079999670386314e-03 + <_> + + 0 -1 1670 -1.8769999733194709e-03 + + 1.4602300524711609e-01 -3.3721101284027100e-01 + <_> + + 0 -1 1671 -8.6226001381874084e-02 + + 7.5143402814865112e-01 1.0711999610066414e-02 + <_> + + 0 -1 1672 4.6833999454975128e-02 + + -1.9119599461555481e-01 4.8414900898933411e-01 + <_> + + 0 -1 1673 -9.2000002041459084e-05 + + 3.5220399498939514e-01 -1.7333300411701202e-01 + <_> + + 0 -1 1674 -1.6343999654054642e-02 + + -6.4397698640823364e-01 9.0680001303553581e-03 + <_> + + 0 -1 1675 4.5703999698162079e-02 + + 1.8216000869870186e-02 3.1970798969268799e-01 + <_> + + 0 -1 1676 -2.7382999658584595e-02 + + 1.0564049482345581e+00 -1.7276400327682495e-01 + <_> + + 0 -1 1677 -2.7602000162005424e-02 + + 2.9715499281883240e-01 -9.4600003212690353e-03 + <_> + + 0 -1 1678 7.6939999125897884e-03 + + -2.1660299599170685e-01 4.7385200858116150e-01 + <_> + + 0 -1 1679 -7.0500001311302185e-04 + + 2.4048799276351929e-01 -2.6776000857353210e-01 + <_> + + 0 -1 1680 1.1054199934005737e-01 + + -3.3539000898599625e-02 -1.0233880281448364e+00 + <_> + + 0 -1 1681 6.8765997886657715e-02 + + -4.3239998631179333e-03 5.7153397798538208e-01 + <_> + + 0 -1 1682 1.7999999690800905e-03 + + 7.7574998140335083e-02 -4.2092698812484741e-01 + <_> + + 0 -1 1683 1.9232000410556793e-01 + + 8.2021996378898621e-02 2.8810169696807861e+00 + <_> + + 0 -1 1684 1.5742099285125732e-01 + + -1.3708199560642242e-01 2.0890059471130371e+00 + <_> + + 0 -1 1685 -4.9387000501155853e-02 + + -1.8610910177230835e+00 1.4332099258899689e-01 + <_> + + 0 -1 1686 5.1929000765085220e-02 + + -1.8737000226974487e-01 5.4231601953506470e-01 + <_> + + 0 -1 1687 4.9965001642704010e-02 + + 1.4175300300121307e-01 -1.5625779628753662e+00 + <_> + + 0 -1 1688 -4.2633000761270523e-02 + + 1.6059479713439941e+00 -1.4712899923324585e-01 + <_> + + 0 -1 1689 -3.7553999572992325e-02 + + -8.0974900722503662e-01 1.3256999850273132e-01 + <_> + + 0 -1 1690 -3.7174999713897705e-02 + + -1.3945020437240601e+00 -5.7055000215768814e-02 + <_> + + 0 -1 1691 1.3945999555289745e-02 + + 3.3427000045776367e-02 5.7474797964096069e-01 + <_> + + 0 -1 1692 -4.4800000614486635e-04 + + -5.5327498912811279e-01 2.1952999755740166e-02 + <_> + + 0 -1 1693 3.1993001699447632e-02 + + 2.0340999588370323e-02 3.7459200620651245e-01 + <_> + + 0 -1 1694 -4.2799999937415123e-03 + + 4.4428700208663940e-01 -2.2999699413776398e-01 + <_> + + 0 -1 1695 9.8550003021955490e-03 + + 1.8315799534320831e-01 -4.0964999794960022e-01 + <_> + + 0 -1 1696 9.3356996774673462e-02 + + -6.3661001622676849e-02 -1.6929290294647217e+00 + <_> + + 0 -1 1697 1.7209999263286591e-02 + + 2.0153899490833282e-01 -4.6061098575592041e-01 + <_> + + 0 -1 1698 8.4319999441504478e-03 + + -3.2003998756408691e-01 1.5312199294567108e-01 + <_> + + 0 -1 1699 -1.4054999686777592e-02 + + 8.6882400512695312e-01 3.2575000077486038e-02 + <_> + + 0 -1 1700 -7.7180000953376293e-03 + + 6.3686698675155640e-01 -1.8425500392913818e-01 + <_> + + 0 -1 1701 2.8005000203847885e-02 + + 1.7357499897480011e-01 -4.7883599996566772e-01 + <_> + + 0 -1 1702 -1.8884999677538872e-02 + + 2.4101600050926208e-01 -2.6547598838806152e-01 + <_> + + 0 -1 1703 -1.8585000187158585e-02 + + 5.4232501983642578e-01 5.3633000701665878e-02 + <_> + + 0 -1 1704 -3.6437001079320908e-02 + + 2.3908898830413818e+00 -1.3634699583053589e-01 + <_> + + 0 -1 1705 3.2455001026391983e-02 + + 1.5910699963569641e-01 -6.7581498622894287e-01 + <_> + + 0 -1 1706 5.9781998395919800e-02 + + -2.3479999508708715e-03 -7.3053699731826782e-01 + <_> + + 0 -1 1707 9.8209995776414871e-03 + + -1.1444099992513657e-01 3.0570301413536072e-01 + <_> + + 0 -1 1708 -3.5163998603820801e-02 + + -1.0511469841003418e+00 -3.3103000372648239e-02 + <_> + + 0 -1 1709 2.7429999317973852e-03 + + -2.0135399699211121e-01 3.2754099369049072e-01 + <_> + + 0 -1 1710 8.1059997901320457e-03 + + -2.1383500099182129e-01 4.3362098932266235e-01 + <_> + + 0 -1 1711 8.8942997157573700e-02 + + 1.0940899699926376e-01 -4.7609338760375977e+00 + <_> + + 0 -1 1712 -3.0054999515414238e-02 + + -1.7169300317764282e+00 -6.0919001698493958e-02 + <_> + + 0 -1 1713 -2.1734999492764473e-02 + + 6.4778900146484375e-01 -3.2830998301506042e-02 + <_> + + 0 -1 1714 3.7648998200893402e-02 + + -1.0060000233352184e-02 -7.6569098234176636e-01 + <_> + + 0 -1 1715 2.7189999818801880e-03 + + 1.9888900220394135e-01 -8.2479000091552734e-02 + <_> + + 0 -1 1716 -1.0548000223934650e-02 + + -8.6613601446151733e-01 -2.5986000895500183e-02 + <_> + + 0 -1 1717 1.2966300547122955e-01 + + 1.3911999762058258e-01 -2.2271950244903564e+00 + <_> + + 0 -1 1718 -1.7676999792456627e-02 + + 3.3967700600624084e-01 -2.3989599943161011e-01 + <_> + + 0 -1 1719 -7.7051997184753418e-02 + + -2.5017969608306885e+00 1.2841999530792236e-01 + <_> + + 0 -1 1720 -1.9230000674724579e-02 + + 5.0641202926635742e-01 -1.9751599431037903e-01 + <_> + + 0 -1 1721 -5.1222998648881912e-02 + + -2.9333369731903076e+00 1.3858500123023987e-01 + <_> + + 0 -1 1722 2.0830000285059214e-03 + + -6.0043597221374512e-01 2.9718000441789627e-02 + <_> + + 0 -1 1723 2.5418000295758247e-02 + + 3.3915799856185913e-01 -1.4392000436782837e-01 + <_> + + 0 -1 1724 -2.3905999958515167e-02 + + -1.1082680225372314e+00 -4.7377001494169235e-02 + <_> + + 0 -1 1725 -6.3740001060068607e-03 + + 4.4533699750900269e-01 -6.7052997648715973e-02 + <_> + + 0 -1 1726 -3.7698999047279358e-02 + + -1.0406579971313477e+00 -4.1790001094341278e-02 + <_> + + 0 -1 1727 2.1655100584030151e-01 + + 3.3863000571727753e-02 8.2017302513122559e-01 + <_> + + 0 -1 1728 -1.3400999829173088e-02 + + 5.2903497219085693e-01 -1.9133000075817108e-01 + <_> + 196 + -3.2103500366210938e+00 + + <_> + + 0 -1 1729 7.1268998086452484e-02 + + -5.3631198406219482e-01 6.0715299844741821e-01 + <_> + + 0 -1 1730 5.6111000478267670e-02 + + -5.0141602754592896e-01 4.3976101279258728e-01 + <_> + + 0 -1 1731 4.0463998913764954e-02 + + -3.2922199368476868e-01 5.4834699630737305e-01 + <_> + + 0 -1 1732 6.3155002892017365e-02 + + -3.1701698899269104e-01 4.6152999997138977e-01 + <_> + + 0 -1 1733 1.0320999659597874e-02 + + 1.0694999992847443e-01 -9.8243898153305054e-01 + <_> + + 0 -1 1734 6.2606997787952423e-02 + + -1.4329700171947479e-01 7.1095001697540283e-01 + <_> + + 0 -1 1735 -3.9416000247001648e-02 + + 9.4380199909210205e-01 -2.1572099626064301e-01 + <_> + + 0 -1 1736 -5.3960001096129417e-03 + + -5.4611998796463013e-01 2.5303798913955688e-01 + <_> + + 0 -1 1737 1.0773199796676636e-01 + + 1.2496000155806541e-02 -1.0809199810028076e+00 + <_> + + 0 -1 1738 1.6982000321149826e-02 + + -3.1536400318145752e-01 5.1239997148513794e-01 + <_> + + 0 -1 1739 3.1216999515891075e-02 + + -4.5199999585747719e-03 -1.2443480491638184e+00 + <_> + + 0 -1 1740 -2.3106999695301056e-02 + + -7.6492899656295776e-01 2.0640599727630615e-01 + <_> + + 0 -1 1741 -1.1203999631106853e-02 + + 2.4092699587345123e-01 -3.5142099857330322e-01 + <_> + + 0 -1 1742 -4.7479998320341110e-03 + + -9.7007997334003448e-02 2.0638099312782288e-01 + <_> + + 0 -1 1743 -1.7358999699354172e-02 + + -7.9020297527313232e-01 2.1852999925613403e-02 + <_> + + 0 -1 1744 1.8851999193429947e-02 + + -1.0394600033760071e-01 5.4844200611114502e-01 + <_> + + 0 -1 1745 7.2249998338520527e-03 + + -4.0409401059150696e-01 2.6763799786567688e-01 + <_> + + 0 -1 1746 1.8915999680757523e-02 + + 2.0508000254631042e-01 -1.0206340551376343e+00 + <_> + + 0 -1 1747 3.1156999990344048e-02 + + 1.2400000123307109e-03 -8.7293499708175659e-01 + <_> + + 0 -1 1748 2.0951999351382256e-02 + + -5.5559999309480190e-03 8.0356198549270630e-01 + <_> + + 0 -1 1749 1.1291000060737133e-02 + + -3.6478400230407715e-01 2.2767899930477142e-01 + <_> + + 0 -1 1750 -5.7011000812053680e-02 + + -1.4295619726181030e+00 1.4322000741958618e-01 + <_> + + 0 -1 1751 7.2194002568721771e-02 + + -4.1850000619888306e-02 -1.9111829996109009e+00 + <_> + + 0 -1 1752 -1.9874000921845436e-02 + + 2.6425498723983765e-01 -3.2617700099945068e-01 + <_> + + 0 -1 1753 -1.6692999750375748e-02 + + -8.3907800912857056e-01 4.0799999260343611e-04 + <_> + + 0 -1 1754 -3.9834998548030853e-02 + + -4.8858499526977539e-01 1.6436100006103516e-01 + <_> + + 0 -1 1755 2.7009999379515648e-02 + + -1.8862499296665192e-01 8.3419400453567505e-01 + <_> + + 0 -1 1756 -3.9420002140104771e-03 + + 2.3231500387191772e-01 -7.2360001504421234e-02 + <_> + + 0 -1 1757 2.2833000868558884e-02 + + -3.5884000360965729e-02 -1.1549400091171265e+00 + <_> + + 0 -1 1758 -6.8888001143932343e-02 + + -1.7837309837341309e+00 1.5159000456333160e-01 + <_> + + 0 -1 1759 4.3097000569105148e-02 + + -2.1608099341392517e-01 5.0624102354049683e-01 + <_> + + 0 -1 1760 8.6239995434880257e-03 + + -1.7795599997043610e-01 2.8957900404930115e-01 + <_> + + 0 -1 1761 1.4561000280082226e-02 + + -1.1408000253140926e-02 -8.9402002096176147e-01 + <_> + + 0 -1 1762 -1.1501000262796879e-02 + + 3.0171999335289001e-01 -4.3659001588821411e-02 + <_> + + 0 -1 1763 -1.0971499979496002e-01 + + -9.5147097110748291e-01 -1.9973000511527061e-02 + <_> + + 0 -1 1764 4.5228000730276108e-02 + + 3.3110998570919037e-02 9.6619802713394165e-01 + <_> + + 0 -1 1765 -2.7047999203205109e-02 + + 9.7963601350784302e-01 -1.7261900007724762e-01 + <_> + + 0 -1 1766 1.8030999228358269e-02 + + -2.0801000297069550e-02 2.7385899424552917e-01 + <_> + + 0 -1 1767 5.0524998456239700e-02 + + -5.6802999228239059e-02 -1.7775089740753174e+00 + <_> + + 0 -1 1768 -2.9923999682068825e-02 + + 6.5329200029373169e-01 -2.3537000641226768e-02 + <_> + + 0 -1 1769 3.8058001548051834e-02 + + 2.6317000389099121e-02 -7.0665699243545532e-01 + <_> + + 0 -1 1770 1.8563899397850037e-01 + + -5.6039998307824135e-03 3.2873699069023132e-01 + <_> + + 0 -1 1771 -4.0670000016689301e-03 + + 3.4204798936843872e-01 -3.0171599984169006e-01 + <_> + + 0 -1 1772 1.0108999907970428e-02 + + -7.3600001633167267e-03 5.7981598377227783e-01 + <_> + + 0 -1 1773 -1.1567000299692154e-02 + + -5.2722197771072388e-01 4.6447999775409698e-02 + <_> + + 0 -1 1774 -6.5649999305605888e-03 + + -5.8529102802276611e-01 1.9101899862289429e-01 + <_> + + 0 -1 1775 1.0582000017166138e-02 + + 2.1073000505566597e-02 -6.8892598152160645e-01 + <_> + + 0 -1 1776 -2.0304000005125999e-02 + + -3.6400699615478516e-01 1.5338799357414246e-01 + <_> + + 0 -1 1777 2.3529999889433384e-03 + + 3.6164000630378723e-02 -5.9825098514556885e-01 + <_> + + 0 -1 1778 -1.4690000098198652e-03 + + -1.4707699418067932e-01 3.7507998943328857e-01 + <_> + + 0 -1 1779 8.6449999362230301e-03 + + -2.1708500385284424e-01 5.1936799287796021e-01 + <_> + + 0 -1 1780 -2.4326000362634659e-02 + + -1.0846769809722900e+00 1.4084799587726593e-01 + <_> + + 0 -1 1781 7.4418999254703522e-02 + + -1.5513800084590912e-01 1.1822769641876221e+00 + <_> + + 0 -1 1782 1.7077999189496040e-02 + + 4.4231001287698746e-02 9.1561102867126465e-01 + <_> + + 0 -1 1783 -2.4577999487519264e-02 + + -1.5504100322723389e+00 -5.4745998233556747e-02 + <_> + + 0 -1 1784 3.0205000191926956e-02 + + 1.6662800312042236e-01 -1.0001239776611328e+00 + <_> + + 0 -1 1785 1.2136000208556652e-02 + + -7.7079099416732788e-01 -4.8639997839927673e-03 + <_> + + 0 -1 1786 8.6717002093791962e-02 + + 1.1061699688434601e-01 -1.6857999563217163e+00 + <_> + + 0 -1 1787 -4.2309001088142395e-02 + + 1.1075930595397949e+00 -1.5438599884510040e-01 + <_> + + 0 -1 1788 -2.6420000940561295e-03 + + 2.7451899647712708e-01 -1.8456199765205383e-01 + <_> + + 0 -1 1789 -5.6662000715732574e-02 + + -8.0625599622726440e-01 -1.6928000375628471e-02 + <_> + + 0 -1 1790 2.3475000634789467e-02 + + 1.4187699556350708e-01 -2.5500899553298950e-01 + <_> + + 0 -1 1791 -2.0803000777959824e-02 + + 1.9826300442218781e-01 -3.1171199679374695e-01 + <_> + + 0 -1 1792 7.2599998675286770e-03 + + -5.0590999424457550e-02 4.1923800110816956e-01 + <_> + + 0 -1 1793 3.4160000085830688e-01 + + -1.6674900054931641e-01 9.2748600244522095e-01 + <_> + + 0 -1 1794 6.2029999680817127e-03 + + -1.2625899910926819e-01 4.0445300936698914e-01 + <_> + + 0 -1 1795 3.2692000269889832e-02 + + -3.2634999603033066e-02 -9.8939800262451172e-01 + <_> + + 0 -1 1796 2.1100000594742596e-04 + + -6.4534001052379608e-02 2.5473698973655701e-01 + <_> + + 0 -1 1797 7.2100001852959394e-04 + + -3.6618599295616150e-01 1.1973100155591965e-01 + <_> + + 0 -1 1798 5.4490998387336731e-02 + + 1.2073499709367752e-01 -1.0291390419006348e+00 + <_> + + 0 -1 1799 -1.0141000151634216e-02 + + -5.2177202701568604e-01 3.3734999597072601e-02 + <_> + + 0 -1 1800 -1.8815999850630760e-02 + + 6.5181797742843628e-01 1.3399999588727951e-03 + <_> + + 0 -1 1801 -5.3480002097785473e-03 + + 1.7370699346065521e-01 -3.4132000803947449e-01 + <_> + + 0 -1 1802 -1.0847000405192375e-02 + + -1.9699899852275848e-01 1.5045499801635742e-01 + <_> + + 0 -1 1803 -4.9926001578569412e-02 + + -5.0888502597808838e-01 3.0762000009417534e-02 + <_> + + 0 -1 1804 1.2160000391304493e-02 + + -6.9251999258995056e-02 1.8745499849319458e-01 + <_> + + 0 -1 1805 -2.2189998999238014e-03 + + -4.0849098563194275e-01 7.9954996705055237e-02 + <_> + + 0 -1 1806 3.1580000650137663e-03 + + -2.1124599874019623e-01 2.2366400063037872e-01 + <_> + + 0 -1 1807 4.1439998894929886e-03 + + -4.9900299310684204e-01 6.2917001545429230e-02 + <_> + + 0 -1 1808 -7.3730000294744968e-03 + + -2.0553299784660339e-01 2.2096699476242065e-01 + <_> + + 0 -1 1809 5.1812000572681427e-02 + + 1.8096800148487091e-01 -4.3495801091194153e-01 + <_> + + 0 -1 1810 1.8340000882744789e-02 + + 1.5200000256299973e-02 3.7991699576377869e-01 + <_> + + 0 -1 1811 1.7490799725055695e-01 + + -2.0920799672603607e-01 4.0013000369071960e-01 + <_> + + 0 -1 1812 5.3993999958038330e-02 + + 2.4751600623130798e-01 -2.6712900400161743e-01 + <_> + + 0 -1 1813 -3.2033199071884155e-01 + + -1.9094380140304565e+00 -6.6960997879505157e-02 + <_> + + 0 -1 1814 -2.7060000225901604e-02 + + -7.1371299028396606e-01 1.5904599428176880e-01 + <_> + + 0 -1 1815 7.7463999390602112e-02 + + -1.6970199346542358e-01 7.7552998065948486e-01 + <_> + + 0 -1 1816 2.3771999403834343e-02 + + 1.9021899998188019e-01 -6.0162097215652466e-01 + <_> + + 0 -1 1817 1.1501000262796879e-02 + + 7.7039999887347221e-03 -6.1730301380157471e-01 + <_> + + 0 -1 1818 3.2616000622510910e-02 + + 1.7159199714660645e-01 -7.0978200435638428e-01 + <_> + + 0 -1 1819 -4.4383000582456589e-02 + + -2.2606229782104492e+00 -7.3276996612548828e-02 + <_> + + 0 -1 1820 -5.8476001024246216e-02 + + 2.4087750911712646e+00 8.3091996610164642e-02 + <_> + + 0 -1 1821 1.9303999841213226e-02 + + -2.7082300186157227e-01 2.7369999885559082e-01 + <_> + + 0 -1 1822 -4.4705998152494431e-02 + + 3.1355598568916321e-01 -6.2492001801729202e-02 + <_> + + 0 -1 1823 -6.0334999114274979e-02 + + -1.4515119791030884e+00 -5.8761000633239746e-02 + <_> + + 0 -1 1824 1.1667000129818916e-02 + + -1.8084999173879623e-02 5.0479698181152344e-01 + <_> + + 0 -1 1825 2.8009999543428421e-02 + + -2.3302899301052094e-01 3.0708700418472290e-01 + <_> + + 0 -1 1826 6.5397001802921295e-02 + + 1.4135900139808655e-01 -5.0010901689529419e-01 + <_> + + 0 -1 1827 9.6239997074007988e-03 + + -2.2054600715637207e-01 3.9191201329231262e-01 + <_> + + 0 -1 1828 2.5510000996291637e-03 + + -1.1381500214338303e-01 2.0032300055027008e-01 + <_> + + 0 -1 1829 3.1847000122070312e-02 + + 2.5476999580860138e-02 -5.3326398134231567e-01 + <_> + + 0 -1 1830 3.3055000007152557e-02 + + 1.7807699739933014e-01 -6.2793898582458496e-01 + <_> + + 0 -1 1831 4.7600999474525452e-02 + + -1.4747899770736694e-01 1.4204180240631104e+00 + <_> + + 0 -1 1832 -1.9571999087929726e-02 + + -5.2693498134613037e-01 1.5838600695133209e-01 + <_> + + 0 -1 1833 -5.4730001837015152e-02 + + 8.8231599330902100e-01 -1.6627800464630127e-01 + <_> + + 0 -1 1834 -2.2686000913381577e-02 + + -4.8386898636817932e-01 1.5000100433826447e-01 + <_> + + 0 -1 1835 1.0713200271129608e-01 + + -2.1336199343204498e-01 4.2333900928497314e-01 + <_> + + 0 -1 1836 -3.6380000412464142e-02 + + -7.4198000133037567e-02 1.4589400589466095e-01 + <_> + + 0 -1 1837 1.3935999944806099e-02 + + -2.4911600351333618e-01 2.6771199703216553e-01 + <_> + + 0 -1 1838 2.0991999655961990e-02 + + 8.7959999218583107e-03 4.3064999580383301e-01 + <_> + + 0 -1 1839 4.9118999391794205e-02 + + -1.7591999471187592e-01 6.9282901287078857e-01 + <_> + + 0 -1 1840 3.6315999925136566e-02 + + 1.3145299255847931e-01 -3.3597299456596375e-01 + <_> + + 0 -1 1841 4.1228000074625015e-02 + + -4.5692000538110733e-02 -1.3515930175781250e+00 + <_> + + 0 -1 1842 1.5672000125050545e-02 + + 1.7544099688529968e-01 -6.0550000518560410e-02 + <_> + + 0 -1 1843 -1.6286000609397888e-02 + + -1.1308189630508423e+00 -3.9533000439405441e-02 + <_> + + 0 -1 1844 -3.0229999683797359e-03 + + -2.2454300522804260e-01 2.3628099262714386e-01 + <_> + + 0 -1 1845 -1.3786299526691437e-01 + + 4.5376899838447571e-01 -2.1098700165748596e-01 + <_> + + 0 -1 1846 -9.6760001033544540e-03 + + -1.5105099976062775e-01 2.0781700313091278e-01 + <_> + + 0 -1 1847 -2.4839999154210091e-02 + + -6.8350297212600708e-01 -8.0040004104375839e-03 + <_> + + 0 -1 1848 -1.3964399695396423e-01 + + 6.5011298656463623e-01 4.6544000506401062e-02 + <_> + + 0 -1 1849 -8.2153998315334320e-02 + + 4.4887199997901917e-01 -2.3591999709606171e-01 + <_> + + 0 -1 1850 3.8449999410659075e-03 + + -8.8173002004623413e-02 2.7346798777580261e-01 + <_> + + 0 -1 1851 -6.6579999402165413e-03 + + -4.6866598725318909e-01 7.7001996338367462e-02 + <_> + + 0 -1 1852 -1.5898000448942184e-02 + + 2.9268398880958557e-01 -2.1941000595688820e-02 + <_> + + 0 -1 1853 -5.0946000963449478e-02 + + -1.2093789577484131e+00 -4.2109999805688858e-02 + <_> + + 0 -1 1854 1.6837999224662781e-02 + + -4.5595999807119370e-02 5.0180697441101074e-01 + <_> + + 0 -1 1855 1.5918999910354614e-02 + + -2.6904299855232239e-01 2.6516300439834595e-01 + <_> + + 0 -1 1856 3.6309999413788319e-03 + + -1.3046100735664368e-01 3.1807100772857666e-01 + <_> + + 0 -1 1857 -8.6144998669624329e-02 + + 1.9443659782409668e+00 -1.3978299498558044e-01 + <_> + + 0 -1 1858 3.3140998333692551e-02 + + 1.5266799926757812e-01 -3.0866000801324844e-02 + <_> + + 0 -1 1859 -3.9679999463260174e-03 + + -7.1202301979064941e-01 -1.3844000175595284e-02 + <_> + + 0 -1 1860 -2.4008000269532204e-02 + + 9.2007797956466675e-01 4.6723999083042145e-02 + <_> + + 0 -1 1861 8.7320003658533096e-03 + + -2.2567300498485565e-01 3.1931799650192261e-01 + <_> + + 0 -1 1862 -2.7786999940872192e-02 + + -7.2337102890014648e-01 1.7018599808216095e-01 + <_> + + 0 -1 1863 -1.9455300271511078e-01 + + 1.2461860179901123e+00 -1.4736199378967285e-01 + <_> + + 0 -1 1864 -1.0869699716567993e-01 + + -1.4465179443359375e+00 1.2145300209522247e-01 + <_> + + 0 -1 1865 -1.9494999200105667e-02 + + -7.8153097629547119e-01 -2.3732999339699745e-02 + <_> + + 0 -1 1866 3.0650000553578138e-03 + + -8.5471397638320923e-01 1.6686999797821045e-01 + <_> + + 0 -1 1867 5.9193998575210571e-02 + + -1.4853699505329132e-01 1.1273469924926758e+00 + <_> + + 0 -1 1868 -5.4207999259233475e-02 + + 5.4726999998092651e-01 3.5523999482393265e-02 + <_> + + 0 -1 1869 -3.9324998855590820e-02 + + 3.6642599105834961e-01 -2.0543999969959259e-01 + <_> + + 0 -1 1870 8.2278996706008911e-02 + + -3.5007998347282410e-02 5.3994202613830566e-01 + <_> + + 0 -1 1871 -7.4479999020695686e-03 + + -6.1537498235702515e-01 -3.5319998860359192e-03 + <_> + + 0 -1 1872 7.3770000599324703e-03 + + -6.5591000020503998e-02 4.1961398720741272e-01 + <_> + + 0 -1 1873 7.0779998786747456e-03 + + -3.4129500389099121e-01 1.2536799907684326e-01 + <_> + + 0 -1 1874 -1.5581999905407429e-02 + + -3.0240398645401001e-01 2.1511000394821167e-01 + <_> + + 0 -1 1875 -2.7399999089539051e-03 + + 7.6553001999855042e-02 -4.1060501337051392e-01 + <_> + + 0 -1 1876 -7.0600003004074097e-02 + + -9.7356200218200684e-01 1.1241800338029861e-01 + <_> + + 0 -1 1877 -1.1706000193953514e-02 + + 1.8560700118541718e-01 -2.9755198955535889e-01 + <_> + + 0 -1 1878 7.1499997284263372e-04 + + -5.9650000184774399e-02 2.4824699759483337e-01 + <_> + + 0 -1 1879 -3.6866001784801483e-02 + + 3.2751700282096863e-01 -2.3059600591659546e-01 + <_> + + 0 -1 1880 -3.2526999711990356e-02 + + -2.9320299625396729e-01 1.5427699685096741e-01 + <_> + + 0 -1 1881 -7.4813999235630035e-02 + + -1.2143570184707642e+00 -5.2244000136852264e-02 + <_> + + 0 -1 1882 4.1469998657703400e-02 + + 1.3062499463558197e-01 -2.3274369239807129e+00 + <_> + + 0 -1 1883 -2.8880000114440918e-02 + + -6.6074597835540771e-01 -9.0960003435611725e-03 + <_> + + 0 -1 1884 4.6381998807191849e-02 + + 1.6630199551582336e-01 -6.6949498653411865e-01 + <_> + + 0 -1 1885 2.5424998998641968e-01 + + -5.4641999304294586e-02 -1.2676080465316772e+00 + <_> + + 0 -1 1886 2.4000001139938831e-03 + + 2.0276799798011780e-01 1.4667999930679798e-02 + <_> + + 0 -1 1887 -8.2805998623371124e-02 + + -7.8713601827621460e-01 -2.4468999356031418e-02 + <_> + + 0 -1 1888 -1.1438000015914440e-02 + + 2.8623399138450623e-01 -3.0894000083208084e-02 + <_> + + 0 -1 1889 -1.2913399934768677e-01 + + 1.7292929887771606e+00 -1.4293900132179260e-01 + <_> + + 0 -1 1890 3.8552999496459961e-02 + + 1.9232999533414841e-02 3.7732601165771484e-01 + <_> + + 0 -1 1891 1.0191400349140167e-01 + + -7.4533998966217041e-02 -3.3868899345397949e+00 + <_> + + 0 -1 1892 -1.9068000838160515e-02 + + 3.1814101338386536e-01 1.9261000677943230e-02 + <_> + + 0 -1 1893 -6.0775000602006912e-02 + + 7.6936298608779907e-01 -1.7644000053405762e-01 + <_> + + 0 -1 1894 2.4679999798536301e-02 + + 1.8396499752998352e-01 -3.0868801474571228e-01 + <_> + + 0 -1 1895 2.6759000495076180e-02 + + -2.3454900085926056e-01 3.3056598901748657e-01 + <_> + + 0 -1 1896 1.4969999901950359e-02 + + 1.7213599383831024e-01 -1.8248899281024933e-01 + <_> + + 0 -1 1897 2.6142999529838562e-02 + + -4.6463999897241592e-02 -1.1318379640579224e+00 + <_> + + 0 -1 1898 -3.7512000650167465e-02 + + 8.0404001474380493e-01 6.9660000503063202e-02 + <_> + + 0 -1 1899 -5.3229997865855694e-03 + + -8.1884402036666870e-01 -1.8224999308586121e-02 + <_> + + 0 -1 1900 1.7813000828027725e-02 + + 1.4957800507545471e-01 -1.8667200207710266e-01 + <_> + + 0 -1 1901 -3.4010000526905060e-02 + + -7.2852301597595215e-01 -1.6615999862551689e-02 + <_> + + 0 -1 1902 -1.5953000634908676e-02 + + 5.6944000720977783e-01 1.3832000084221363e-02 + <_> + + 0 -1 1903 1.9743999466300011e-02 + + 4.0525000542402267e-02 -4.1773399710655212e-01 + <_> + + 0 -1 1904 -1.0374800115823746e-01 + + -1.9825149774551392e+00 1.1960200220346451e-01 + <_> + + 0 -1 1905 -1.9285000860691071e-02 + + 5.0230598449707031e-01 -1.9745899736881256e-01 + <_> + + 0 -1 1906 -1.2780000455677509e-02 + + 4.0195000171661377e-01 -2.6957999914884567e-02 + <_> + + 0 -1 1907 -1.6352999955415726e-02 + + -7.6608800888061523e-01 -2.4209000170230865e-02 + <_> + + 0 -1 1908 -1.2763699889183044e-01 + + 8.6578500270843506e-01 6.4205996692180634e-02 + <_> + + 0 -1 1909 1.9068999215960503e-02 + + -5.5929797887802124e-01 -1.6880000475794077e-03 + <_> + + 0 -1 1910 3.2480999827384949e-02 + + 4.0722001343965530e-02 4.8925098776817322e-01 + <_> + + 0 -1 1911 9.4849998131394386e-03 + + -1.9231900572776794e-01 5.1139700412750244e-01 + <_> + + 0 -1 1912 5.0470000132918358e-03 + + 1.8706800043582916e-01 -1.6113600134849548e-01 + <_> + + 0 -1 1913 4.1267998516559601e-02 + + -4.8817999660968781e-02 -1.1326299905776978e+00 + <_> + + 0 -1 1914 -7.6358996331691742e-02 + + 1.4169390201568604e+00 8.7319999933242798e-02 + <_> + + 0 -1 1915 -7.2834998369216919e-02 + + 1.3189860582351685e+00 -1.4819100499153137e-01 + <_> + + 0 -1 1916 5.9576999396085739e-02 + + 4.8376999795436859e-02 8.5611802339553833e-01 + <_> + + 0 -1 1917 2.0263999700546265e-02 + + -2.1044099330902100e-01 3.3858999609947205e-01 + <_> + + 0 -1 1918 -8.0301001667976379e-02 + + -1.2464400529861450e+00 1.1857099831104279e-01 + <_> + + 0 -1 1919 -1.7835000529885292e-02 + + 2.5782299041748047e-01 -2.4564799666404724e-01 + <_> + + 0 -1 1920 1.1431000195443630e-02 + + 2.2949799895286560e-01 -2.9497599601745605e-01 + <_> + + 0 -1 1921 -2.5541000068187714e-02 + + -8.6252999305725098e-01 -7.0400000549852848e-04 + <_> + + 0 -1 1922 -7.6899997657164931e-04 + + 3.1511399149894714e-01 -1.4349000155925751e-01 + <_> + + 0 -1 1923 -1.4453999698162079e-02 + + 2.5148499011993408e-01 -2.8232899308204651e-01 + <_> + + 0 -1 1924 8.6730001494288445e-03 + + 2.6601400971412659e-01 -2.8190800547599792e-01 + <_> + 197 + -3.2772979736328125e+00 + + <_> + + 0 -1 1925 5.4708998650312424e-02 + + -5.4144299030303955e-01 6.1043000221252441e-01 + <_> + + 0 -1 1926 -1.0838799923658371e-01 + + 7.1739900112152100e-01 -4.1196098923683167e-01 + <_> + + 0 -1 1927 2.2996999323368073e-02 + + -5.8269798755645752e-01 2.9645600914955139e-01 + <_> + + 0 -1 1928 2.7540000155568123e-03 + + -7.4243897199630737e-01 1.4183300733566284e-01 + <_> + + 0 -1 1929 -2.1520000882446766e-03 + + 1.7879900336265564e-01 -6.8548601865768433e-01 + <_> + + 0 -1 1930 -2.2559000179171562e-02 + + -1.0775549411773682e+00 1.2388999760150909e-01 + <_> + + 0 -1 1931 8.3025000989437103e-02 + + 2.4500999599695206e-02 -1.0251879692077637e+00 + <_> + + 0 -1 1932 -6.6740000620484352e-03 + + -4.5283100008964539e-01 2.1230199933052063e-01 + <_> + + 0 -1 1933 7.6485000550746918e-02 + + -2.6972699165344238e-01 4.8580199480056763e-01 + <_> + + 0 -1 1934 5.4910001344978809e-03 + + -4.8871201276779175e-01 3.1616398692131042e-01 + <_> + + 0 -1 1935 -1.0414999909698963e-02 + + 4.1512900590896606e-01 -3.0044800043106079e-01 + <_> + + 0 -1 1936 2.7607999742031097e-02 + + 1.6203799843788147e-01 -9.9868500232696533e-01 + <_> + + 0 -1 1937 -2.3272000253200531e-02 + + -1.1024399995803833e+00 2.1124999970197678e-02 + <_> + + 0 -1 1938 -5.5619999766349792e-02 + + 6.5033102035522461e-01 -2.7938000857830048e-02 + <_> + + 0 -1 1939 -4.0631998330354691e-02 + + 4.2117300629615784e-01 -2.6763799786567688e-01 + <_> + + 0 -1 1940 -7.3560001328587532e-03 + + 3.5277798771858215e-01 -3.7854000926017761e-01 + <_> + + 0 -1 1941 1.7007000744342804e-02 + + -2.9189500212669373e-01 4.1053798794746399e-01 + <_> + + 0 -1 1942 -3.7034001201391220e-02 + + -1.3216309547424316e+00 1.2966500222682953e-01 + <_> + + 0 -1 1943 -1.9633000716567039e-02 + + -8.7702298164367676e-01 1.0799999581649899e-03 + <_> + + 0 -1 1944 -2.3546999320387840e-02 + + 2.6106101274490356e-01 -2.1481400728225708e-01 + <_> + + 0 -1 1945 -4.3352998793125153e-02 + + -9.9089699983596802e-01 -9.9560003727674484e-03 + <_> + + 0 -1 1946 -2.2183999419212341e-02 + + 6.3454401493072510e-01 -5.6547001004219055e-02 + <_> + + 0 -1 1947 1.6530999913811684e-02 + + 2.4664999917149544e-02 -7.3326802253723145e-01 + <_> + + 0 -1 1948 -3.2744001597166061e-02 + + -5.6297200918197632e-01 1.6640299558639526e-01 + <_> + + 0 -1 1949 7.1415998041629791e-02 + + -3.0000001424923539e-04 -9.3286401033401489e-01 + <_> + + 0 -1 1950 8.0999999772757292e-04 + + -9.5380000770092010e-02 2.5184699892997742e-01 + <_> + + 0 -1 1951 -8.4090000018477440e-03 + + -6.5496802330017090e-01 6.7300997674465179e-02 + <_> + + 0 -1 1952 -1.7254000529646873e-02 + + -4.6492999792098999e-01 1.6070899367332458e-01 + <_> + + 0 -1 1953 -1.8641000613570213e-02 + + -1.0594010353088379e+00 -1.9617000594735146e-02 + <_> + + 0 -1 1954 -9.1979997232556343e-03 + + 5.0716197490692139e-01 -1.5339200198650360e-01 + <_> + + 0 -1 1955 1.8538000062108040e-02 + + -3.0498200654983521e-01 7.3506200313568115e-01 + <_> + + 0 -1 1956 -5.0335001200437546e-02 + + -1.1140480041503906e+00 1.8000100553035736e-01 + <_> + + 0 -1 1957 -2.3529000580310822e-02 + + -8.6907899379730225e-01 -1.2459999881684780e-02 + <_> + + 0 -1 1958 -2.7100000530481339e-02 + + 6.5942901372909546e-01 -3.5323999822139740e-02 + <_> + + 0 -1 1959 6.5879998728632927e-03 + + -2.2953400015830994e-01 4.2425099015235901e-01 + <_> + + 0 -1 1960 2.3360000923275948e-02 + + 1.8356199562549591e-01 -9.8587298393249512e-01 + <_> + + 0 -1 1961 1.2946999631822109e-02 + + -3.3147400617599487e-01 2.1323199570178986e-01 + <_> + + 0 -1 1962 -6.6559999249875546e-03 + + -1.1951400339603424e-01 2.9752799868583679e-01 + <_> + + 0 -1 1963 -2.2570999339222908e-02 + + 3.8499400019645691e-01 -2.4434499442577362e-01 + <_> + + 0 -1 1964 -6.3813999295234680e-02 + + -8.9383500814437866e-01 1.4217500388622284e-01 + <_> + + 0 -1 1965 -4.9945000559091568e-02 + + 5.3864401578903198e-01 -2.0485299825668335e-01 + <_> + + 0 -1 1966 6.8319998681545258e-03 + + -5.6678999215364456e-02 3.9970999956130981e-01 + <_> + + 0 -1 1967 -5.5835999548435211e-02 + + -1.5239470005035400e+00 -5.1183000206947327e-02 + <_> + + 0 -1 1968 3.1957000494003296e-01 + + 7.4574001133441925e-02 1.2447799444198608e+00 + <_> + + 0 -1 1969 8.0955997109413147e-02 + + -1.9665500521659851e-01 5.9889698028564453e-01 + <_> + + 0 -1 1970 -1.4911999925971031e-02 + + -6.4020597934722900e-01 1.5807600319385529e-01 + <_> + + 0 -1 1971 4.6709001064300537e-02 + + 8.5239000618457794e-02 -4.5487201213836670e-01 + <_> + + 0 -1 1972 6.0539999976754189e-03 + + -4.3184000253677368e-01 2.2452600300312042e-01 + <_> + + 0 -1 1973 -3.4375999122858047e-02 + + 4.0202501416206360e-01 -2.3903599381446838e-01 + <_> + + 0 -1 1974 -3.4924000501632690e-02 + + 5.2870100736618042e-01 3.9709001779556274e-02 + <_> + + 0 -1 1975 3.0030000489205122e-03 + + -3.8754299283027649e-01 1.4192600548267365e-01 + <_> + + 0 -1 1976 -1.4132999815046787e-02 + + 8.7528401613235474e-01 8.5507996380329132e-02 + <_> + + 0 -1 1977 -6.7940000444650650e-03 + + -1.1649219989776611e+00 -3.3943001180887222e-02 + <_> + + 0 -1 1978 -5.2886001765727997e-02 + + 1.0930680036544800e+00 5.1187001168727875e-02 + <_> + + 0 -1 1979 -2.1079999860376120e-03 + + 1.3696199655532837e-01 -3.3849999308586121e-01 + <_> + + 0 -1 1980 1.8353000283241272e-02 + + 1.3661600649356842e-01 -4.0777799487113953e-01 + <_> + + 0 -1 1981 1.2671999633312225e-02 + + -1.4936000108718872e-02 -8.1707501411437988e-01 + <_> + + 0 -1 1982 1.2924999929964542e-02 + + 1.7625099420547485e-01 -3.2491698861122131e-01 + <_> + + 0 -1 1983 -1.7921000719070435e-02 + + -5.2745401859283447e-01 4.4443000108003616e-02 + <_> + + 0 -1 1984 1.9160000374540687e-03 + + -1.0978599637746811e-01 2.2067500650882721e-01 + <_> + + 0 -1 1985 -1.4697999693453312e-02 + + 3.9067798852920532e-01 -2.2224999964237213e-01 + <_> + + 0 -1 1986 -1.4972999691963196e-02 + + -2.5450900197029114e-01 1.7790000140666962e-01 + <_> + + 0 -1 1987 1.4636999927461147e-02 + + -2.5125000625848770e-02 -8.7121301889419556e-01 + <_> + + 0 -1 1988 -1.0974000208079815e-02 + + 7.9082798957824707e-01 2.0121000707149506e-02 + <_> + + 0 -1 1989 -9.1599998995661736e-03 + + -4.7906899452209473e-01 5.2232000976800919e-02 + <_> + + 0 -1 1990 4.6179997734725475e-03 + + -1.7244599759578705e-01 3.4527799487113953e-01 + <_> + + 0 -1 1991 2.3476999253034592e-02 + + 3.7760001141577959e-03 -6.5333700180053711e-01 + <_> + + 0 -1 1992 3.1766999512910843e-02 + + 1.6364000737667084e-02 5.8723700046539307e-01 + <_> + + 0 -1 1993 -1.8419999629259109e-02 + + 1.9993899762630463e-01 -3.2056498527526855e-01 + <_> + + 0 -1 1994 1.9543999806046486e-02 + + 1.8450200557708740e-01 -2.3793600499629974e-01 + <_> + + 0 -1 1995 4.1159498691558838e-01 + + -6.0382001101970673e-02 -1.6072119474411011e+00 + <_> + + 0 -1 1996 -4.1595999151468277e-02 + + -3.2756200432777405e-01 1.5058000385761261e-01 + <_> + + 0 -1 1997 -1.0335999540984631e-02 + + -6.2394398450851440e-01 1.3112000189721584e-02 + <_> + + 0 -1 1998 1.2392999604344368e-02 + + -3.3114999532699585e-02 5.5579900741577148e-01 + <_> + + 0 -1 1999 -8.7270000949501991e-03 + + 1.9883200526237488e-01 -3.7635600566864014e-01 + <_> + + 0 -1 2000 1.6295000910758972e-02 + + 2.0373000204563141e-01 -4.2800799012184143e-01 + <_> + + 0 -1 2001 -1.0483999736607075e-02 + + -5.6847000122070312e-01 4.4199001044034958e-02 + <_> + + 0 -1 2002 -1.2431999668478966e-02 + + 7.4641901254653931e-01 4.3678998947143555e-02 + <_> + + 0 -1 2003 -5.0374999642372131e-02 + + 8.5090100765228271e-01 -1.7773799598217010e-01 + <_> + + 0 -1 2004 4.9548000097274780e-02 + + 1.6784900426864624e-01 -2.9877498745918274e-01 + <_> + + 0 -1 2005 -4.1085001081228256e-02 + + -1.3302919864654541e+00 -4.9182001501321793e-02 + <_> + + 0 -1 2006 1.0069999843835831e-03 + + -6.0538999736309052e-02 1.8483200669288635e-01 + <_> + + 0 -1 2007 -5.0142999738454819e-02 + + 7.6447701454162598e-01 -1.8356999754905701e-01 + <_> + + 0 -1 2008 -8.7879998609423637e-03 + + 2.2655999660491943e-01 -6.3156999647617340e-02 + <_> + + 0 -1 2009 -5.0170999020338058e-02 + + -1.5899070501327515e+00 -6.1255000531673431e-02 + <_> + + 0 -1 2010 1.0216099768877029e-01 + + 1.2071800231933594e-01 -1.4120110273361206e+00 + <_> + + 0 -1 2011 -1.4372999779880047e-02 + + -1.3116970062255859e+00 -5.1936000585556030e-02 + <_> + + 0 -1 2012 1.0281999595463276e-02 + + -2.1639999467879534e-03 4.4247201085090637e-01 + <_> + + 0 -1 2013 -1.1814000084996223e-02 + + 6.5378099679946899e-01 -1.8723699450492859e-01 + <_> + + 0 -1 2014 7.2114996612071991e-02 + + 7.1846999228000641e-02 8.1496298313140869e-01 + <_> + + 0 -1 2015 -1.9001999869942665e-02 + + -6.7427200078964233e-01 -4.3200000072829425e-04 + <_> + + 0 -1 2016 -4.6990001574158669e-03 + + 3.3311501145362854e-01 5.5794000625610352e-02 + <_> + + 0 -1 2017 -5.8157000690698624e-02 + + 4.5572298765182495e-01 -2.0305100083351135e-01 + <_> + + 0 -1 2018 1.1360000353306532e-03 + + -4.4686999171972275e-02 2.2681899368762970e-01 + <_> + + 0 -1 2019 -4.9414999783039093e-02 + + 2.6694598793983459e-01 -2.6116999983787537e-01 + <_> + + 0 -1 2020 -1.1913800239562988e-01 + + -8.3017998933792114e-01 1.3248500227928162e-01 + <_> + + 0 -1 2021 -1.8303999677300453e-02 + + -6.7499202489852905e-01 1.7092000693082809e-02 + <_> + + 0 -1 2022 -7.9199997708201408e-03 + + -7.2287000715732574e-02 1.4425800740718842e-01 + <_> + + 0 -1 2023 5.1925998181104660e-02 + + 3.0921999365091324e-02 -5.5860602855682373e-01 + <_> + + 0 -1 2024 6.6724002361297607e-02 + + 1.3666400313377380e-01 -2.9411000013351440e-01 + <_> + + 0 -1 2025 -1.3778000138700008e-02 + + -5.9443902969360352e-01 1.5300000086426735e-02 + <_> + + 0 -1 2026 -1.7760999500751495e-02 + + 4.0496501326560974e-01 -3.3559999428689480e-03 + <_> + + 0 -1 2027 -4.2234998196363449e-02 + + -1.0897940397262573e+00 -4.0224999189376831e-02 + <_> + + 0 -1 2028 -1.3524999842047691e-02 + + 2.8921899199485779e-01 -2.5194799900054932e-01 + <_> + + 0 -1 2029 -1.1106000281870365e-02 + + 6.5312802791595459e-01 -1.8053700029850006e-01 + <_> + + 0 -1 2030 -1.2284599989652634e-01 + + -1.9570649862289429e+00 1.4815400540828705e-01 + <_> + + 0 -1 2031 4.7715999186038971e-02 + + -2.2875599563121796e-01 3.4233701229095459e-01 + <_> + + 0 -1 2032 3.1817000359296799e-02 + + 1.5976299345493317e-01 -1.0091969966888428e+00 + <_> + + 0 -1 2033 4.2570000514388084e-03 + + -3.8881298899650574e-01 8.4210000932216644e-02 + <_> + + 0 -1 2034 -6.1372999101877213e-02 + + 1.7152810096740723e+00 5.9324998408555984e-02 + <_> + + 0 -1 2035 -2.7030000928789377e-03 + + -3.8161700963973999e-01 8.5127003490924835e-02 + <_> + + 0 -1 2036 -6.8544000387191772e-02 + + -3.0925889015197754e+00 1.1788000166416168e-01 + <_> + + 0 -1 2037 1.0372500121593475e-01 + + -1.3769300282001495e-01 1.9009410142898560e+00 + <_> + + 0 -1 2038 1.5799000859260559e-02 + + -6.2660001218318939e-02 2.5917699933052063e-01 + <_> + + 0 -1 2039 -9.8040001466870308e-03 + + -5.6291598081588745e-01 4.3923001736402512e-02 + <_> + + 0 -1 2040 -9.0229995548725128e-03 + + 2.5287100672721863e-01 -4.1225999593734741e-02 + <_> + + 0 -1 2041 -6.3754998147487640e-02 + + -2.6178569793701172e+00 -7.4005998671054840e-02 + <_> + + 0 -1 2042 3.8954999297857285e-02 + + 5.9032998979091644e-02 8.5945600271224976e-01 + <_> + + 0 -1 2043 -3.9802998304367065e-02 + + 9.3600499629974365e-01 -1.5639400482177734e-01 + <_> + + 0 -1 2044 5.0301998853683472e-02 + + 1.3725900650024414e-01 -2.5549728870391846e+00 + <_> + + 0 -1 2045 4.6250000596046448e-02 + + -1.3964000158011913e-02 -7.1026200056076050e-01 + <_> + + 0 -1 2046 6.2196001410484314e-02 + + 5.9526000171899796e-02 1.6509100198745728e+00 + <_> + + 0 -1 2047 -6.4776003360748291e-02 + + 7.1368998289108276e-01 -1.7270000278949738e-01 + <_> + + 0 -1 2048 2.7522999793291092e-02 + + 1.4631600677967072e-01 -8.1428997218608856e-02 + <_> + + 0 -1 2049 3.9900001138448715e-04 + + -3.7144500017166138e-01 1.0152699798345566e-01 + <_> + + 0 -1 2050 -4.3299999088048935e-03 + + -2.3756299912929535e-01 2.6798400282859802e-01 + <_> + + 0 -1 2051 4.7297000885009766e-02 + + -2.7682000771164894e-02 -8.4910297393798828e-01 + <_> + + 0 -1 2052 1.2508999556303024e-02 + + 1.8730199337005615e-01 -5.6001102924346924e-01 + <_> + + 0 -1 2053 4.5899000018835068e-02 + + -1.5601199865341187e-01 9.7073000669479370e-01 + <_> + + 0 -1 2054 1.9853399693965912e-01 + + 1.4895500242710114e-01 -1.1015529632568359e+00 + <_> + + 0 -1 2055 1.6674999147653580e-02 + + -1.6615299880504608e-01 8.2210999727249146e-01 + <_> + + 0 -1 2056 1.9829999655485153e-03 + + -7.1249999105930328e-02 2.8810900449752808e-01 + <_> + + 0 -1 2057 2.2447999566793442e-02 + + -2.0981000736355782e-02 -7.8416502475738525e-01 + <_> + + 0 -1 2058 -1.3913000002503395e-02 + + -1.8165799975395203e-01 2.0491799712181091e-01 + <_> + + 0 -1 2059 -7.7659999951720238e-03 + + -4.5595899224281311e-01 6.3576996326446533e-02 + <_> + + 0 -1 2060 -1.3209000229835510e-02 + + 2.6632300019264221e-01 -1.7795999348163605e-01 + <_> + + 0 -1 2061 4.9052998423576355e-02 + + -1.5476800501346588e-01 1.1069979667663574e+00 + <_> + + 0 -1 2062 2.0263999700546265e-02 + + 6.8915002048015594e-02 6.9867497682571411e-01 + <_> + + 0 -1 2063 -1.6828000545501709e-02 + + 2.7607199549674988e-01 -2.5139200687408447e-01 + <_> + + 0 -1 2064 -1.6939499974250793e-01 + + -3.0767529010772705e+00 1.1617500334978104e-01 + <_> + + 0 -1 2065 -1.1336100101470947e-01 + + -1.4639229774475098e+00 -5.1447000354528427e-02 + <_> + + 0 -1 2066 -7.7685996890068054e-02 + + 8.8430202007293701e-01 4.3306998908519745e-02 + <_> + + 0 -1 2067 -1.5568000264465809e-02 + + 1.3672499358654022e-01 -3.4505501389503479e-01 + <_> + + 0 -1 2068 -6.6018998622894287e-02 + + -1.0300110578536987e+00 1.1601399630308151e-01 + <_> + + 0 -1 2069 8.3699999377131462e-03 + + 7.6429001986980438e-02 -4.4002500176429749e-01 + <_> + + 0 -1 2070 3.5402998328208923e-02 + + 1.1979500204324722e-01 -7.2668302059173584e-01 + <_> + + 0 -1 2071 -3.9051000028848648e-02 + + 6.7375302314758301e-01 -1.8196000158786774e-01 + <_> + + 0 -1 2072 -9.7899995744228363e-03 + + 2.1264599263668060e-01 3.6756001412868500e-02 + <_> + + 0 -1 2073 -2.3047000169754028e-02 + + 4.4742199778556824e-01 -2.0986700057983398e-01 + <_> + + 0 -1 2074 3.1169999856501818e-03 + + 3.7544000893831253e-02 2.7808201313018799e-01 + <_> + + 0 -1 2075 1.3136000372469425e-02 + + -1.9842399656772614e-01 5.4335701465606689e-01 + <_> + + 0 -1 2076 1.4782000333070755e-02 + + 1.3530600070953369e-01 -1.1153600364923477e-01 + <_> + + 0 -1 2077 -6.0139000415802002e-02 + + 8.4039300680160522e-01 -1.6711600124835968e-01 + <_> + + 0 -1 2078 5.1998998969793320e-02 + + 1.7372000217437744e-01 -7.8547602891921997e-01 + <_> + + 0 -1 2079 2.4792000651359558e-02 + + -1.7739200592041016e-01 6.6752600669860840e-01 + <_> + + 0 -1 2080 -1.2014999985694885e-02 + + -1.4263699948787689e-01 1.6070500016212463e-01 + <_> + + 0 -1 2081 -9.8655998706817627e-02 + + 1.0429769754409790e+00 -1.5770199894905090e-01 + <_> + + 0 -1 2082 1.1758299916982651e-01 + + 1.0955700278282166e-01 -4.4920377731323242e+00 + <_> + + 0 -1 2083 -1.8922999501228333e-02 + + -7.8543400764465332e-01 1.2984000146389008e-02 + <_> + + 0 -1 2084 -2.8390999883413315e-02 + + -6.0569900274276733e-01 1.2903499603271484e-01 + <_> + + 0 -1 2085 1.3182999566197395e-02 + + -1.4415999874472618e-02 -7.3210501670837402e-01 + <_> + + 0 -1 2086 -1.1653000116348267e-01 + + -2.0442469120025635e+00 1.4053100347518921e-01 + <_> + + 0 -1 2087 -3.8880000356584787e-03 + + -4.1861599683761597e-01 7.8704997897148132e-02 + <_> + + 0 -1 2088 3.1229000538587570e-02 + + 2.4632999673485756e-02 4.1870400309562683e-01 + <_> + + 0 -1 2089 2.5198999792337418e-02 + + -1.7557799816131592e-01 6.4710599184036255e-01 + <_> + + 0 -1 2090 -2.8124000877141953e-02 + + -2.2005599737167358e-01 1.4121000468730927e-01 + <_> + + 0 -1 2091 3.6499001085758209e-02 + + -6.8426996469497681e-02 -2.3410849571228027e+00 + <_> + + 0 -1 2092 -7.2292998433113098e-02 + + 1.2898750305175781e+00 8.4875002503395081e-02 + <_> + + 0 -1 2093 -4.1671000421047211e-02 + + -1.1630970239639282e+00 -5.3752999752759933e-02 + <_> + + 0 -1 2094 4.7703001648187637e-02 + + 7.0101000368595123e-02 7.3676502704620361e-01 + <_> + + 0 -1 2095 6.5793000161647797e-02 + + -1.7755299806594849e-01 6.9780498743057251e-01 + <_> + + 0 -1 2096 1.3904999941587448e-02 + + 2.1936799585819244e-01 -2.0390799641609192e-01 + <_> + + 0 -1 2097 -2.7730999514460564e-02 + + 6.1867898702621460e-01 -1.7804099619388580e-01 + <_> + + 0 -1 2098 -1.5879999846220016e-02 + + -4.6484100818634033e-01 1.8828600645065308e-01 + <_> + + 0 -1 2099 7.4128001928329468e-02 + + -1.2858100235462189e-01 3.2792479991912842e+00 + <_> + + 0 -1 2100 -8.9000002481043339e-04 + + -3.0117601156234741e-01 2.3818799853324890e-01 + <_> + + 0 -1 2101 1.7965000122785568e-02 + + -2.2284999489784241e-01 2.9954001307487488e-01 + <_> + + 0 -1 2102 -2.5380000006407499e-03 + + 2.5064399838447571e-01 -1.3665600121021271e-01 + <_> + + 0 -1 2103 -9.0680001303553581e-03 + + 2.9017499089241028e-01 -2.8929701447486877e-01 + <_> + + 0 -1 2104 4.9169998615980148e-02 + + 1.9156399369239807e-01 -6.8328702449798584e-01 + <_> + + 0 -1 2105 -3.0680999159812927e-02 + + -7.5677001476287842e-01 -1.3279999606311321e-02 + <_> + + 0 -1 2106 1.0017400234937668e-01 + + 8.4453999996185303e-02 1.0888710021972656e+00 + <_> + + 0 -1 2107 3.1950001139193773e-03 + + -2.6919400691986084e-01 1.9537900388240814e-01 + <_> + + 0 -1 2108 3.5503000020980835e-02 + + 1.3632300496101379e-01 -5.6917202472686768e-01 + <_> + + 0 -1 2109 4.5900000259280205e-04 + + -4.0443998575210571e-01 1.4074799418449402e-01 + <_> + + 0 -1 2110 2.5258999317884445e-02 + + 1.6243200004100800e-01 -5.5741798877716064e-01 + <_> + + 0 -1 2111 -5.1549999043345451e-03 + + 3.1132599711418152e-01 -2.2756099700927734e-01 + <_> + + 0 -1 2112 1.5869999770075083e-03 + + -2.6867699623107910e-01 1.9565400481224060e-01 + <_> + + 0 -1 2113 -1.6204999759793282e-02 + + 1.5486499667167664e-01 -3.4057798981666565e-01 + <_> + + 0 -1 2114 -2.9624000191688538e-02 + + 1.1466799974441528e+00 9.0557999908924103e-02 + <_> + + 0 -1 2115 -1.5930000226944685e-03 + + -7.1257501840591431e-01 -7.0400000549852848e-04 + <_> + + 0 -1 2116 -5.4019000381231308e-02 + + 4.1537499427795410e-01 2.7246000245213509e-02 + <_> + + 0 -1 2117 -6.6211000084877014e-02 + + -1.3340090513229370e+00 -4.7352999448776245e-02 + <_> + + 0 -1 2118 2.7940999716520309e-02 + + 1.4446300268173218e-01 -5.1518398523330688e-01 + <_> + + 0 -1 2119 2.8957000002264977e-02 + + -4.9966000020503998e-02 -1.1929039955139160e+00 + <_> + + 0 -1 2120 -2.0424999296665192e-02 + + 6.3881301879882812e-01 3.8141001015901566e-02 + <_> + + 0 -1 2121 1.2416999787092209e-02 + + -2.1547000110149384e-01 4.9477699398994446e-01 + <_> + 181 + -3.3196411132812500e+00 + + <_> + + 0 -1 2122 4.3274000287055969e-02 + + -8.0494397878646851e-01 3.9897298812866211e-01 + <_> + + 0 -1 2123 1.8615500628948212e-01 + + -3.1655299663543701e-01 6.8877297639846802e-01 + <_> + + 0 -1 2124 3.1860999763011932e-02 + + -6.4266198873519897e-01 2.5550898909568787e-01 + <_> + + 0 -1 2125 1.4022000133991241e-02 + + -4.5926600694656372e-01 3.1171199679374695e-01 + <_> + + 0 -1 2126 -6.3029997982084751e-03 + + 4.6026900410652161e-01 -2.7438500523567200e-01 + <_> + + 0 -1 2127 -5.4310001432895660e-03 + + 3.6608600616455078e-01 -2.7205801010131836e-01 + <_> + + 0 -1 2128 1.6822999343276024e-02 + + 2.3476999253034592e-02 -8.8443797826766968e-01 + <_> + + 0 -1 2129 2.6039000600576401e-02 + + 1.7488799989223480e-01 -5.4564702510833740e-01 + <_> + + 0 -1 2130 -2.6720000430941582e-02 + + -9.6396499872207642e-01 2.3524999618530273e-02 + <_> + + 0 -1 2131 -1.7041999846696854e-02 + + -7.0848798751831055e-01 2.1468099951744080e-01 + <_> + + 0 -1 2132 5.9569999575614929e-03 + + 7.3601000010967255e-02 -6.8225598335266113e-01 + <_> + + 0 -1 2133 -2.8679999522864819e-03 + + -7.4935001134872437e-01 2.3803399503231049e-01 + <_> + + 0 -1 2134 -4.3774999678134918e-02 + + 6.8323302268981934e-01 -2.1380299329757690e-01 + <_> + + 0 -1 2135 5.1633000373840332e-02 + + -1.2566499412059784e-01 6.7523801326751709e-01 + <_> + + 0 -1 2136 8.1780003383755684e-03 + + 7.0689998567104340e-02 -8.0665898323059082e-01 + <_> + + 0 -1 2137 -5.2841998636722565e-02 + + 9.5433902740478516e-01 1.6548000276088715e-02 + <_> + + 0 -1 2138 5.2583999931812286e-02 + + -2.8414401412010193e-01 4.7129800915718079e-01 + <_> + + 0 -1 2139 -1.2659000232815742e-02 + + 3.8445401191711426e-01 -6.2288001179695129e-02 + <_> + + 0 -1 2140 1.1694000102579594e-02 + + 5.6000000768108293e-05 -1.0173139572143555e+00 + <_> + + 0 -1 2141 -2.3918999359011650e-02 + + 8.4921300411224365e-01 5.7399999350309372e-03 + <_> + + 0 -1 2142 -6.1673998832702637e-02 + + -9.2571401596069336e-01 -1.7679999582469463e-03 + <_> + + 0 -1 2143 -1.8279999494552612e-03 + + -5.4372298717498779e-01 2.4932399392127991e-01 + <_> + + 0 -1 2144 3.5257998853921890e-02 + + -7.3719997890293598e-03 -9.3963998556137085e-01 + <_> + + 0 -1 2145 -1.8438000231981277e-02 + + 7.2136700153350830e-01 1.0491999797523022e-02 + <_> + + 0 -1 2146 -3.8389001041650772e-02 + + 1.9272600114345551e-01 -3.5832101106643677e-01 + <_> + + 0 -1 2147 9.9720999598503113e-02 + + 1.1354199796915054e-01 -1.6304190158843994e+00 + <_> + + 0 -1 2148 8.4462001919746399e-02 + + -5.3420998156070709e-02 -1.6981120109558105e+00 + <_> + + 0 -1 2149 4.0270000696182251e-02 + + -1.0783199965953827e-01 5.1926600933074951e-01 + <_> + + 0 -1 2150 5.8935999870300293e-02 + + -1.8053700029850006e-01 9.5119798183441162e-01 + <_> + + 0 -1 2151 1.4957000315189362e-01 + + 1.6785299777984619e-01 -1.1591869592666626e+00 + <_> + + 0 -1 2152 6.9399998756125569e-04 + + 2.0491400361061096e-01 -3.3118200302124023e-01 + <_> + + 0 -1 2153 -3.3369001001119614e-02 + + 9.3468099832534790e-01 -2.9639999847859144e-03 + <_> + + 0 -1 2154 9.3759996816515923e-03 + + 3.7000000011175871e-03 -7.7549797296524048e-01 + <_> + + 0 -1 2155 4.3193999677896500e-02 + + -2.2040000185370445e-03 7.4589699506759644e-01 + <_> + + 0 -1 2156 -6.7555002868175507e-02 + + 7.2292101383209229e-01 -1.8404200673103333e-01 + <_> + + 0 -1 2157 -3.1168600916862488e-01 + + 1.0014270544052124e+00 3.4003000706434250e-02 + <_> + + 0 -1 2158 2.9743999242782593e-02 + + -4.6356000006198883e-02 -1.2781809568405151e+00 + <_> + + 0 -1 2159 1.0737000033259392e-02 + + 1.4812000095844269e-02 6.6649997234344482e-01 + <_> + + 0 -1 2160 -2.8841000050306320e-02 + + -9.4222599267959595e-01 -2.0796999335289001e-02 + <_> + + 0 -1 2161 -5.7649998925626278e-03 + + -4.3541899323463440e-01 2.3386000096797943e-01 + <_> + + 0 -1 2162 2.8410999104380608e-02 + + -1.7615799605846405e-01 8.5765302181243896e-01 + <_> + + 0 -1 2163 -2.9007999226450920e-02 + + 5.7978099584579468e-01 2.8565999120473862e-02 + <_> + + 0 -1 2164 2.4965999647974968e-02 + + -2.2729000076651573e-02 -9.6773099899291992e-01 + <_> + + 0 -1 2165 1.2036000378429890e-02 + + -1.4214700460433960e-01 5.1687997579574585e-01 + <_> + + 0 -1 2166 -4.2514000087976456e-02 + + 9.7273802757263184e-01 -1.8119800090789795e-01 + <_> + + 0 -1 2167 1.0276000015437603e-02 + + -8.3099998533725739e-02 3.1762799620628357e-01 + <_> + + 0 -1 2168 -6.9191999733448029e-02 + + -2.0668580532073975e+00 -6.0173999518156052e-02 + <_> + + 0 -1 2169 -4.6769999898970127e-03 + + 4.4131800532341003e-01 2.3209000006318092e-02 + <_> + + 0 -1 2170 -1.3923999853432178e-02 + + 2.8606700897216797e-01 -2.9152700304985046e-01 + <_> + + 0 -1 2171 -1.5333999879658222e-02 + + -5.7414501905441284e-01 2.3063300549983978e-01 + <_> + + 0 -1 2172 -1.0239000432193279e-02 + + 3.4479200839996338e-01 -2.6080399751663208e-01 + <_> + + 0 -1 2173 -5.0988998264074326e-02 + + 5.6154102087020874e-01 6.1218999326229095e-02 + <_> + + 0 -1 2174 3.0689999461174011e-02 + + -1.4772799611091614e-01 1.6378489732742310e+00 + <_> + + 0 -1 2175 -1.1223999783396721e-02 + + 2.4006199836730957e-01 -4.4864898920059204e-01 + <_> + + 0 -1 2176 -6.2899999320507050e-03 + + 4.3119499087333679e-01 -2.3808999359607697e-01 + <_> + + 0 -1 2177 7.8590996563434601e-02 + + 1.9865000620484352e-02 8.0853801965713501e-01 + <_> + + 0 -1 2178 -1.0178999975323677e-02 + + 1.8193200230598450e-01 -3.2877799868583679e-01 + <_> + + 0 -1 2179 3.1227000057697296e-02 + + 1.4973899722099304e-01 -1.4180339574813843e+00 + <_> + + 0 -1 2180 4.0196999907493591e-02 + + -1.9760499894618988e-01 5.8508199453353882e-01 + <_> + + 0 -1 2181 1.6138000413775444e-02 + + 5.0000002374872565e-04 3.9050000905990601e-01 + <_> + + 0 -1 2182 -4.5519001781940460e-02 + + 1.2646820545196533e+00 -1.5632599592208862e-01 + <_> + + 0 -1 2183 -1.8130000680685043e-02 + + 6.5148502588272095e-01 1.0235999710857868e-02 + <_> + + 0 -1 2184 -1.4001999981701374e-02 + + -1.0344820022583008e+00 -3.2182998955249786e-02 + <_> + + 0 -1 2185 -3.8816001266241074e-02 + + -4.7874298691749573e-01 1.6290700435638428e-01 + <_> + + 0 -1 2186 3.1656000763177872e-02 + + -2.0983399450778961e-01 5.4575902223587036e-01 + <_> + + 0 -1 2187 -1.0839999653398991e-02 + + 5.1898801326751709e-01 -1.5080000273883343e-02 + <_> + + 0 -1 2188 1.2032999657094479e-02 + + -2.1107600629329681e-01 7.5937002897262573e-01 + <_> + + 0 -1 2189 7.0772998034954071e-02 + + 1.8048800528049469e-01 -7.4048501253128052e-01 + <_> + + 0 -1 2190 5.3139799833297729e-01 + + -1.4491699635982513e-01 1.5360039472579956e+00 + <_> + + 0 -1 2191 -1.4774000272154808e-02 + + -2.8153699636459351e-01 2.0407299697399139e-01 + <_> + + 0 -1 2192 -2.2410000674426556e-03 + + -4.4876301288604736e-01 5.3989000618457794e-02 + <_> + + 0 -1 2193 4.9968000501394272e-02 + + 4.1514001786708832e-02 2.9417100548744202e-01 + <_> + + 0 -1 2194 -4.7701999545097351e-02 + + 3.9674299955368042e-01 -2.8301799297332764e-01 + <_> + + 0 -1 2195 -9.1311000287532806e-02 + + 2.1994259357452393e+00 8.7964996695518494e-02 + <_> + + 0 -1 2196 3.8070000708103180e-02 + + -2.8025600314140320e-01 2.5156199932098389e-01 + <_> + + 0 -1 2197 -1.5538999810814857e-02 + + 3.4157499670982361e-01 1.7924999818205833e-02 + <_> + + 0 -1 2198 -1.5445999801158905e-02 + + 2.8680199384689331e-01 -2.5135898590087891e-01 + <_> + + 0 -1 2199 -5.7388000190258026e-02 + + 6.3830000162124634e-01 8.8597998023033142e-02 + <_> + + 0 -1 2200 -5.9440000914037228e-03 + + 7.9016998410224915e-02 -4.0774899721145630e-01 + <_> + + 0 -1 2201 -6.9968998432159424e-02 + + -4.4644200801849365e-01 1.7219600081443787e-01 + <_> + + 0 -1 2202 -2.5064999237656593e-02 + + -9.8270201683044434e-01 -3.5388000309467316e-02 + <_> + + 0 -1 2203 1.7216000705957413e-02 + + 2.2705900669097900e-01 -8.0550098419189453e-01 + <_> + + 0 -1 2204 -4.4279001653194427e-02 + + 8.3951997756958008e-01 -1.7429600656032562e-01 + <_> + + 0 -1 2205 4.3988998979330063e-02 + + 1.1557199805974960e-01 -1.9666889905929565e+00 + <_> + + 0 -1 2206 1.5907000750303268e-02 + + -3.7576001137495041e-02 -1.0311100482940674e+00 + <_> + + 0 -1 2207 -9.2754997313022614e-02 + + -1.3530019521713257e+00 1.2141299992799759e-01 + <_> + + 0 -1 2208 7.1037001907825470e-02 + + -1.7684300243854523e-01 7.4485200643539429e-01 + <_> + + 0 -1 2209 5.7762000709772110e-02 + + 1.2835599482059479e-01 -4.4444200396537781e-01 + <_> + + 0 -1 2210 -1.6432000324130058e-02 + + 8.0152702331542969e-01 -1.7491699755191803e-01 + <_> + + 0 -1 2211 2.3939000442624092e-02 + + 1.6144999861717224e-01 -1.2364500015974045e-01 + <_> + + 0 -1 2212 1.2636000290513039e-02 + + 1.5411999821662903e-01 -3.3293798565864563e-01 + <_> + + 0 -1 2213 -5.4347999393939972e-02 + + -1.8400700092315674e+00 1.4835999906063080e-01 + <_> + + 0 -1 2214 -1.3261999934911728e-02 + + -8.0838799476623535e-01 -2.7726000174880028e-02 + <_> + + 0 -1 2215 6.1340001411736012e-03 + + -1.3785000145435333e-01 3.2858499884605408e-01 + <_> + + 0 -1 2216 2.8991000726819038e-02 + + -2.5516999885439873e-02 -8.3387202024459839e-01 + <_> + + 0 -1 2217 -2.1986000239849091e-02 + + -7.3739999532699585e-01 1.7887100577354431e-01 + <_> + + 0 -1 2218 5.3269998170435429e-03 + + -4.5449298620223999e-01 6.8791002035140991e-02 + <_> + + 0 -1 2219 8.6047999560832977e-02 + + 2.1008500456809998e-01 -3.7808901071548462e-01 + <_> + + 0 -1 2220 -8.5549997165799141e-03 + + 4.0134999155998230e-01 -2.1074099838733673e-01 + <_> + + 0 -1 2221 6.7790001630783081e-03 + + -2.1648999303579330e-02 4.5421499013900757e-01 + <_> + + 0 -1 2222 -6.3959998078644276e-03 + + -4.9818599224090576e-01 7.5907997786998749e-02 + <_> + + 0 -1 2223 8.9469999074935913e-03 + + 1.7857700586318970e-01 -2.8454899787902832e-01 + <_> + + 0 -1 2224 3.2589999027550220e-03 + + 4.6624999493360519e-02 -5.5206298828125000e-01 + <_> + + 0 -1 2225 4.1476998478174210e-02 + + 1.7550499737262726e-01 -2.0703999698162079e-01 + <_> + + 0 -1 2226 -6.7449999041855335e-03 + + -4.6392598748207092e-01 6.9303996860980988e-02 + <_> + + 0 -1 2227 3.0564999207854271e-02 + + 5.1734998822212219e-02 7.5550502538681030e-01 + <_> + + 0 -1 2228 -7.4780001305043697e-03 + + 1.4893899857997894e-01 -3.1906801462173462e-01 + <_> + + 0 -1 2229 8.9088998734951019e-02 + + 1.3738800585269928e-01 -1.1379710435867310e+00 + <_> + + 0 -1 2230 7.3230001144111156e-03 + + -2.8829199075698853e-01 1.9088600575923920e-01 + <_> + + 0 -1 2231 -1.8205000087618828e-02 + + -3.0178600549697876e-01 1.6795800626277924e-01 + <_> + + 0 -1 2232 -2.5828000158071518e-02 + + -9.8137998580932617e-01 -1.9860999658703804e-02 + <_> + + 0 -1 2233 1.0936199873685837e-01 + + 4.8790000379085541e-02 5.3118300437927246e-01 + <_> + + 0 -1 2234 -1.1424999684095383e-02 + + 2.3705999553203583e-01 -2.7925300598144531e-01 + <_> + + 0 -1 2235 -5.7565998286008835e-02 + + 4.7255399823188782e-01 6.5171003341674805e-02 + <_> + + 0 -1 2236 1.0278300195932388e-01 + + -2.0765100419521332e-01 5.0947701930999756e-01 + <_> + + 0 -1 2237 2.7041999623179436e-02 + + 1.6421200335025787e-01 -1.4508620500564575e+00 + <_> + + 0 -1 2238 -1.3635000213980675e-02 + + -5.6543898582458496e-01 2.3788999766111374e-02 + <_> + + 0 -1 2239 -3.2158198952674866e-01 + + -3.5602829456329346e+00 1.1801300197839737e-01 + <_> + + 0 -1 2240 2.0458100736141205e-01 + + -3.7016000598669052e-02 -1.0225499868392944e+00 + <_> + + 0 -1 2241 -7.0347003638744354e-02 + + -5.6491899490356445e-01 1.8525199592113495e-01 + <_> + + 0 -1 2242 3.7831000983715057e-02 + + -2.9901999980211258e-02 -8.2921499013900757e-01 + <_> + + 0 -1 2243 -7.0298001170158386e-02 + + -5.3172302246093750e-01 1.4430199563503265e-01 + <_> + + 0 -1 2244 6.3221000134944916e-02 + + -2.2041200101375580e-01 4.7952198982238770e-01 + <_> + + 0 -1 2245 3.6393001675605774e-02 + + 1.4222699403762817e-01 -6.1193901300430298e-01 + <_> + + 0 -1 2246 4.0099998004734516e-03 + + -3.4560799598693848e-01 1.1738699674606323e-01 + <_> + + 0 -1 2247 -4.9106001853942871e-02 + + 9.5984101295471191e-01 6.4934998750686646e-02 + <_> + + 0 -1 2248 -7.1583002805709839e-02 + + 1.7385669946670532e+00 -1.4252899587154388e-01 + <_> + + 0 -1 2249 -3.8008999079465866e-02 + + 1.3872820138931274e+00 6.6188000142574310e-02 + <_> + + 0 -1 2250 -3.1570000573992729e-03 + + 5.3677000105381012e-02 -5.4048001766204834e-01 + <_> + + 0 -1 2251 1.9458999857306480e-02 + + -9.3620002269744873e-02 3.9131000638008118e-01 + <_> + + 0 -1 2252 1.1293999850749969e-02 + + 3.7223998457193375e-02 -5.4251801967620850e-01 + <_> + + 0 -1 2253 -3.3495001494884491e-02 + + 9.5307898521423340e-01 3.7696998566389084e-02 + <_> + + 0 -1 2254 9.2035003006458282e-02 + + -1.3488399982452393e-01 2.2897069454193115e+00 + <_> + + 0 -1 2255 3.7529999390244484e-03 + + 2.2824199497699738e-01 -5.9983700513839722e-01 + <_> + + 0 -1 2256 1.2848000042140484e-02 + + -2.2005200386047363e-01 3.7221899628639221e-01 + <_> + + 0 -1 2257 -1.4316199719905853e-01 + + 1.2855789661407471e+00 4.7237001359462738e-02 + <_> + + 0 -1 2258 -9.6879996359348297e-02 + + -3.9550929069519043e+00 -7.2903998196125031e-02 + <_> + + 0 -1 2259 -8.8459998369216919e-03 + + 3.7674999237060547e-01 -4.6484000980854034e-02 + <_> + + 0 -1 2260 1.5900000929832458e-02 + + -2.4457000195980072e-02 -8.0034798383712769e-01 + <_> + + 0 -1 2261 7.0372000336647034e-02 + + 1.7019000649452209e-01 -6.3068997859954834e-01 + <_> + + 0 -1 2262 -3.7953998893499374e-02 + + -9.3667197227478027e-01 -4.1214000433683395e-02 + <_> + + 0 -1 2263 5.1597899198532104e-01 + + 1.3080599904060364e-01 -1.5802290439605713e+00 + <_> + + 0 -1 2264 -3.2843001186847687e-02 + + -1.1441620588302612e+00 -4.9173999577760696e-02 + <_> + + 0 -1 2265 -3.6357000470161438e-02 + + 4.9606400728225708e-01 -3.4458998590707779e-02 + <_> + + 0 -1 2266 6.8080001510679722e-03 + + -3.0997800827026367e-01 1.7054800689220428e-01 + <_> + + 0 -1 2267 -1.6114000231027603e-02 + + -3.7904599308967590e-01 1.6078999638557434e-01 + <_> + + 0 -1 2268 8.4530003368854523e-03 + + -1.8655499815940857e-01 5.6367701292037964e-01 + <_> + + 0 -1 2269 -1.3752399384975433e-01 + + -5.8989900350570679e-01 1.1749500036239624e-01 + <_> + + 0 -1 2270 1.7688000202178955e-01 + + -1.5424899756908417e-01 9.2911100387573242e-01 + <_> + + 0 -1 2271 7.9309996217489243e-03 + + 3.2190701365470886e-01 -1.6392600536346436e-01 + <_> + + 0 -1 2272 1.0971800237894058e-01 + + -1.5876500308513641e-01 1.0186259746551514e+00 + <_> + + 0 -1 2273 -3.0293000862002373e-02 + + 7.5587302446365356e-01 3.1794998794794083e-02 + <_> + + 0 -1 2274 -2.3118000477552414e-02 + + -8.8451498746871948e-01 -9.5039997249841690e-03 + <_> + + 0 -1 2275 -3.0900000128895044e-03 + + 2.3838299512863159e-01 -1.1606200039386749e-01 + <_> + + 0 -1 2276 -3.3392000943422318e-02 + + -1.8738139867782593e+00 -6.8502999842166901e-02 + <_> + + 0 -1 2277 1.3190000317990780e-02 + + 1.2919899821281433e-01 -6.7512202262878418e-01 + <_> + + 0 -1 2278 1.4661000110208988e-02 + + -2.4829000234603882e-02 -7.4396800994873047e-01 + <_> + + 0 -1 2279 -1.3248000293970108e-02 + + 4.6820199489593506e-01 -2.4165000766515732e-02 + <_> + + 0 -1 2280 -1.6218999400734901e-02 + + 4.0083798766136169e-01 -2.1255700290203094e-01 + <_> + + 0 -1 2281 -2.9052000492811203e-02 + + -1.5650019645690918e+00 1.4375899732112885e-01 + <_> + + 0 -1 2282 -1.0153199732303619e-01 + + -1.9220689535140991e+00 -6.9559998810291290e-02 + <_> + + 0 -1 2283 3.7753999233245850e-02 + + 1.3396799564361572e-01 -2.2639141082763672e+00 + <_> + + 0 -1 2284 -2.8555598855018616e-01 + + 1.0215270519256592e+00 -1.5232199430465698e-01 + <_> + + 0 -1 2285 1.5360699594020844e-01 + + -9.7409002482891083e-02 4.1662400960922241e-01 + <_> + + 0 -1 2286 -2.1199999901000410e-04 + + 1.1271899938583374e-01 -4.1653999686241150e-01 + <_> + + 0 -1 2287 -2.0597999915480614e-02 + + 6.0540497303009033e-01 6.2467999756336212e-02 + <_> + + 0 -1 2288 3.7353999912738800e-02 + + -1.8919000029563904e-01 4.6464699506759644e-01 + <_> + + 0 -1 2289 5.7275000959634781e-02 + + 1.1565300077199936e-01 -1.3213009834289551e+00 + <_> + + 0 -1 2290 5.1029999740421772e-03 + + -2.8061500191688538e-01 1.9313399493694305e-01 + <_> + + 0 -1 2291 -5.4644998162984848e-02 + + 7.2428500652313232e-01 7.5447998940944672e-02 + <_> + + 0 -1 2292 2.5349000468850136e-02 + + -1.9481800496578217e-01 4.6032801270484924e-01 + <_> + + 0 -1 2293 2.4311000481247902e-02 + + 1.5564100444316864e-01 -4.9913901090621948e-01 + <_> + + 0 -1 2294 3.5962000489234924e-02 + + -5.8573000133037567e-02 -1.5418399572372437e+00 + <_> + + 0 -1 2295 -1.0000699758529663e-01 + + -1.6100039482116699e+00 1.1450500041246414e-01 + <_> + + 0 -1 2296 8.4435999393463135e-02 + + -6.1406999826431274e-02 -1.4673349857330322e+00 + <_> + + 0 -1 2297 1.5947999432682991e-02 + + 1.6287900507450104e-01 -1.1026400327682495e-01 + <_> + + 0 -1 2298 3.3824000507593155e-02 + + -1.7932699620723724e-01 5.7218402624130249e-01 + <_> + + 0 -1 2299 -6.1996001750230789e-02 + + 4.6511812210083008e+00 9.4534002244472504e-02 + <_> + + 0 -1 2300 6.9876998662948608e-02 + + -1.6985900700092316e-01 8.7028998136520386e-01 + <_> + + 0 -1 2301 -2.7916999533772469e-02 + + 9.1042500734329224e-01 5.6827001273632050e-02 + <_> + + 0 -1 2302 -1.2764000333845615e-02 + + 2.2066700458526611e-01 -2.7769100666046143e-01 + <_> + 199 + -3.2573320865631104e+00 + + <_> + + 0 -1 2303 2.1662000566720963e-02 + + -8.9868897199630737e-01 2.9436299204826355e-01 + <_> + + 0 -1 2304 1.0044500231742859e-01 + + -3.7659201025962830e-01 6.0891002416610718e-01 + <_> + + 0 -1 2305 2.6003999635577202e-02 + + -3.8128501176834106e-01 3.9217400550842285e-01 + <_> + + 0 -1 2306 2.8441000729799271e-02 + + -1.8182300031185150e-01 5.8927202224731445e-01 + <_> + + 0 -1 2307 3.8612000644207001e-02 + + -2.2399599850177765e-01 6.3779997825622559e-01 + <_> + + 0 -1 2308 -4.6594999730587006e-02 + + 7.0812201499938965e-01 -1.4666199684143066e-01 + <_> + + 0 -1 2309 -4.2791999876499176e-02 + + 4.7680398821830750e-01 -2.9233199357986450e-01 + <_> + + 0 -1 2310 3.7960000336170197e-03 + + -1.8510299921035767e-01 5.2626699209213257e-01 + <_> + + 0 -1 2311 4.2348999530076981e-02 + + 3.9244998246431351e-02 -8.9197701215744019e-01 + <_> + + 0 -1 2312 1.9598999992012978e-02 + + -2.3358400166034698e-01 4.4146499037742615e-01 + <_> + + 0 -1 2313 8.7400001939386129e-04 + + -4.6063598990440369e-01 1.7689600586891174e-01 + <_> + + 0 -1 2314 -4.3629999272525311e-03 + + 3.3493199944496155e-01 -2.9893401265144348e-01 + <_> + + 0 -1 2315 1.6973000019788742e-02 + + -1.6408699750900269e-01 1.5993679761886597e+00 + <_> + + 0 -1 2316 3.6063998937606812e-02 + + 2.2601699829101562e-01 -5.3186100721359253e-01 + <_> + + 0 -1 2317 -7.0864997804164886e-02 + + 1.5220500528812408e-01 -4.1914600133895874e-01 + <_> + + 0 -1 2318 -6.3075996935367584e-02 + + -1.4874019622802734e+00 1.2953700125217438e-01 + <_> + + 0 -1 2319 2.9670000076293945e-02 + + -1.9145900011062622e-01 9.8184901475906372e-01 + <_> + + 0 -1 2320 3.7873998284339905e-02 + + 1.3459500670433044e-01 -5.6316298246383667e-01 + <_> + + 0 -1 2321 -3.3289000391960144e-02 + + -1.0828030109405518e+00 -1.1504000052809715e-02 + <_> + + 0 -1 2322 -3.1608998775482178e-02 + + -5.9224498271942139e-01 1.3394799828529358e-01 + <_> + + 0 -1 2323 1.0740000288933516e-03 + + -4.9185800552368164e-01 9.4446003437042236e-02 + <_> + + 0 -1 2324 -7.1556001901626587e-02 + + 5.9710198640823364e-01 -3.9553001523017883e-02 + <_> + + 0 -1 2325 -8.1170000135898590e-02 + + -1.1817820072174072e+00 -2.8254000470042229e-02 + <_> + + 0 -1 2326 4.4860001653432846e-03 + + -6.1028099060058594e-01 2.2619099915027618e-01 + <_> + + 0 -1 2327 -4.2176000773906708e-02 + + -1.1435619592666626e+00 -2.9001999646425247e-02 + <_> + + 0 -1 2328 -6.5640002489089966e-02 + + -1.6470279693603516e+00 1.2810300290584564e-01 + <_> + + 0 -1 2329 1.8188999965786934e-02 + + -3.1149399280548096e-01 2.5739601254463196e-01 + <_> + + 0 -1 2330 -5.1520001143217087e-02 + + -6.9206899404525757e-01 1.5270799398422241e-01 + <_> + + 0 -1 2331 -4.7150999307632446e-02 + + -7.1868300437927246e-01 2.6879999786615372e-03 + <_> + + 0 -1 2332 1.7488999292254448e-02 + + 2.2371199727058411e-01 -5.5381798744201660e-01 + <_> + + 0 -1 2333 -2.5264000520110130e-02 + + 1.0319819450378418e+00 -1.7496499419212341e-01 + <_> + + 0 -1 2334 -4.0745001286268234e-02 + + 4.4961598515510559e-01 3.9349000900983810e-02 + <_> + + 0 -1 2335 -3.7666998803615570e-02 + + -8.5475701093673706e-01 -1.2463999912142754e-02 + <_> + + 0 -1 2336 -1.3411000370979309e-02 + + 5.7845598459243774e-01 -1.7467999830842018e-02 + <_> + + 0 -1 2337 -7.8999997640494257e-05 + + -3.7749201059341431e-01 1.3961799442768097e-01 + <_> + + 0 -1 2338 -1.1415000073611736e-02 + + -2.6186600327491760e-01 2.3712499439716339e-01 + <_> + + 0 -1 2339 3.7200000137090683e-02 + + -2.8626000508666039e-02 -1.2945239543914795e+00 + <_> + + 0 -1 2340 3.4050000831484795e-03 + + 2.0531399548053741e-01 -1.8747499585151672e-01 + <_> + + 0 -1 2341 -2.2483000531792641e-02 + + 6.7027199268341064e-01 -1.9594000279903412e-01 + <_> + + 0 -1 2342 2.3274999111890793e-02 + + 1.7405399680137634e-01 -3.2746300101280212e-01 + <_> + + 0 -1 2343 -1.3917000032961369e-02 + + -8.3954298496246338e-01 -6.3760001212358475e-03 + <_> + + 0 -1 2344 7.5429999269545078e-03 + + -3.4194998443126678e-02 5.8998197317123413e-01 + <_> + + 0 -1 2345 -1.1539000086486340e-02 + + 4.2142799496650696e-01 -2.3510499298572540e-01 + <_> + + 0 -1 2346 5.2501998841762543e-02 + + 6.9303996860980988e-02 7.3226499557495117e-01 + <_> + + 0 -1 2347 5.2715998142957687e-02 + + -1.5688100457191467e-01 1.0907289981842041e+00 + <_> + + 0 -1 2348 -1.1726000346243382e-02 + + -7.0934301614761353e-01 1.6828800737857819e-01 + <_> + + 0 -1 2349 9.5945999026298523e-02 + + -1.6192899644374847e-01 1.0072519779205322e+00 + <_> + + 0 -1 2350 -1.5871999785304070e-02 + + 3.9008399844169617e-01 -5.3777001798152924e-02 + <_> + + 0 -1 2351 3.4818001091480255e-02 + + 1.7179999500513077e-02 -9.3941801786422729e-01 + <_> + + 0 -1 2352 3.4791998565196991e-02 + + 5.0462998449802399e-02 5.4465699195861816e-01 + <_> + + 0 -1 2353 1.6284000128507614e-02 + + -2.6981300115585327e-01 4.0365299582481384e-01 + <_> + + 0 -1 2354 -4.4319000095129013e-02 + + 8.4399998188018799e-01 3.2882999628782272e-02 + <_> + + 0 -1 2355 -5.5689997971057892e-03 + + 1.5309399366378784e-01 -3.4959799051284790e-01 + <_> + + 0 -1 2356 -6.5842002630233765e-02 + + -9.2711198329925537e-01 1.6800999641418457e-01 + <_> + + 0 -1 2357 -7.3337003588676453e-02 + + 5.1614499092102051e-01 -2.0236000418663025e-01 + <_> + + 0 -1 2358 1.6450000926852226e-02 + + 1.3950599730014801e-01 -4.9301299452781677e-01 + <_> + + 0 -1 2359 -9.2630004510283470e-03 + + -9.0101999044418335e-01 -1.6116000711917877e-02 + <_> + + 0 -1 2360 5.9139998629689217e-03 + + 1.9858199357986450e-01 -1.6731299459934235e-01 + <_> + + 0 -1 2361 -8.4699998842552304e-04 + + 9.4005003571510315e-02 -4.1570898890495300e-01 + <_> + + 0 -1 2362 2.0532900094985962e-01 + + -6.0022000223398209e-02 7.0993602275848389e-01 + <_> + + 0 -1 2363 -1.6883000731468201e-02 + + 2.4392199516296387e-01 -3.0551800131797791e-01 + <_> + + 0 -1 2364 -1.9111000001430511e-02 + + 6.1229902505874634e-01 2.4252999573945999e-02 + <_> + + 0 -1 2365 -2.5962999090552330e-02 + + 9.0764999389648438e-01 -1.6722099483013153e-01 + <_> + + 0 -1 2366 -2.1762000396847725e-02 + + -3.1384700536727905e-01 2.0134599506855011e-01 + <_> + + 0 -1 2367 -2.4119999259710312e-02 + + -6.6588401794433594e-01 7.4559999629855156e-03 + <_> + + 0 -1 2368 4.7129999846220016e-02 + + 5.9533998370170593e-02 8.7804502248764038e-01 + <_> + + 0 -1 2369 -4.5984998345375061e-02 + + 8.0067998170852661e-01 -1.7252300679683685e-01 + <_> + + 0 -1 2370 2.6507999747991562e-02 + + 1.8774099647998810e-01 -6.0850602388381958e-01 + <_> + + 0 -1 2371 -4.8615001142024994e-02 + + 5.8644098043441772e-01 -1.9427700340747833e-01 + <_> + + 0 -1 2372 -1.8562000244855881e-02 + + -2.5587901473045349e-01 1.6326199471950531e-01 + <_> + + 0 -1 2373 1.2678000144660473e-02 + + -1.4228000305593014e-02 -7.6738101243972778e-01 + <_> + + 0 -1 2374 -1.1919999960809946e-03 + + 2.0495000481605530e-01 -1.1404299736022949e-01 + <_> + + 0 -1 2375 -4.9088999629020691e-02 + + -1.0740849971771240e+00 -3.8940999656915665e-02 + <_> + + 0 -1 2376 -1.7436999827623367e-02 + + -5.7973802089691162e-01 1.8584500253200531e-01 + <_> + + 0 -1 2377 -1.4770000241696835e-02 + + -6.6150301694869995e-01 5.3119999356567860e-03 + <_> + + 0 -1 2378 -2.2905200719833374e-01 + + -4.8305100202560425e-01 1.2326399981975555e-01 + <_> + + 0 -1 2379 -1.2707099318504333e-01 + + 5.7452601194381714e-01 -1.9420400261878967e-01 + <_> + + 0 -1 2380 1.0339000262320042e-02 + + -5.4641999304294586e-02 2.4501800537109375e-01 + <_> + + 0 -1 2381 6.9010001607239246e-03 + + 1.2180600315332413e-01 -3.8797399401664734e-01 + <_> + + 0 -1 2382 2.9025399684906006e-01 + + 1.0966199636459351e-01 -30. + <_> + + 0 -1 2383 -2.3804999887943268e-01 + + -1.7352679967880249e+00 -6.3809998333454132e-02 + <_> + + 0 -1 2384 6.2481001019477844e-02 + + 1.3523000478744507e-01 -7.0301097631454468e-01 + <_> + + 0 -1 2385 4.7109997831285000e-03 + + -4.6984100341796875e-01 6.0341998934745789e-02 + <_> + + 0 -1 2386 -2.7815999463200569e-02 + + 6.9807600975036621e-01 1.3719999697059393e-03 + <_> + + 0 -1 2387 -1.7020000144839287e-02 + + 1.6870440244674683e+00 -1.4314800500869751e-01 + <_> + + 0 -1 2388 -4.9754999577999115e-02 + + 7.9497700929641724e-01 7.7199999941512942e-04 + <_> + + 0 -1 2389 -7.4732996523380280e-02 + + -1.0132360458374023e+00 -1.9388999789953232e-02 + <_> + + 0 -1 2390 3.2009001821279526e-02 + + 1.4412100613117218e-01 -4.2139101028442383e-01 + <_> + + 0 -1 2391 -9.4463996589183807e-02 + + 5.0682598352432251e-01 -2.0478899776935577e-01 + <_> + + 0 -1 2392 -1.5426999889314175e-02 + + -1.5811300277709961e-01 1.7806899547576904e-01 + <_> + + 0 -1 2393 -4.0540001355111599e-03 + + -5.4366701841354370e-01 3.1235000118613243e-02 + <_> + + 0 -1 2394 3.0080000869929790e-03 + + -1.7376799881458282e-01 3.0441701412200928e-01 + <_> + + 0 -1 2395 -1.0091999545693398e-02 + + 2.5103801488876343e-01 -2.6224100589752197e-01 + <_> + + 0 -1 2396 -3.8818001747131348e-02 + + 9.3226701021194458e-01 7.2659999132156372e-02 + <_> + + 0 -1 2397 3.4651998430490494e-02 + + -3.3934999257326126e-02 -8.5707902908325195e-01 + <_> + + 0 -1 2398 -4.6729999594390392e-03 + + 3.4969300031661987e-01 -4.8517998307943344e-02 + <_> + + 0 -1 2399 6.8499997723847628e-04 + + 6.6573001444339752e-02 -4.4973799586296082e-01 + <_> + + 0 -1 2400 3.5317000001668930e-02 + + 1.4275799691677094e-01 -4.6726399660110474e-01 + <_> + + 0 -1 2401 -2.3569999262690544e-02 + + -1.0286079645156860e+00 -4.5288000255823135e-02 + <_> + + 0 -1 2402 -1.9109999993816018e-03 + + -1.9652199745178223e-01 2.8661000728607178e-01 + <_> + + 0 -1 2403 -1.6659000888466835e-02 + + -7.7532202005386353e-01 -8.3280000835657120e-03 + <_> + + 0 -1 2404 6.6062200069427490e-01 + + 1.3232499361038208e-01 -3.5266680717468262e+00 + <_> + + 0 -1 2405 1.0970599949359894e-01 + + -1.5547199547290802e-01 1.4674140214920044e+00 + <_> + + 0 -1 2406 1.3500999659299850e-02 + + 1.5233400464057922e-01 -1.3020930290222168e+00 + <_> + + 0 -1 2407 -2.2871999070048332e-02 + + -7.1325999498367310e-01 -8.7040001526474953e-03 + <_> + + 0 -1 2408 -8.1821002066135406e-02 + + 1.1127580404281616e+00 8.3219997584819794e-02 + <_> + + 0 -1 2409 -5.2728001028299332e-02 + + 9.3165099620819092e-01 -1.7103999853134155e-01 + <_> + + 0 -1 2410 -2.5242000818252563e-02 + + -1.9733799993991852e-01 2.5359401106834412e-01 + <_> + + 0 -1 2411 -4.3818999081850052e-02 + + 4.1815200448036194e-01 -2.4585500359535217e-01 + <_> + + 0 -1 2412 -1.8188999965786934e-02 + + -5.1743197441101074e-01 2.0174199342727661e-01 + <_> + + 0 -1 2413 2.3466000333428383e-02 + + -4.3071001768112183e-02 -1.0636579990386963e+00 + <_> + + 0 -1 2414 3.4216001629829407e-02 + + 5.3780999034643173e-02 4.9707201123237610e-01 + <_> + + 0 -1 2415 2.5692999362945557e-02 + + -2.3800100386142731e-01 4.1651499271392822e-01 + <_> + + 0 -1 2416 -2.6565000414848328e-02 + + -8.8574802875518799e-01 1.3365900516510010e-01 + <_> + + 0 -1 2417 6.0942001640796661e-02 + + -2.0669700205326080e-01 5.8309000730514526e-01 + <_> + + 0 -1 2418 1.4474500715732574e-01 + + 1.3282300531864166e-01 -3.1449348926544189e+00 + <_> + + 0 -1 2419 5.3410999476909637e-02 + + -1.7325200140476227e-01 6.9190698862075806e-01 + <_> + + 0 -1 2420 1.1408000253140926e-02 + + 5.4822001606225967e-02 3.0240398645401001e-01 + <_> + + 0 -1 2421 -2.3179999552667141e-03 + + 1.5820899605751038e-01 -3.1973201036453247e-01 + <_> + + 0 -1 2422 -2.9695000499486923e-02 + + 7.1274799108505249e-01 5.8136001229286194e-02 + <_> + + 0 -1 2423 2.7249999344348907e-02 + + -1.5754100680351257e-01 9.2143797874450684e-01 + <_> + + 0 -1 2424 -3.6200000904500484e-03 + + -3.4548398852348328e-01 2.0220999419689178e-01 + <_> + + 0 -1 2425 -1.2578999623656273e-02 + + -5.5650299787521362e-01 2.0388999953866005e-02 + <_> + + 0 -1 2426 -8.8849000632762909e-02 + + -3.6100010871887207e+00 1.3164199888706207e-01 + <_> + + 0 -1 2427 -1.9256999716162682e-02 + + 5.1908999681472778e-01 -1.9284300506114960e-01 + <_> + + 0 -1 2428 -1.6666999086737633e-02 + + -8.7499998509883881e-02 1.5812499821186066e-01 + <_> + + 0 -1 2429 1.2931999750435352e-02 + + 2.7405999600887299e-02 -5.5123901367187500e-01 + <_> + + 0 -1 2430 -1.3431999832391739e-02 + + 2.3457799851894379e-01 -4.3235000222921371e-02 + <_> + + 0 -1 2431 1.8810000270605087e-02 + + -3.9680998772382736e-02 -9.4373297691345215e-01 + <_> + + 0 -1 2432 -6.4349998719990253e-03 + + 4.5703700184822083e-01 -4.0520001202821732e-03 + <_> + + 0 -1 2433 -2.4249000474810600e-02 + + -7.6248002052307129e-01 -1.9857000559568405e-02 + <_> + + 0 -1 2434 -2.9667999595403671e-02 + + -3.7412509918212891e+00 1.1250600218772888e-01 + <_> + + 0 -1 2435 5.1150000654160976e-03 + + -6.3781797885894775e-01 1.1223999783396721e-02 + <_> + + 0 -1 2436 -5.7819997891783714e-03 + + 1.9374400377273560e-01 -8.2042001187801361e-02 + <_> + + 0 -1 2437 1.6606999561190605e-02 + + -1.6192099452018738e-01 1.1334990262985229e+00 + <_> + + 0 -1 2438 3.8228001445531845e-02 + + 2.1105000749230385e-02 7.6264202594757080e-01 + <_> + + 0 -1 2439 -5.7094000279903412e-02 + + -1.6974929571151733e+00 -5.9762001037597656e-02 + <_> + + 0 -1 2440 -5.3883001208305359e-02 + + 1.1850190162658691e+00 9.0966999530792236e-02 + <_> + + 0 -1 2441 -2.6110000908374786e-03 + + -4.0941199660301208e-01 8.3820998668670654e-02 + <_> + + 0 -1 2442 2.9714399576187134e-01 + + 1.5529899299144745e-01 -1.0995409488677979e+00 + <_> + + 0 -1 2443 -8.9063003659248352e-02 + + 4.8947200179100037e-01 -2.0041200518608093e-01 + <_> + + 0 -1 2444 -5.6193001568317413e-02 + + -2.4581399559974670e-01 1.4365500211715698e-01 + <_> + + 0 -1 2445 3.7004999816417694e-02 + + -4.8168998211622238e-02 -1.2310709953308105e+00 + <_> + + 0 -1 2446 -8.4840003401041031e-03 + + 4.3372601270675659e-01 1.3779999688267708e-02 + <_> + + 0 -1 2447 -2.4379999376833439e-03 + + 1.8949699401855469e-01 -3.2294198870658875e-01 + <_> + + 0 -1 2448 -7.1639999747276306e-02 + + -4.3979001045227051e-01 2.2730199992656708e-01 + <_> + + 0 -1 2449 5.2260002121329308e-03 + + -2.0548400282859802e-01 5.0933301448822021e-01 + <_> + + 0 -1 2450 -6.1360001564025879e-03 + + 3.1157198548316956e-01 7.0680998265743256e-02 + <_> + + 0 -1 2451 1.5595000237226486e-02 + + -3.0934798717498779e-01 1.5627700090408325e-01 + <_> + + 0 -1 2452 2.5995999574661255e-02 + + 1.3821600377559662e-01 -1.7616599798202515e-01 + <_> + + 0 -1 2453 -1.2085000053048134e-02 + + -5.1070201396942139e-01 5.8440998196601868e-02 + <_> + + 0 -1 2454 -6.7836001515388489e-02 + + 4.7757101058959961e-01 -7.1446001529693604e-02 + <_> + + 0 -1 2455 -1.4715000055730343e-02 + + 4.5238900184631348e-01 -1.9861400127410889e-01 + <_> + + 0 -1 2456 2.5118999183177948e-02 + + 1.2954899668693542e-01 -8.6266398429870605e-01 + <_> + + 0 -1 2457 1.8826000392436981e-02 + + -4.1570000350475311e-02 -1.1354700326919556e+00 + <_> + + 0 -1 2458 -2.1263999864459038e-02 + + -3.4738001227378845e-01 1.5779499709606171e-01 + <_> + + 0 -1 2459 9.4609996303915977e-03 + + 4.8639997839927673e-03 -6.1654800176620483e-01 + <_> + + 0 -1 2460 2.2957700490951538e-01 + + 8.1372998654842377e-02 6.9841402769088745e-01 + <_> + + 0 -1 2461 -3.8061998784542084e-02 + + 1.1616369485855103e+00 -1.4976699650287628e-01 + <_> + + 0 -1 2462 -1.3484999537467957e-02 + + -3.2036399841308594e-01 1.7365099489688873e-01 + <_> + + 0 -1 2463 3.6238998174667358e-02 + + -1.8158499896526337e-01 6.1956697702407837e-01 + <_> + + 0 -1 2464 6.7210001870989799e-03 + + 7.9600000753998756e-04 4.2441400885581970e-01 + <_> + + 0 -1 2465 9.6525996923446655e-02 + + -1.4696800708770752e-01 1.2525680065155029e+00 + <_> + + 0 -1 2466 -3.5656999796628952e-02 + + -3.9781698584556580e-01 1.4191399514675140e-01 + <_> + + 0 -1 2467 1.0772000066936016e-02 + + -1.8194000422954559e-01 5.9762197732925415e-01 + <_> + + 0 -1 2468 7.9279996454715729e-02 + + 1.4642499387264252e-01 -7.8836899995803833e-01 + <_> + + 0 -1 2469 3.2841000705957413e-02 + + -6.2408000230789185e-02 -1.4227490425109863e+00 + <_> + + 0 -1 2470 -2.7781000360846519e-02 + + 3.4033098816871643e-01 3.0670000240206718e-02 + <_> + + 0 -1 2471 -4.0339999832212925e-03 + + 3.1084701418876648e-01 -2.2595700621604919e-01 + <_> + + 0 -1 2472 7.4260002002120018e-03 + + -3.8936998695135117e-02 3.1702101230621338e-01 + <_> + + 0 -1 2473 1.1213999986648560e-01 + + -1.7578299343585968e-01 6.5056598186492920e-01 + <_> + + 0 -1 2474 -1.1878100037574768e-01 + + -1.0092990398406982e+00 1.1069700121879578e-01 + <_> + + 0 -1 2475 -4.1584998369216919e-02 + + -5.3806400299072266e-01 1.9905000925064087e-02 + <_> + + 0 -1 2476 -2.7966000139713287e-02 + + 4.8143199086189270e-01 3.3590998500585556e-02 + <_> + + 0 -1 2477 -1.2506400048732758e-01 + + 2.6352199912071228e-01 -2.5737899541854858e-01 + <_> + + 0 -1 2478 2.3666900396347046e-01 + + 3.6508001387119293e-02 9.0655601024627686e-01 + <_> + + 0 -1 2479 -2.9475999996066093e-02 + + -6.0048800706863403e-01 9.5880003646016121e-03 + <_> + + 0 -1 2480 3.7792999297380447e-02 + + 1.5506200492382050e-01 -9.5733499526977539e-01 + <_> + + 0 -1 2481 7.2044000029563904e-02 + + -1.4525899291038513e-01 1.3676730394363403e+00 + <_> + + 0 -1 2482 9.7759999334812164e-03 + + 1.2915999628603458e-02 2.1640899777412415e-01 + <_> + + 0 -1 2483 5.2154000848531723e-02 + + -1.6359999775886536e-02 -8.8356298208236694e-01 + <_> + + 0 -1 2484 -4.3790999799966812e-02 + + 3.5829600691795349e-01 6.5131001174449921e-02 + <_> + + 0 -1 2485 -3.8378998637199402e-02 + + 1.1961040496826172e+00 -1.4971500635147095e-01 + <_> + + 0 -1 2486 -9.8838999867439270e-02 + + -6.1834001541137695e-01 1.2786200642585754e-01 + <_> + + 0 -1 2487 -1.2190700322389603e-01 + + -1.8276120424270630e+00 -6.4862996339797974e-02 + <_> + + 0 -1 2488 -1.1981700360774994e-01 + + -30. 1.1323300004005432e-01 + <_> + + 0 -1 2489 3.0910000205039978e-02 + + -2.3934000730514526e-01 3.6332899332046509e-01 + <_> + + 0 -1 2490 1.0800999589264393e-02 + + -3.5140000283718109e-02 2.7707898616790771e-01 + <_> + + 0 -1 2491 5.6844998151063919e-02 + + -1.5524299442768097e-01 1.0802700519561768e+00 + <_> + + 0 -1 2492 1.0280000278726220e-03 + + -6.1202999204397202e-02 2.0508000254631042e-01 + <_> + + 0 -1 2493 -2.8273999691009521e-02 + + -6.4778000116348267e-01 2.3917000740766525e-02 + <_> + + 0 -1 2494 -1.6013599932193756e-01 + + 1.0892050266265869e+00 5.8389000594615936e-02 + <_> + + 0 -1 2495 4.9629998393356800e-03 + + -2.5806298851966858e-01 2.0834599435329437e-01 + <_> + + 0 -1 2496 4.6937000006437302e-02 + + 1.3886299729347229e-01 -1.5662620067596436e+00 + <_> + + 0 -1 2497 2.4286000058054924e-02 + + -2.0728300511837006e-01 5.2430999279022217e-01 + <_> + + 0 -1 2498 7.0202000439167023e-02 + + 1.4796899259090424e-01 -1.3095090389251709e+00 + <_> + + 0 -1 2499 9.8120002076029778e-03 + + 2.7906000614166260e-02 -5.0864601135253906e-01 + <_> + + 0 -1 2500 -5.6200999766588211e-02 + + 1.2618130445480347e+00 6.3801996409893036e-02 + <_> + + 0 -1 2501 1.0982800275087357e-01 + + -1.2850099802017212e-01 3.0776169300079346e+00 + <_> + 211 + -3.3703000545501709e+00 + + <_> + + 0 -1 2502 2.0910000428557396e-02 + + -6.8559402227401733e-01 3.8984298706054688e-01 + <_> + + 0 -1 2503 3.5032000392675400e-02 + + -4.7724398970603943e-01 4.5027199387550354e-01 + <_> + + 0 -1 2504 3.9799001067876816e-02 + + -4.7011101245880127e-01 4.2702499032020569e-01 + <_> + + 0 -1 2505 -4.8409998416900635e-03 + + 2.5614300370216370e-01 -6.6556298732757568e-01 + <_> + + 0 -1 2506 2.3439999204128981e-03 + + -4.8083499073982239e-01 2.8013798594474792e-01 + <_> + + 0 -1 2507 2.5312999263405800e-02 + + -2.3948200047016144e-01 4.4191798567771912e-01 + <_> + + 0 -1 2508 -3.2193001359701157e-02 + + 7.6086699962615967e-01 -2.5059100985527039e-01 + <_> + + 0 -1 2509 7.5409002602100372e-02 + + -3.4974598884582520e-01 3.4380298852920532e-01 + <_> + + 0 -1 2510 -1.8469000235199928e-02 + + -7.9085600376129150e-01 3.4788001328706741e-02 + <_> + + 0 -1 2511 -1.2802000157535076e-02 + + 4.7107800841331482e-01 -6.0006000101566315e-02 + <_> + + 0 -1 2512 -2.6598000898957253e-02 + + 6.7116099596023560e-01 -2.4257500469684601e-01 + <_> + + 0 -1 2513 2.1988999098539352e-02 + + 2.4717499315738678e-01 -4.8301699757575989e-01 + <_> + + 0 -1 2514 1.4654099941253662e-01 + + -2.1504099667072296e-01 7.2055900096893311e-01 + <_> + + 0 -1 2515 3.5310001112520695e-03 + + 2.7930998802185059e-01 -3.4339898824691772e-01 + <_> + + 0 -1 2516 9.4010001048445702e-03 + + 5.5861998349428177e-02 -8.2143598794937134e-01 + <_> + + 0 -1 2517 -8.6390003561973572e-03 + + -9.9620598554611206e-01 1.8874999880790710e-01 + <_> + + 0 -1 2518 -3.9193000644445419e-02 + + -1.1945559978485107e+00 -2.9198000207543373e-02 + <_> + + 0 -1 2519 2.4855000898241997e-02 + + 1.4987599849700928e-01 -5.4137802124023438e-01 + <_> + + 0 -1 2520 -3.4995000809431076e-02 + + -1.4210180044174194e+00 -4.2314000427722931e-02 + <_> + + 0 -1 2521 -1.8378999084234238e-02 + + -2.8242599964141846e-01 1.5581800043582916e-01 + <_> + + 0 -1 2522 -1.3592000119388103e-02 + + 4.7317099571228027e-01 -2.1937200427055359e-01 + <_> + + 0 -1 2523 6.2629999592900276e-03 + + -5.9714000672101974e-02 6.0625898838043213e-01 + <_> + + 0 -1 2524 -1.8478000536561012e-02 + + -8.5647201538085938e-01 -1.3783999718725681e-02 + <_> + + 0 -1 2525 1.4236000366508961e-02 + + 1.6654799878597260e-01 -2.7713999152183533e-01 + <_> + + 0 -1 2526 -3.2547000795602798e-02 + + -1.1728240251541138e+00 -4.0185000747442245e-02 + <_> + + 0 -1 2527 -2.6410000864416361e-03 + + 2.6514300704002380e-01 -5.6343000382184982e-02 + <_> + + 0 -1 2528 -8.7799999164417386e-04 + + 3.6556001752614975e-02 -5.5075198411941528e-01 + <_> + + 0 -1 2529 4.7371998429298401e-02 + + -4.2614001780748367e-02 4.8194900155067444e-01 + <_> + + 0 -1 2530 -7.0790001191198826e-03 + + 2.8698998689651489e-01 -3.2923001050949097e-01 + <_> + + 0 -1 2531 -4.3145999312400818e-02 + + -1.4065419435501099e+00 1.2836399674415588e-01 + <_> + + 0 -1 2532 2.0592000335454941e-02 + + -2.1435299515724182e-01 5.3981798887252808e-01 + <_> + + 0 -1 2533 -2.2367000579833984e-02 + + 3.3718299865722656e-01 4.5212000608444214e-02 + <_> + + 0 -1 2534 5.0039999186992645e-02 + + -2.5121700763702393e-01 4.1750499606132507e-01 + <_> + + 0 -1 2535 6.1794999986886978e-02 + + 4.0084999054670334e-02 6.8779802322387695e-01 + <_> + + 0 -1 2536 -4.1861999779939651e-02 + + 5.3027397394180298e-01 -2.2901999950408936e-01 + <_> + + 0 -1 2537 -3.1959998887032270e-03 + + 2.5161498785018921e-01 -2.1514600515365601e-01 + <_> + + 0 -1 2538 2.4255000054836273e-02 + + 7.2320001199841499e-03 -7.2519099712371826e-01 + <_> + + 0 -1 2539 -1.7303999513387680e-02 + + -4.9958199262619019e-01 1.8394500017166138e-01 + <_> + + 0 -1 2540 -4.1470001451671124e-03 + + 8.5211999714374542e-02 -4.6364700794219971e-01 + <_> + + 0 -1 2541 -1.4369999989867210e-02 + + -5.2258902788162231e-01 2.3892599344253540e-01 + <_> + + 0 -1 2542 -9.0399999171495438e-03 + + -6.3250398635864258e-01 3.2551001757383347e-02 + <_> + + 0 -1 2543 -1.2373100221157074e-01 + + 1.2856210470199585e+00 7.6545000076293945e-02 + <_> + + 0 -1 2544 -8.2221999764442444e-02 + + 8.3208197355270386e-01 -1.8590599298477173e-01 + <_> + + 0 -1 2545 6.5659001469612122e-02 + + 1.1298800259828568e-01 -30. + <_> + + 0 -1 2546 -3.1582999974489212e-02 + + -1.3485900163650513e+00 -4.7097001224756241e-02 + <_> + + 0 -1 2547 -7.9636000096797943e-02 + + -1.3533639907836914e+00 1.5668800473213196e-01 + <_> + + 0 -1 2548 -1.8880000337958336e-02 + + 4.0300300717353821e-01 -2.5148901343345642e-01 + <_> + + 0 -1 2549 -5.0149997696280479e-03 + + -2.6287099719047546e-01 1.8582500517368317e-01 + <_> + + 0 -1 2550 -1.2218000367283821e-02 + + 5.8692401647567749e-01 -1.9427700340747833e-01 + <_> + + 0 -1 2551 1.2710000155493617e-03 + + -1.6688999533653259e-01 2.3006899654865265e-01 + <_> + + 0 -1 2552 2.9743999242782593e-02 + + 1.2520000338554382e-02 -6.6723597049713135e-01 + <_> + + 0 -1 2553 2.8175000101327896e-02 + + -1.7060000449419022e-02 6.4579397439956665e-01 + <_> + + 0 -1 2554 3.0345000326633453e-02 + + -2.4178700149059296e-01 3.4878900647163391e-01 + <_> + + 0 -1 2555 -1.7325999215245247e-02 + + -5.3599399328231812e-01 2.0995999872684479e-01 + <_> + + 0 -1 2556 -8.4178000688552856e-02 + + 7.5093299150466919e-01 -1.7593200504779816e-01 + <_> + + 0 -1 2557 7.4950000271201134e-03 + + -1.6188099980354309e-01 3.0657500028610229e-01 + <_> + + 0 -1 2558 5.6494999676942825e-02 + + -1.7318800091743469e-01 1.0016150474548340e+00 + <_> + + 0 -1 2559 -5.2939997985959053e-03 + + 2.3417599499225616e-01 -6.5347000956535339e-02 + <_> + + 0 -1 2560 -1.4945000410079956e-02 + + 2.5018900632858276e-01 -3.0591198801994324e-01 + <_> + + 0 -1 2561 5.4919000715017319e-02 + + 1.3121999800205231e-01 -9.3765097856521606e-01 + <_> + + 0 -1 2562 -1.9721999764442444e-02 + + -8.3978497982025146e-01 -2.3473000153899193e-02 + <_> + + 0 -1 2563 -6.7158997058868408e-02 + + 2.3586840629577637e+00 8.2970999181270599e-02 + <_> + + 0 -1 2564 -1.4325999654829502e-02 + + 1.8814499676227570e-01 -3.1221601366996765e-01 + <_> + + 0 -1 2565 2.9841000214219093e-02 + + 1.4825099706649780e-01 -8.4681701660156250e-01 + <_> + + 0 -1 2566 5.1883000880479813e-02 + + -4.3731000274419785e-02 -1.3366169929504395e+00 + <_> + + 0 -1 2567 4.1127000004053116e-02 + + 1.7660099267959595e-01 -6.0904097557067871e-01 + <_> + + 0 -1 2568 -1.2865099310874939e-01 + + -9.8701000213623047e-01 -3.7785001099109650e-02 + <_> + + 0 -1 2569 2.4170000106096268e-03 + + -1.6119599342346191e-01 3.2675701379776001e-01 + <_> + + 0 -1 2570 7.7030002139508724e-03 + + -2.3841500282287598e-01 2.9319399595260620e-01 + <_> + + 0 -1 2571 4.5520000159740448e-02 + + 1.4424599707126617e-01 -1.5010160207748413e+00 + <_> + + 0 -1 2572 -7.8700996935367584e-02 + + -1.0394560098648071e+00 -4.5375999063253403e-02 + <_> + + 0 -1 2573 7.8619997948408127e-03 + + 1.9633600115776062e-01 -1.4472399652004242e-01 + <_> + + 0 -1 2574 -1.3458999805152416e-02 + + -9.0634697675704956e-01 -3.8049001246690750e-02 + <_> + + 0 -1 2575 2.8827000409364700e-02 + + -2.9473999515175819e-02 6.0058397054672241e-01 + <_> + + 0 -1 2576 -2.7365999296307564e-02 + + -9.9804002046585083e-01 -3.8653001189231873e-02 + <_> + + 0 -1 2577 -7.2917997837066650e-02 + + 7.3361498117446899e-01 5.7440001517534256e-02 + <_> + + 0 -1 2578 -1.3988999649882317e-02 + + 2.7892601490020752e-01 -2.6516300439834595e-01 + <_> + + 0 -1 2579 4.3242998421192169e-02 + + 4.7760000452399254e-03 3.5925900936126709e-01 + <_> + + 0 -1 2580 2.9533000662922859e-02 + + -2.0083999633789062e-01 5.1202899217605591e-01 + <_> + + 0 -1 2581 -3.1897000968456268e-02 + + 6.4721697568893433e-01 -1.3760000001639128e-03 + <_> + + 0 -1 2582 3.7868998944759369e-02 + + -1.8363800644874573e-01 6.1343097686767578e-01 + <_> + + 0 -1 2583 -2.2417999804019928e-02 + + -2.9187899827957153e-01 1.8194800615310669e-01 + <_> + + 0 -1 2584 5.8958999812602997e-02 + + -6.6451996564865112e-02 -1.9290030002593994e+00 + <_> + + 0 -1 2585 3.1222999095916748e-02 + + -1.2732000090181828e-02 6.1560797691345215e-01 + <_> + + 0 -1 2586 3.7484999746084213e-02 + + -2.0856900513172150e-01 4.4363999366760254e-01 + <_> + + 0 -1 2587 -2.0966000854969025e-02 + + -3.5712799429893494e-01 2.4252200126647949e-01 + <_> + + 0 -1 2588 -2.5477999821305275e-02 + + 1.0846560001373291e+00 -1.5054400265216827e-01 + <_> + + 0 -1 2589 -7.2570000775158405e-03 + + 2.1302600204944611e-01 -1.8308199942111969e-01 + <_> + + 0 -1 2590 -5.0983000546693802e-02 + + 5.1736801862716675e-01 -1.8833099305629730e-01 + <_> + + 0 -1 2591 -2.0640000700950623e-02 + + -4.4030201435089111e-01 2.2745999693870544e-01 + <_> + + 0 -1 2592 1.0672999545931816e-02 + + 3.5059999674558640e-02 -5.1665002107620239e-01 + <_> + + 0 -1 2593 3.1895998865365982e-02 + + 1.3228000141680241e-02 3.4915199875831604e-01 + <_> + + 0 -1 2594 -2.3824999108910561e-02 + + 3.4118801355361938e-01 -2.1510200202465057e-01 + <_> + + 0 -1 2595 -6.0680001042783260e-03 + + 3.2937398552894592e-01 -2.8523799777030945e-01 + <_> + + 0 -1 2596 2.3881999775767326e-02 + + -2.5333800911903381e-01 2.6296100020408630e-01 + <_> + + 0 -1 2597 2.7966000139713287e-02 + + 1.4049099385738373e-01 -4.9887099862098694e-01 + <_> + + 0 -1 2598 1.4603000134229660e-02 + + -1.5395999886095524e-02 -7.6958000659942627e-01 + <_> + + 0 -1 2599 1.0872399806976318e-01 + + 1.9069600105285645e-01 -3.2393100857734680e-01 + <_> + + 0 -1 2600 -1.4038000255823135e-02 + + 3.4924700856208801e-01 -2.2358700633049011e-01 + <_> + + 0 -1 2601 4.0440000593662262e-03 + + -3.8329001516103745e-02 5.1177299022674561e-01 + <_> + + 0 -1 2602 -4.9769999459385872e-03 + + -4.2888298630714417e-01 4.9173999577760696e-02 + <_> + + 0 -1 2603 -8.5183002054691315e-02 + + 6.6624599695205688e-01 7.8079998493194580e-03 + <_> + + 0 -1 2604 2.1559998858720064e-03 + + -4.9135199189186096e-01 6.9555997848510742e-02 + <_> + + 0 -1 2605 3.6384499073028564e-01 + + 1.2997099757194519e-01 -1.8949509859085083e+00 + <_> + + 0 -1 2606 2.2082500159740448e-01 + + -5.7211998850107193e-02 -1.4281120300292969e+00 + <_> + + 0 -1 2607 -1.6140000894665718e-02 + + -5.7589399814605713e-01 1.8062500655651093e-01 + <_> + + 0 -1 2608 -4.8330001533031464e-02 + + 9.7308498620986938e-01 -1.6513000428676605e-01 + <_> + + 0 -1 2609 1.7529999837279320e-02 + + 1.7932699620723724e-01 -2.7948901057243347e-01 + <_> + + 0 -1 2610 -3.4309998154640198e-02 + + -8.1072497367858887e-01 -1.6596000641584396e-02 + <_> + + 0 -1 2611 -4.5830002054572105e-03 + + 2.7908998727798462e-01 -7.4519999325275421e-03 + <_> + + 0 -1 2612 1.2896400690078735e-01 + + -1.3508500158786774e-01 2.5411539077758789e+00 + <_> + + 0 -1 2613 3.0361000448465347e-02 + + -6.8419001996517181e-02 2.8734099864959717e-01 + <_> + + 0 -1 2614 4.4086001813411713e-02 + + -1.8135899305343628e-01 6.5413200855255127e-01 + <_> + + 0 -1 2615 3.0159999150782824e-03 + + -1.5690499544143677e-01 2.6963800191879272e-01 + <_> + + 0 -1 2616 -2.6336999610066414e-02 + + 2.9175600409507751e-01 -2.5274100899696350e-01 + <_> + + 0 -1 2617 -2.7866000309586525e-02 + + 4.4387501478195190e-01 5.5038001388311386e-02 + <_> + + 0 -1 2618 1.1725000105798244e-02 + + -1.9346499443054199e-01 4.6656700968742371e-01 + <_> + + 0 -1 2619 1.5689999563619494e-03 + + -8.2360003143548965e-03 2.5700899958610535e-01 + <_> + + 0 -1 2620 -3.5550000611692667e-03 + + -4.2430898547172546e-01 7.1174003183841705e-02 + <_> + + 0 -1 2621 -3.1695000827312469e-02 + + -8.5393500328063965e-01 1.6916200518608093e-01 + <_> + + 0 -1 2622 -3.2097000628709793e-02 + + 8.3784902095794678e-01 -1.7597299814224243e-01 + <_> + + 0 -1 2623 1.5544199943542480e-01 + + 9.9550001323223114e-02 2.3873300552368164e+00 + <_> + + 0 -1 2624 8.8045999407768250e-02 + + -1.8725299835205078e-01 6.2384301424026489e-01 + <_> + + 0 -1 2625 -1.6720000421628356e-03 + + 2.5008699297904968e-01 -6.5118998289108276e-02 + <_> + + 0 -1 2626 9.3409996479749680e-03 + + -3.5378900170326233e-01 1.0715000331401825e-01 + <_> + + 0 -1 2627 3.7138000130653381e-02 + + 1.6387000679969788e-01 -9.1718399524688721e-01 + <_> + + 0 -1 2628 8.0183997750282288e-02 + + -1.4812999963760376e-01 1.4895190000534058e+00 + <_> + + 0 -1 2629 -7.9100002767518163e-04 + + -2.1326899528503418e-01 1.9676400721073151e-01 + <_> + + 0 -1 2630 -5.0400001928210258e-03 + + -7.1318697929382324e-01 1.8240000354126096e-03 + <_> + + 0 -1 2631 1.1962399631738663e-01 + + 3.3098999410867691e-02 1.0441709756851196e+00 + <_> + + 0 -1 2632 -4.5280000194907188e-03 + + -2.7308499813079834e-01 2.7229800820350647e-01 + <_> + + 0 -1 2633 -2.9639000073075294e-02 + + 3.6225798726081848e-01 5.6795001029968262e-02 + <_> + + 0 -1 2634 2.6650000363588333e-02 + + -4.8041000962257385e-02 -9.6723502874374390e-01 + <_> + + 0 -1 2635 4.4422000646591187e-02 + + 1.3052900135517120e-01 -3.5077300667762756e-01 + <_> + + 0 -1 2636 -2.4359999224543571e-02 + + -1.0766899585723877e+00 -5.1222998648881912e-02 + <_> + + 0 -1 2637 1.9734999164938927e-02 + + 2.6238000020384789e-02 2.8070500493049622e-01 + <_> + + 0 -1 2638 5.4930001497268677e-03 + + -2.6111298799514771e-01 2.1011400222778320e-01 + <_> + + 0 -1 2639 -2.3200300335884094e-01 + + -1.7748440504074097e+00 1.1482600122690201e-01 + <_> + + 0 -1 2640 -2.5614000856876373e-02 + + 2.9900801181793213e-01 -2.2502499818801880e-01 + <_> + + 0 -1 2641 -6.4949998632073402e-03 + + 1.9563800096511841e-01 -9.9762998521327972e-02 + <_> + + 0 -1 2642 3.9840000681579113e-03 + + -4.3021500110626221e-01 8.1261001527309418e-02 + <_> + + 0 -1 2643 -3.5813000053167343e-02 + + -5.0987398624420166e-01 1.6345900297164917e-01 + <_> + + 0 -1 2644 -1.4169000089168549e-02 + + 7.7978098392486572e-01 -1.7476299405097961e-01 + <_> + + 0 -1 2645 -1.2642100453376770e-01 + + -6.3047897815704346e-01 1.2728300690650940e-01 + <_> + + 0 -1 2646 6.8677999079227448e-02 + + -4.6447999775409698e-02 -1.1128979921340942e+00 + <_> + + 0 -1 2647 8.5864998400211334e-02 + + 1.1835400015115738e-01 -4.8235158920288086e+00 + <_> + + 0 -1 2648 1.5511999838054180e-02 + + -1.7467999830842018e-02 -6.3693398237228394e-01 + <_> + + 0 -1 2649 8.1091001629829407e-02 + + 8.6133003234863281e-02 2.4559431076049805e+00 + <_> + + 0 -1 2650 1.8495000898838043e-02 + + 4.0229000151157379e-02 -5.0858199596405029e-01 + <_> + + 0 -1 2651 -8.6320996284484863e-02 + + -1.9006760120391846e+00 1.1019100248813629e-01 + <_> + + 0 -1 2652 7.2355002164840698e-02 + + -6.2111999839544296e-02 -1.4165179729461670e+00 + <_> + + 0 -1 2653 -7.8179001808166504e-02 + + 8.8849300146102905e-01 4.2369998991489410e-02 + <_> + + 0 -1 2654 9.6681997179985046e-02 + + -2.2094200551509857e-01 3.3575099706649780e-01 + <_> + + 0 -1 2655 -3.9875999093055725e-02 + + 5.7804799079895020e-01 4.5347999781370163e-02 + <_> + + 0 -1 2656 -9.5349997282028198e-03 + + -5.4175698757171631e-01 3.2399999909102917e-03 + <_> + + 0 -1 2657 4.0600000647827983e-04 + + -8.1549003720283508e-02 3.5837900638580322e-01 + <_> + + 0 -1 2658 1.2107999995350838e-02 + + -2.0280399918556213e-01 4.3768000602722168e-01 + <_> + + 0 -1 2659 -2.0873999223113060e-02 + + 4.1469898819923401e-01 -4.5568000525236130e-02 + <_> + + 0 -1 2660 5.7888001203536987e-02 + + -2.9009999707341194e-02 -9.1822302341461182e-01 + <_> + + 0 -1 2661 1.3200000103097409e-04 + + -1.1772400140762329e-01 2.0000000298023224e-01 + <_> + + 0 -1 2662 -1.7137000337243080e-02 + + 3.3004799485206604e-01 -2.3055200278759003e-01 + <_> + + 0 -1 2663 3.0655000358819962e-02 + + -2.1545000374317169e-02 2.6878198981285095e-01 + <_> + + 0 -1 2664 -7.8699999721720815e-04 + + -4.4100698828697205e-01 4.9157999455928802e-02 + <_> + + 0 -1 2665 8.8036999106407166e-02 + + 1.1782000213861465e-01 -2.8293309211730957e+00 + <_> + + 0 -1 2666 -3.9028998464345932e-02 + + 9.1777199506759644e-01 -1.5827399492263794e-01 + <_> + + 0 -1 2667 8.0105997622013092e-02 + + 1.1289200186729431e-01 -1.9937280416488647e+00 + <_> + + 0 -1 2668 3.9538998156785965e-02 + + -1.4357399940490723e-01 1.3085240125656128e+00 + <_> + + 0 -1 2669 2.0684000104665756e-02 + + 2.0048099756240845e-01 -4.4186998158693314e-02 + <_> + + 0 -1 2670 -6.7037999629974365e-02 + + 3.2618600130081177e-01 -2.0550400018692017e-01 + <_> + + 0 -1 2671 4.6815000474452972e-02 + + 1.5825299918651581e-01 -9.5535099506378174e-01 + <_> + + 0 -1 2672 7.8443996608257294e-02 + + -7.4651002883911133e-02 -2.1161499023437500e+00 + <_> + + 0 -1 2673 6.6380001604557037e-02 + + 1.1641900241374969e-01 -1.6113519668579102e+00 + <_> + + 0 -1 2674 3.0053999274969101e-02 + + -1.6562600433826447e-01 7.0025402307510376e-01 + <_> + + 0 -1 2675 1.7119999974966049e-02 + + 2.2627699375152588e-01 -4.0114998817443848e-01 + <_> + + 0 -1 2676 2.0073000341653824e-02 + + -1.9389699399471283e-01 4.4420298933982849e-01 + <_> + + 0 -1 2677 3.3101998269557953e-02 + + 1.1637499928474426e-01 -1.5771679878234863e+00 + <_> + + 0 -1 2678 -1.4882000163197517e-02 + + -8.9680302143096924e-01 -4.2010001838207245e-02 + <_> + + 0 -1 2679 -1.0281000286340714e-02 + + 3.5602998733520508e-01 -1.3124000281095505e-02 + <_> + + 0 -1 2680 -2.8695000335574150e-02 + + -4.6039599180221558e-01 2.6801999658346176e-02 + <_> + + 0 -1 2681 -4.7189998440444469e-03 + + 2.3788799345493317e-01 -6.5518997609615326e-02 + <_> + + 0 -1 2682 3.2201600074768066e-01 + + -2.8489999473094940e-02 -8.4234601259231567e-01 + <_> + + 0 -1 2683 -1.7045000568032265e-02 + + -5.0938802957534790e-01 1.6057600080966949e-01 + <_> + + 0 -1 2684 -7.3469998314976692e-03 + + -5.4154998064041138e-01 4.7320001758635044e-03 + <_> + + 0 -1 2685 -3.0001999810338020e-02 + + -8.8785797357559204e-01 1.3621799647808075e-01 + <_> + + 0 -1 2686 -1.1292999610304832e-02 + + 8.0615198612213135e-01 -1.6159500181674957e-01 + <_> + + 0 -1 2687 4.7749998047947884e-03 + + 1.2968000024557114e-02 5.5079901218414307e-01 + <_> + + 0 -1 2688 5.0710001960396767e-03 + + -4.5728001743555069e-02 -1.0766259431838989e+00 + <_> + + 0 -1 2689 1.9344100356101990e-01 + + 7.1262001991271973e-02 1.1694519519805908e+00 + <_> + + 0 -1 2690 5.3750001825392246e-03 + + -1.9736200571060181e-01 3.8206899166107178e-01 + <_> + + 0 -1 2691 -6.8276003003120422e-02 + + -5.4372339248657227e+00 1.1151900142431259e-01 + <_> + + 0 -1 2692 -3.4933000802993774e-02 + + 4.4793400168418884e-01 -1.8657900393009186e-01 + <_> + + 0 -1 2693 5.1219998858869076e-03 + + -1.4871999621391296e-02 1.8413899838924408e-01 + <_> + + 0 -1 2694 9.5311999320983887e-02 + + -1.5117099881172180e-01 9.4991499185562134e-01 + <_> + + 0 -1 2695 -6.2849000096321106e-02 + + 4.6473601460456848e-01 3.8405001163482666e-02 + <_> + + 0 -1 2696 -1.7040699720382690e-01 + + -1.6499999761581421e+00 -6.3236996531486511e-02 + <_> + + 0 -1 2697 1.0583999566733837e-02 + + -3.8348998874425888e-02 4.1913801431655884e-01 + <_> + + 0 -1 2698 -4.1579000651836395e-02 + + 3.4461900591850281e-01 -2.1187700331211090e-01 + <_> + + 0 -1 2699 1.2718600034713745e-01 + + 1.2398199737071991e-01 -2.1254889965057373e+00 + <_> + + 0 -1 2700 8.2557000219821930e-02 + + -6.2024001032114029e-02 -1.4875819683074951e+00 + <_> + + 0 -1 2701 8.5293002426624298e-02 + + 1.7087999731302261e-02 3.2076600193977356e-01 + <_> + + 0 -1 2702 5.5544000118970871e-02 + + -2.7414000034332275e-01 1.8976399302482605e-01 + <_> + + 0 -1 2703 4.5650000683963299e-03 + + -1.7920200526714325e-01 2.7967301011085510e-01 + <_> + + 0 -1 2704 1.2997999787330627e-02 + + -3.2297500967979431e-01 2.6941800117492676e-01 + <_> + + 0 -1 2705 5.7891998440027237e-02 + + 1.2644399702548981e-01 -6.0713499784469604e-01 + <_> + + 0 -1 2706 -2.2824000567197800e-02 + + -4.9682098627090454e-01 2.2376999258995056e-02 + <_> + + 0 -1 2707 4.8312000930309296e-02 + + 4.3607000261545181e-02 4.8537799715995789e-01 + <_> + + 0 -1 2708 2.5714000687003136e-02 + + -4.2950998991727829e-02 -9.3023502826690674e-01 + <_> + + 0 -1 2709 6.9269998930394650e-03 + + -2.9680000152438879e-03 3.4296301007270813e-01 + <_> + + 0 -1 2710 -3.4446999430656433e-02 + + -1.5299769639968872e+00 -6.1014998704195023e-02 + <_> + + 0 -1 2711 2.9387999325990677e-02 + + 3.7595998495817184e-02 6.4172399044036865e-01 + <_> + + 0 -1 2712 -2.4319998919963837e-03 + + 9.9088996648788452e-02 -3.9688101410865784e-01 + <_> + 200 + -2.9928278923034668e+00 + + <_> + + 0 -1 2713 -9.5944002270698547e-02 + + 6.2419098615646362e-01 -4.5875200629234314e-01 + <_> + + 0 -1 2714 1.6834000125527382e-02 + + -9.3072801828384399e-01 2.1563600003719330e-01 + <_> + + 0 -1 2715 2.6049999520182610e-02 + + -4.0532299876213074e-01 4.2256599664688110e-01 + <_> + + 0 -1 2716 3.6500001442618668e-04 + + 9.5288001000881195e-02 -6.3298100233078003e-01 + <_> + + 0 -1 2717 -6.6940002143383026e-03 + + 3.7243801355361938e-01 -3.0332401394844055e-01 + <_> + + 0 -1 2718 1.8874000757932663e-02 + + -2.3357200622558594e-01 4.0330699086189270e-01 + <_> + + 0 -1 2719 -1.6300000424962491e-04 + + 4.2886998504400253e-02 -7.7796798944473267e-01 + <_> + + 0 -1 2720 -7.6259002089500427e-02 + + -4.9628499150276184e-01 1.6335399448871613e-01 + <_> + + 0 -1 2721 5.0149001181125641e-02 + + 3.2747000455856323e-02 -8.0047899484634399e-01 + <_> + + 0 -1 2722 -2.9239999130368233e-03 + + -5.0002801418304443e-01 2.5480601191520691e-01 + <_> + + 0 -1 2723 1.6243999823927879e-02 + + 3.8913000375032425e-02 -7.0724898576736450e-01 + <_> + + 0 -1 2724 3.7811998277902603e-02 + + -6.6267997026443481e-02 7.3868799209594727e-01 + <_> + + 0 -1 2725 -1.2319999746978283e-02 + + 4.8696398735046387e-01 -2.4485599994659424e-01 + <_> + + 0 -1 2726 5.8003999292850494e-02 + + 1.3459099829196930e-01 -1.3232100009918213e-01 + <_> + + 0 -1 2727 4.8630000092089176e-03 + + -4.4172900915145874e-01 1.4005599915981293e-01 + <_> + + 0 -1 2728 4.5690998435020447e-02 + + 3.1217999756336212e-02 8.9818298816680908e-01 + <_> + + 0 -1 2729 2.1321000531315804e-02 + + 1.2008000165224075e-02 -8.6066198348999023e-01 + <_> + + 0 -1 2730 1.5679100155830383e-01 + + 1.4055999927222729e-02 8.5332900285720825e-01 + <_> + + 0 -1 2731 -1.0328999720513821e-02 + + 2.9022800922393799e-01 -2.9478800296783447e-01 + <_> + + 0 -1 2732 2.4290001019835472e-03 + + -4.0439900755882263e-01 1.9400200247764587e-01 + <_> + + 0 -1 2733 -2.3338999599218369e-02 + + 3.2945200800895691e-01 -2.5712698698043823e-01 + <_> + + 0 -1 2734 -6.8970001302659512e-03 + + -5.3352999687194824e-01 2.1635200083255768e-01 + <_> + + 0 -1 2735 -3.4403000026941299e-02 + + -1.4425489902496338e+00 -4.4682998210191727e-02 + <_> + + 0 -1 2736 -2.1235000342130661e-02 + + -7.9017502069473267e-01 1.9084100425243378e-01 + <_> + + 0 -1 2737 2.0620001014322042e-03 + + -2.6931199431419373e-01 3.1488001346588135e-01 + <_> + + 0 -1 2738 -4.2190002277493477e-03 + + -5.4464399814605713e-01 1.6574600338935852e-01 + <_> + + 0 -1 2739 -1.4334999956190586e-02 + + 2.2105000913143158e-02 -6.2342500686645508e-01 + <_> + + 0 -1 2740 -8.2120001316070557e-03 + + -4.9884998798370361e-01 1.9237099587917328e-01 + <_> + + 0 -1 2741 -9.3350000679492950e-03 + + -7.9131197929382324e-01 -1.4143999665975571e-02 + <_> + + 0 -1 2742 -3.7937998771667480e-02 + + 7.9841297864913940e-01 -3.3799000084400177e-02 + <_> + + 0 -1 2743 4.7059999778866768e-03 + + -3.3163401484489441e-01 2.0726299285888672e-01 + <_> + + 0 -1 2744 -4.4499998912215233e-03 + + -2.7256301045417786e-01 1.8402199447154999e-01 + <_> + + 0 -1 2745 5.2189999260008335e-03 + + -5.3096002340316772e-01 5.2607998251914978e-02 + <_> + + 0 -1 2746 -9.5399999991059303e-03 + + -5.6485402584075928e-01 1.9269399344921112e-01 + <_> + + 0 -1 2747 4.4969998300075531e-02 + + -1.7411500215530396e-01 9.5382601022720337e-01 + <_> + + 0 -1 2748 1.4209000393748283e-02 + + -9.1949000954627991e-02 2.4836100637912750e-01 + <_> + + 0 -1 2749 1.6380199790000916e-01 + + -5.8497000485658646e-02 -1.6404409408569336e+00 + <_> + + 0 -1 2750 2.5579999200999737e-03 + + 2.3447999358177185e-01 -9.2734001576900482e-02 + <_> + + 0 -1 2751 -3.8499999791383743e-03 + + 1.7880700528621674e-01 -3.5844099521636963e-01 + <_> + + 0 -1 2752 -2.5221999734640121e-02 + + -4.2903000116348267e-01 2.0244500041007996e-01 + <_> + + 0 -1 2753 -1.9415000453591347e-02 + + 5.8016300201416016e-01 -1.8806399405002594e-01 + <_> + + 0 -1 2754 1.4419999904930592e-02 + + 3.2846998423337936e-02 8.1980502605438232e-01 + <_> + + 0 -1 2755 5.1582999527454376e-02 + + 6.9176003336906433e-02 -4.5866298675537109e-01 + <_> + + 0 -1 2756 -3.7960000336170197e-02 + + -1.2553000450134277e+00 1.4332899451255798e-01 + <_> + + 0 -1 2757 -2.9560999944806099e-02 + + 5.3151798248291016e-01 -2.0596499741077423e-01 + <_> + + 0 -1 2758 -3.9110999554395676e-02 + + 1.1658719778060913e+00 5.3897000849246979e-02 + <_> + + 0 -1 2759 -2.9159000143408775e-02 + + 3.9307600259780884e-01 -2.2184500098228455e-01 + <_> + + 0 -1 2760 -8.3617001771926880e-02 + + -7.3744499683380127e-01 1.4268200099468231e-01 + <_> + + 0 -1 2761 4.2004001140594482e-01 + + -1.4277400076389313e-01 1.7894840240478516e+00 + <_> + + 0 -1 2762 6.0005001723766327e-02 + + 1.1976700276136398e-01 -1.8886189460754395e+00 + <_> + + 0 -1 2763 -1.8981000408530235e-02 + + -1.4148449897766113e+00 -5.6522998958826065e-02 + <_> + + 0 -1 2764 -6.0049998573958874e-03 + + 4.4170799851417542e-01 -1.0200800001621246e-01 + <_> + + 0 -1 2765 -5.8214001357555389e-02 + + -1.3918470144271851e+00 -4.8268999904394150e-02 + <_> + + 0 -1 2766 -1.2271000072360039e-02 + + 5.1317697763442993e-01 -9.3696996569633484e-02 + <_> + + 0 -1 2767 4.6585999429225922e-02 + + -5.7484000921249390e-02 -1.4283169507980347e+00 + <_> + + 0 -1 2768 1.2110000243410468e-03 + + -8.0891996622085571e-02 3.2333201169967651e-01 + <_> + + 0 -1 2769 -8.8642001152038574e-02 + + -8.6449098587036133e-01 -3.3146999776363373e-02 + <_> + + 0 -1 2770 -2.3184999823570251e-02 + + 5.2162200212478638e-01 -1.6168000176548958e-02 + <_> + + 0 -1 2771 4.3090000748634338e-02 + + -1.6153800487518311e-01 1.0915000438690186e+00 + <_> + + 0 -1 2772 2.0599999697878957e-04 + + -1.7091499269008636e-01 3.1236699223518372e-01 + <_> + + 0 -1 2773 8.9159999042749405e-03 + + -6.7039998248219490e-03 -6.8810397386550903e-01 + <_> + + 0 -1 2774 -1.7752999439835548e-02 + + 6.3292801380157471e-01 -4.2360001243650913e-03 + <_> + + 0 -1 2775 6.2299999408423901e-03 + + -3.3637198805809021e-01 1.2790599465370178e-01 + <_> + + 0 -1 2776 2.2770000621676445e-02 + + -3.4703999757766724e-02 3.9141800999641418e-01 + <_> + + 0 -1 2777 -2.1534999832510948e-02 + + 6.4765101671218872e-01 -2.0097799599170685e-01 + <_> + + 0 -1 2778 6.1758998781442642e-02 + + 5.4297000169754028e-02 9.0700101852416992e-01 + <_> + + 0 -1 2779 -7.8069999814033508e-02 + + 6.5523397922515869e-01 -1.9754399359226227e-01 + <_> + + 0 -1 2780 1.1315000243484974e-02 + + 1.9385300576686859e-01 -5.1707297563552856e-01 + <_> + + 0 -1 2781 -2.5590000674128532e-02 + + -9.3096500635147095e-01 -3.1546998769044876e-02 + <_> + + 0 -1 2782 -3.8058999925851822e-02 + + -6.8326902389526367e-01 1.2709100544452667e-01 + <_> + + 0 -1 2783 9.7970003262162209e-03 + + 1.5523999929428101e-02 -6.3347899913787842e-01 + <_> + + 0 -1 2784 -1.3841999694705009e-02 + + 1.0060529708862305e+00 6.2812998890876770e-02 + <_> + + 0 -1 2785 8.3459997549653053e-03 + + -2.3383200168609619e-01 3.0982699990272522e-01 + <_> + + 0 -1 2786 -7.1439996361732483e-02 + + -7.2505402565002441e-01 1.7148299515247345e-01 + <_> + + 0 -1 2787 1.0006000287830830e-02 + + -2.2071999311447144e-01 3.5266199707984924e-01 + <_> + + 0 -1 2788 1.1005300283432007e-01 + + 1.6662000119686127e-01 -7.4318999052047729e-01 + <_> + + 0 -1 2789 3.5310998558998108e-02 + + -2.3982700705528259e-01 4.1435998678207397e-01 + <_> + + 0 -1 2790 -1.1174699664115906e-01 + + 5.1045399904251099e-01 2.2319999989122152e-03 + <_> + + 0 -1 2791 -1.1367800086736679e-01 + + 9.0475201606750488e-01 -1.6615299880504608e-01 + <_> + + 0 -1 2792 1.6667999327182770e-02 + + 1.4024500548839569e-01 -5.2178502082824707e-01 + <_> + + 0 -1 2793 -8.0340001732110977e-03 + + -6.6178399324417114e-01 3.7640000227838755e-03 + <_> + + 0 -1 2794 -3.3096998929977417e-02 + + 8.0185902118682861e-01 5.9385001659393311e-02 + <_> + + 0 -1 2795 1.2547999620437622e-02 + + -3.3545500040054321e-01 1.4578600227832794e-01 + <_> + + 0 -1 2796 -4.2073998600244522e-02 + + -5.5509102344512939e-01 1.3266600668430328e-01 + <_> + + 0 -1 2797 2.5221999734640121e-02 + + -6.1631999909877777e-02 -1.3678770065307617e+00 + <_> + + 0 -1 2798 -2.4268999695777893e-02 + + 3.4185099601745605e-01 -7.4160001240670681e-03 + <_> + + 0 -1 2799 -1.2280000373721123e-02 + + 2.7745801210403442e-01 -3.1033900380134583e-01 + <_> + + 0 -1 2800 -1.1377099901437759e-01 + + 1.1719540357589722e+00 8.3681002259254456e-02 + <_> + + 0 -1 2801 -8.4771998226642609e-02 + + 8.1694799661636353e-01 -1.7837500572204590e-01 + <_> + + 0 -1 2802 -2.4552000686526299e-02 + + -1.8627299368381500e-01 1.4340099692344666e-01 + <_> + + 0 -1 2803 -9.0269995853304863e-03 + + 3.2659199833869934e-01 -2.3541299998760223e-01 + <_> + + 0 -1 2804 1.1177999898791313e-02 + + 1.9761200249195099e-01 -2.1701000630855560e-02 + <_> + + 0 -1 2805 -2.9366999864578247e-02 + + -9.3414801359176636e-01 -2.1704999729990959e-02 + <_> + + 0 -1 2806 6.3640000298619270e-03 + + 2.5573000311851501e-02 4.6412798762321472e-01 + <_> + + 0 -1 2807 1.4026000164449215e-02 + + -2.1228599548339844e-01 4.0078800916671753e-01 + <_> + + 0 -1 2808 -1.3341999612748623e-02 + + 7.4202698469161987e-01 2.9001999646425247e-02 + <_> + + 0 -1 2809 2.8422799706459045e-01 + + -1.9243599474430084e-01 4.3631199002265930e-01 + <_> + + 0 -1 2810 -2.3724000155925751e-01 + + 6.9736397266387939e-01 6.9307997822761536e-02 + <_> + + 0 -1 2811 -1.1169700324535370e-01 + + 3.9147201180458069e-01 -2.0922000706195831e-01 + <_> + + 0 -1 2812 1.2787500023841858e-01 + + -7.2555996477603912e-02 3.6088201403617859e-01 + <_> + + 0 -1 2813 -6.2900997698307037e-02 + + 9.5424997806549072e-01 -1.5402799844741821e-01 + <_> + + 0 -1 2814 1.7439000308513641e-02 + + -5.1134999841451645e-02 2.7750301361083984e-01 + <_> + + 0 -1 2815 1.2319999514147639e-03 + + 7.5627997517585754e-02 -3.6456099152565002e-01 + <_> + + 0 -1 2816 2.7495000511407852e-02 + + 5.1844000816345215e-02 4.1562598943710327e-01 + <_> + + 0 -1 2817 -4.3543998152017593e-02 + + 7.1969997882843018e-01 -1.7132200300693512e-01 + <_> + + 0 -1 2818 1.1025999672710896e-02 + + 1.4354600012302399e-01 -6.5403002500534058e-01 + <_> + + 0 -1 2819 2.0865999162197113e-02 + + 4.0089000016450882e-02 -4.5743298530578613e-01 + <_> + + 0 -1 2820 -2.2304000332951546e-02 + + 5.3855001926422119e-01 7.1662999689579010e-02 + <_> + + 0 -1 2821 3.2492000609636307e-02 + + -4.5991998165845871e-02 -1.0047069787979126e+00 + <_> + + 0 -1 2822 1.2269999831914902e-02 + + 3.4334998577833176e-02 4.2431798577308655e-01 + <_> + + 0 -1 2823 8.3820000290870667e-03 + + -2.5850600004196167e-01 2.6263499259948730e-01 + <_> + + 0 -1 2824 3.7353999912738800e-02 + + 1.5692499279975891e-01 -1.0429090261459351e+00 + <_> + + 0 -1 2825 -1.4111000113189220e-02 + + -7.3177701234817505e-01 -2.0276999101042747e-02 + <_> + + 0 -1 2826 5.7066999375820160e-02 + + 8.3360001444816589e-02 1.5661499500274658e+00 + <_> + + 0 -1 2827 4.9680001102387905e-03 + + -3.5318198800086975e-01 1.4698399603366852e-01 + <_> + + 0 -1 2828 -2.4492999538779259e-02 + + 2.8325900435447693e-01 -3.4640000667423010e-03 + <_> + + 0 -1 2829 -1.1254999786615372e-02 + + -8.4017497301101685e-01 -3.6251999437808990e-02 + <_> + + 0 -1 2830 3.4533001482486725e-02 + + 1.4998500049114227e-01 -8.7367099523544312e-01 + <_> + + 0 -1 2831 2.4303000420331955e-02 + + -1.8787500262260437e-01 5.9483999013900757e-01 + <_> + + 0 -1 2832 -7.8790001571178436e-03 + + 4.4315698742866516e-01 -5.6570999324321747e-02 + <_> + + 0 -1 2833 3.5142000764608383e-02 + + -5.6494999676942825e-02 -1.3617190122604370e+00 + <_> + + 0 -1 2834 4.6259998343884945e-03 + + -3.1161698698997498e-01 2.5447699427604675e-01 + <_> + + 0 -1 2835 -8.3131000399589539e-02 + + 1.6424349546432495e+00 -1.4429399371147156e-01 + <_> + + 0 -1 2836 -1.4015999622642994e-02 + + -7.7819502353668213e-01 1.7173300683498383e-01 + <_> + + 0 -1 2837 1.2450000504031777e-03 + + -2.3191399872303009e-01 2.8527900576591492e-01 + <_> + + 0 -1 2838 -1.6803000122308731e-02 + + -3.5965099930763245e-01 2.0412999391555786e-01 + <_> + + 0 -1 2839 -7.6747998595237732e-02 + + 7.8050500154495239e-01 -1.5612800419330597e-01 + <_> + + 0 -1 2840 -2.3671999573707581e-01 + + 1.1813700199127197e+00 7.8111998736858368e-02 + <_> + + 0 -1 2841 -1.0057400166988373e-01 + + -4.7104099392890930e-01 7.9172998666763306e-02 + <_> + + 0 -1 2842 1.3239999534562230e-03 + + 2.2262699902057648e-01 -3.7099799513816833e-01 + <_> + + 0 -1 2843 2.2152999415993690e-02 + + -3.8649000227451324e-02 -9.2274999618530273e-01 + <_> + + 0 -1 2844 -1.1246199905872345e-01 + + 4.1899600625038147e-01 8.0411002039909363e-02 + <_> + + 0 -1 2845 1.6481000930070877e-02 + + -1.6756699979305267e-01 7.1842402219772339e-01 + <_> + + 0 -1 2846 6.8113997578620911e-02 + + 1.5719899535179138e-01 -8.7681102752685547e-01 + <_> + + 0 -1 2847 1.6011999920010567e-02 + + -4.1600000113248825e-03 -5.9327799081802368e-01 + <_> + + 0 -1 2848 4.6640001237392426e-03 + + -3.0153999105095863e-02 4.8345300555229187e-01 + <_> + + 0 -1 2849 6.7579997703433037e-03 + + -2.2667400538921356e-01 3.3662301301956177e-01 + <_> + + 0 -1 2850 4.7289999201893806e-03 + + -6.0373999178409576e-02 3.1458100676536560e-01 + <_> + + 0 -1 2851 2.5869999080896378e-03 + + -2.9872599244117737e-01 1.7787499725818634e-01 + <_> + + 0 -1 2852 2.8989999555051327e-03 + + 2.1890200674533844e-01 -2.9567098617553711e-01 + <_> + + 0 -1 2853 -3.0053999274969101e-02 + + 1.2150429487228394e+00 -1.4354999363422394e-01 + <_> + + 0 -1 2854 1.4181000180542469e-02 + + 1.2451999820768833e-02 5.5490100383758545e-01 + <_> + + 0 -1 2855 -6.0527000576257706e-02 + + -1.4933999776840210e+00 -6.5227001905441284e-02 + <_> + + 0 -1 2856 -1.9882999360561371e-02 + + -3.8526400923728943e-01 1.9761200249195099e-01 + <_> + + 0 -1 2857 3.1218999996781349e-02 + + -2.1281200647354126e-01 2.9446500539779663e-01 + <_> + + 0 -1 2858 1.8271999433636665e-02 + + 9.7200000891461968e-04 6.6814202070236206e-01 + <_> + + 0 -1 2859 1.1089999461546540e-03 + + -6.2467902898788452e-01 -1.6599999507889152e-03 + <_> + + 0 -1 2860 -3.6713998764753342e-02 + + -4.2333900928497314e-01 1.2084700167179108e-01 + <_> + + 0 -1 2861 1.2044000439345837e-02 + + 2.5882000103592873e-02 -5.0732398033142090e-01 + <_> + + 0 -1 2862 7.4749000370502472e-02 + + 1.3184699416160583e-01 -2.1739600598812103e-01 + <_> + + 0 -1 2863 -2.3473200201988220e-01 + + 1.1775610446929932e+00 -1.5114699304103851e-01 + <_> + + 0 -1 2864 1.4096499979496002e-01 + + 3.3991001546382904e-02 3.9923098683357239e-01 + <_> + + 0 -1 2865 6.1789997853338718e-03 + + -3.1806701421737671e-01 1.1681699752807617e-01 + <_> + + 0 -1 2866 -5.7216998189687729e-02 + + 8.4399098157882690e-01 8.3889000117778778e-02 + <_> + + 0 -1 2867 -5.5227000266313553e-02 + + 3.6888301372528076e-01 -1.8913400173187256e-01 + <_> + + 0 -1 2868 -2.1583000198006630e-02 + + -5.2161800861358643e-01 1.5772600471973419e-01 + <_> + + 0 -1 2869 2.5747999548912048e-02 + + -5.9921998530626297e-02 -1.0674990415573120e+00 + <_> + + 0 -1 2870 -1.3098999857902527e-02 + + 7.8958398103713989e-01 5.2099999040365219e-02 + <_> + + 0 -1 2871 2.2799998987466097e-03 + + -1.1704430580139160e+00 -5.9356998652219772e-02 + <_> + + 0 -1 2872 8.8060004636645317e-03 + + 4.1717998683452606e-02 6.6352599859237671e-01 + <_> + + 0 -1 2873 -8.9699998497962952e-03 + + -3.5862699151039124e-01 6.0458000749349594e-02 + <_> + + 0 -1 2874 4.0230001322925091e-03 + + 2.0979399979114532e-01 -2.4806000292301178e-01 + <_> + + 0 -1 2875 2.5017000734806061e-02 + + -1.8795900046825409e-01 3.9547100663185120e-01 + <_> + + 0 -1 2876 -5.9009999968111515e-03 + + 2.5663900375366211e-01 -9.4919003546237946e-02 + <_> + + 0 -1 2877 4.3850000947713852e-03 + + 3.3139001578092575e-02 -4.6075400710105896e-01 + <_> + + 0 -1 2878 -3.3771999180316925e-02 + + -9.8881602287292480e-01 1.4636899530887604e-01 + <_> + + 0 -1 2879 4.4523000717163086e-02 + + -1.3286699354648590e-01 1.5796790122985840e+00 + <_> + + 0 -1 2880 -4.0929000824689865e-02 + + 3.3877098560333252e-01 7.4970997869968414e-02 + <_> + + 0 -1 2881 3.9351999759674072e-02 + + -1.8327899277210236e-01 4.6980699896812439e-01 + <_> + + 0 -1 2882 -7.0322997868061066e-02 + + -9.8322701454162598e-01 1.1808100342750549e-01 + <_> + + 0 -1 2883 3.5743001848459244e-02 + + -3.3050999045372009e-02 -8.3610898256301880e-01 + <_> + + 0 -1 2884 -4.2961999773979187e-02 + + 1.1670809984207153e+00 8.0692000687122345e-02 + <_> + + 0 -1 2885 -2.1007999777793884e-02 + + 6.3869798183441162e-01 -1.7626300454139709e-01 + <_> + + 0 -1 2886 -1.5742200613021851e-01 + + -2.3302499949932098e-01 1.2517499923706055e-01 + <_> + + 0 -1 2887 7.8659998252987862e-03 + + -2.2037999331951141e-01 2.7196800708770752e-01 + <_> + + 0 -1 2888 2.3622000589966774e-02 + + 1.6127300262451172e-01 -4.3329000473022461e-01 + <_> + + 0 -1 2889 7.4692003428936005e-02 + + -1.6991999745368958e-01 5.8884900808334351e-01 + <_> + + 0 -1 2890 -6.4799998654052615e-04 + + 2.5842899084091187e-01 -3.5911999642848969e-02 + <_> + + 0 -1 2891 -1.6290999948978424e-02 + + -7.6764398813247681e-01 -2.0472999662160873e-02 + <_> + + 0 -1 2892 -3.3133998513221741e-02 + + -2.7180099487304688e-01 1.4325700700283051e-01 + <_> + + 0 -1 2893 4.8797998577356339e-02 + + 7.6408997178077698e-02 -4.1445198655128479e-01 + <_> + + 0 -1 2894 2.2869999520480633e-03 + + -3.8628999143838882e-02 2.0753799378871918e-01 + <_> + + 0 -1 2895 4.5304000377655029e-02 + + -1.7777900397777557e-01 6.3461399078369141e-01 + <_> + + 0 -1 2896 1.0705800354480743e-01 + + 1.8972299993038177e-01 -5.1236200332641602e-01 + <_> + + 0 -1 2897 -4.0525000542402267e-02 + + 7.0614999532699585e-01 -1.7803299427032471e-01 + <_> + + 0 -1 2898 3.1968999654054642e-02 + + 6.8149998784065247e-02 6.8733102083206177e-01 + <_> + + 0 -1 2899 -5.7617001235485077e-02 + + 7.5170499086380005e-01 -1.5764999389648438e-01 + <_> + + 0 -1 2900 1.3593999668955803e-02 + + 1.9411900639533997e-01 -2.4561899900436401e-01 + <_> + + 0 -1 2901 7.1396000683307648e-02 + + -4.6881001442670822e-02 -8.8198298215866089e-01 + <_> + + 0 -1 2902 -1.4895999804139137e-02 + + -4.4532400369644165e-01 1.7679899930953979e-01 + <_> + + 0 -1 2903 -1.0026000440120697e-02 + + 6.5122699737548828e-01 -1.6709999740123749e-01 + <_> + + 0 -1 2904 3.7589999847114086e-03 + + -5.8301001787185669e-02 3.4483298659324646e-01 + <_> + + 0 -1 2905 1.6263000667095184e-02 + + -1.5581500530242920e-01 8.6432701349258423e-01 + <_> + + 0 -1 2906 -4.0176000446081161e-02 + + -6.1028599739074707e-01 1.1796399950981140e-01 + <_> + + 0 -1 2907 2.7080999687314034e-02 + + -4.9601998180150986e-02 -8.9990001916885376e-01 + <_> + + 0 -1 2908 5.2420001477003098e-02 + + 1.1297199875116348e-01 -1.0833640098571777e+00 + <_> + + 0 -1 2909 -1.9160000607371330e-02 + + -7.9880100488662720e-01 -3.4079000353813171e-02 + <_> + + 0 -1 2910 -3.7730000913143158e-03 + + -1.9124099612236023e-01 2.1535199880599976e-01 + <_> + + 0 -1 2911 7.5762003660202026e-02 + + -1.3421699404716492e-01 1.6807060241699219e+00 + <_> + + 0 -1 2912 -2.2173000499606133e-02 + + 4.8600998520851135e-01 3.6160000599920750e-03 + + <_> + + <_> + 6 4 12 9 -1. + <_> + 6 7 12 3 3. + <_> + + <_> + 6 4 12 7 -1. + <_> + 10 4 4 7 3. + <_> + + <_> + 3 9 18 9 -1. + <_> + 3 12 18 3 3. + <_> + + <_> + 8 18 9 6 -1. + <_> + 8 20 9 2 3. + <_> + + <_> + 3 5 4 19 -1. + <_> + 5 5 2 19 2. + <_> + + <_> + 6 5 12 16 -1. + <_> + 6 13 12 8 2. + <_> + + <_> + 5 8 12 6 -1. + <_> + 5 11 12 3 2. + <_> + + <_> + 11 14 4 10 -1. + <_> + 11 19 4 5 2. + <_> + + <_> + 4 0 7 6 -1. + <_> + 4 3 7 3 2. + <_> + + <_> + 6 6 12 6 -1. + <_> + 6 8 12 2 3. + <_> + + <_> + 6 4 12 7 -1. + <_> + 10 4 4 7 3. + <_> + + <_> + 1 8 19 12 -1. + <_> + 1 12 19 4 3. + <_> + + <_> + 0 2 24 3 -1. + <_> + 8 2 8 3 3. + <_> + + <_> + 9 9 6 15 -1. + <_> + 9 14 6 5 3. + <_> + + <_> + 5 6 14 10 -1. + <_> + 5 11 14 5 2. + <_> + + <_> + 5 0 14 9 -1. + <_> + 5 3 14 3 3. + <_> + + <_> + 13 11 9 6 -1. + <_> + 16 11 3 6 3. + <_> + + <_> + 7 5 6 10 -1. + <_> + 9 5 2 10 3. + <_> + + <_> + 10 8 6 10 -1. + <_> + 12 8 2 10 3. + <_> + + <_> + 2 5 4 9 -1. + <_> + 4 5 2 9 2. + <_> + + <_> + 18 0 6 11 -1. + <_> + 20 0 2 11 3. + <_> + + <_> + 0 6 24 13 -1. + <_> + 8 6 8 13 3. + <_> + + <_> + 9 6 6 9 -1. + <_> + 11 6 2 9 3. + <_> + + <_> + 7 18 10 6 -1. + <_> + 7 20 10 2 3. + <_> + + <_> + 5 7 14 12 -1. + <_> + 5 13 14 6 2. + <_> + + <_> + 0 3 24 3 -1. + <_> + 8 3 8 3 3. + <_> + + <_> + 5 8 15 6 -1. + <_> + 5 11 15 3 2. + <_> + + <_> + 9 6 5 14 -1. + <_> + 9 13 5 7 2. + <_> + + <_> + 9 5 6 10 -1. + <_> + 11 5 2 10 3. + <_> + + <_> + 6 6 3 12 -1. + <_> + 6 12 3 6 2. + <_> + + <_> + 3 21 18 3 -1. + <_> + 9 21 6 3 3. + <_> + + <_> + 5 6 13 6 -1. + <_> + 5 8 13 2 3. + <_> + + <_> + 18 1 6 15 -1. + <_> + 18 1 3 15 2. + <_> + + <_> + 1 1 6 15 -1. + <_> + 4 1 3 15 2. + <_> + + <_> + 0 8 24 15 -1. + <_> + 8 8 8 15 3. + <_> + + <_> + 5 6 14 12 -1. + <_> + 5 6 7 6 2. + <_> + 12 12 7 6 2. + <_> + + <_> + 2 12 21 12 -1. + <_> + 2 16 21 4 3. + <_> + + <_> + 8 1 4 10 -1. + <_> + 10 1 2 10 2. + <_> + + <_> + 2 13 20 10 -1. + <_> + 2 13 10 10 2. + <_> + + <_> + 0 1 6 13 -1. + <_> + 2 1 2 13 3. + <_> + + <_> + 20 2 4 13 -1. + <_> + 20 2 2 13 2. + <_> + + <_> + 0 5 22 19 -1. + <_> + 11 5 11 19 2. + <_> + + <_> + 18 4 6 9 -1. + <_> + 20 4 2 9 3. + <_> + + <_> + 0 3 6 11 -1. + <_> + 2 3 2 11 3. + <_> + + <_> + 12 1 4 9 -1. + <_> + 12 1 2 9 2. + <_> + + <_> + 0 6 19 3 -1. + <_> + 0 7 19 1 3. + <_> + + <_> + 12 1 4 9 -1. + <_> + 12 1 2 9 2. + <_> + + <_> + 8 1 4 9 -1. + <_> + 10 1 2 9 2. + <_> + + <_> + 5 5 14 14 -1. + <_> + 12 5 7 7 2. + <_> + 5 12 7 7 2. + <_> + + <_> + 1 10 18 2 -1. + <_> + 1 11 18 1 2. + <_> + + <_> + 17 13 4 11 -1. + <_> + 17 13 2 11 2. + <_> + + <_> + 0 4 6 9 -1. + <_> + 0 7 6 3 3. + <_> + + <_> + 6 4 12 9 -1. + <_> + 6 7 12 3 3. + <_> + + <_> + 6 5 12 6 -1. + <_> + 10 5 4 6 3. + <_> + + <_> + 0 1 24 5 -1. + <_> + 8 1 8 5 3. + <_> + + <_> + 4 10 18 6 -1. + <_> + 4 12 18 2 3. + <_> + + <_> + 2 17 12 6 -1. + <_> + 2 17 6 3 2. + <_> + 8 20 6 3 2. + <_> + + <_> + 19 3 4 13 -1. + <_> + 19 3 2 13 2. + <_> + + <_> + 1 3 4 13 -1. + <_> + 3 3 2 13 2. + <_> + + <_> + 0 1 24 23 -1. + <_> + 8 1 8 23 3. + <_> + + <_> + 1 7 8 12 -1. + <_> + 1 11 8 4 3. + <_> + + <_> + 14 7 3 14 -1. + <_> + 14 14 3 7 2. + <_> + + <_> + 3 12 16 6 -1. + <_> + 3 12 8 3 2. + <_> + 11 15 8 3 2. + <_> + + <_> + 6 6 12 6 -1. + <_> + 6 8 12 2 3. + <_> + + <_> + 8 7 6 12 -1. + <_> + 8 13 6 6 2. + <_> + + <_> + 15 15 9 6 -1. + <_> + 15 17 9 2 3. + <_> + + <_> + 1 17 18 3 -1. + <_> + 1 18 18 1 3. + <_> + + <_> + 4 4 16 12 -1. + <_> + 4 10 16 6 2. + <_> + + <_> + 0 1 4 20 -1. + <_> + 2 1 2 20 2. + <_> + + <_> + 3 0 18 2 -1. + <_> + 3 1 18 1 2. + <_> + + <_> + 1 5 20 14 -1. + <_> + 1 5 10 7 2. + <_> + 11 12 10 7 2. + <_> + + <_> + 5 8 14 12 -1. + <_> + 5 12 14 4 3. + <_> + + <_> + 3 14 7 9 -1. + <_> + 3 17 7 3 3. + <_> + + <_> + 14 15 9 6 -1. + <_> + 14 17 9 2 3. + <_> + + <_> + 1 15 9 6 -1. + <_> + 1 17 9 2 3. + <_> + + <_> + 11 6 8 10 -1. + <_> + 15 6 4 5 2. + <_> + 11 11 4 5 2. + <_> + + <_> + 5 5 14 14 -1. + <_> + 5 5 7 7 2. + <_> + 12 12 7 7 2. + <_> + + <_> + 6 0 12 5 -1. + <_> + 10 0 4 5 3. + <_> + + <_> + 9 0 6 9 -1. + <_> + 9 3 6 3 3. + <_> + + <_> + 9 6 6 9 -1. + <_> + 11 6 2 9 3. + <_> + + <_> + 7 0 6 9 -1. + <_> + 9 0 2 9 3. + <_> + + <_> + 10 6 6 9 -1. + <_> + 12 6 2 9 3. + <_> + + <_> + 8 6 6 9 -1. + <_> + 10 6 2 9 3. + <_> + + <_> + 3 8 18 4 -1. + <_> + 9 8 6 4 3. + <_> + + <_> + 6 0 12 9 -1. + <_> + 6 3 12 3 3. + <_> + + <_> + 0 0 24 6 -1. + <_> + 8 0 8 6 3. + <_> + + <_> + 4 7 16 12 -1. + <_> + 4 11 16 4 3. + <_> + + <_> + 11 6 6 6 -1. + <_> + 11 6 3 6 2. + <_> + + <_> + 0 20 24 3 -1. + <_> + 8 20 8 3 3. + <_> + + <_> + 11 6 4 9 -1. + <_> + 11 6 2 9 2. + <_> + + <_> + 4 13 15 4 -1. + <_> + 9 13 5 4 3. + <_> + + <_> + 11 6 4 9 -1. + <_> + 11 6 2 9 2. + <_> + + <_> + 9 6 4 9 -1. + <_> + 11 6 2 9 2. + <_> + + <_> + 9 12 6 12 -1. + <_> + 9 18 6 6 2. + <_> + + <_> + 1 22 18 2 -1. + <_> + 1 23 18 1 2. + <_> + + <_> + 10 7 4 10 -1. + <_> + 10 12 4 5 2. + <_> + + <_> + 6 7 8 10 -1. + <_> + 6 12 8 5 2. + <_> + + <_> + 7 6 10 6 -1. + <_> + 7 8 10 2 3. + <_> + + <_> + 0 14 10 4 -1. + <_> + 0 16 10 2 2. + <_> + + <_> + 6 18 18 2 -1. + <_> + 6 19 18 1 2. + <_> + + <_> + 1 1 22 3 -1. + <_> + 1 2 22 1 3. + <_> + + <_> + 6 16 18 3 -1. + <_> + 6 17 18 1 3. + <_> + + <_> + 2 4 6 15 -1. + <_> + 5 4 3 15 2. + <_> + + <_> + 20 4 4 10 -1. + <_> + 20 4 2 10 2. + <_> + + <_> + 0 4 4 10 -1. + <_> + 2 4 2 10 2. + <_> + + <_> + 2 16 20 6 -1. + <_> + 12 16 10 3 2. + <_> + 2 19 10 3 2. + <_> + + <_> + 0 12 8 9 -1. + <_> + 4 12 4 9 2. + <_> + + <_> + 12 0 6 9 -1. + <_> + 14 0 2 9 3. + <_> + + <_> + 5 10 6 6 -1. + <_> + 8 10 3 6 2. + <_> + + <_> + 11 8 12 6 -1. + <_> + 17 8 6 3 2. + <_> + 11 11 6 3 2. + <_> + + <_> + 0 8 12 6 -1. + <_> + 0 8 6 3 2. + <_> + 6 11 6 3 2. + <_> + + <_> + 12 0 6 9 -1. + <_> + 14 0 2 9 3. + <_> + + <_> + 6 0 6 9 -1. + <_> + 8 0 2 9 3. + <_> + + <_> + 8 14 9 6 -1. + <_> + 8 16 9 2 3. + <_> + + <_> + 0 16 9 6 -1. + <_> + 0 18 9 2 3. + <_> + + <_> + 10 8 6 10 -1. + <_> + 12 8 2 10 3. + <_> + + <_> + 3 19 12 3 -1. + <_> + 9 19 6 3 2. + <_> + + <_> + 2 10 20 2 -1. + <_> + 2 11 20 1 2. + <_> + + <_> + 2 9 18 12 -1. + <_> + 2 9 9 6 2. + <_> + 11 15 9 6 2. + <_> + + <_> + 3 0 18 24 -1. + <_> + 3 0 9 24 2. + <_> + + <_> + 5 6 14 10 -1. + <_> + 5 6 7 5 2. + <_> + 12 11 7 5 2. + <_> + + <_> + 9 5 10 12 -1. + <_> + 14 5 5 6 2. + <_> + 9 11 5 6 2. + <_> + + <_> + 4 5 12 12 -1. + <_> + 4 5 6 6 2. + <_> + 10 11 6 6 2. + <_> + + <_> + 4 14 18 3 -1. + <_> + 4 15 18 1 3. + <_> + + <_> + 6 13 8 8 -1. + <_> + 6 17 8 4 2. + <_> + + <_> + 3 16 18 6 -1. + <_> + 3 19 18 3 2. + <_> + + <_> + 0 0 6 6 -1. + <_> + 3 0 3 6 2. + <_> + + <_> + 6 6 12 18 -1. + <_> + 10 6 4 18 3. + <_> + + <_> + 6 1 4 14 -1. + <_> + 8 1 2 14 2. + <_> + + <_> + 3 2 19 2 -1. + <_> + 3 3 19 1 2. + <_> + + <_> + 1 8 22 13 -1. + <_> + 12 8 11 13 2. + <_> + + <_> + 8 9 11 4 -1. + <_> + 8 11 11 2 2. + <_> + + <_> + 0 12 15 10 -1. + <_> + 5 12 5 10 3. + <_> + + <_> + 12 16 12 6 -1. + <_> + 16 16 4 6 3. + <_> + + <_> + 0 16 12 6 -1. + <_> + 4 16 4 6 3. + <_> + + <_> + 19 1 5 12 -1. + <_> + 19 5 5 4 3. + <_> + + <_> + 0 2 24 4 -1. + <_> + 8 2 8 4 3. + <_> + + <_> + 6 8 12 4 -1. + <_> + 6 10 12 2 2. + <_> + + <_> + 7 5 9 6 -1. + <_> + 10 5 3 6 3. + <_> + + <_> + 9 17 6 6 -1. + <_> + 9 20 6 3 2. + <_> + + <_> + 0 7 22 15 -1. + <_> + 0 12 22 5 3. + <_> + + <_> + 4 1 17 9 -1. + <_> + 4 4 17 3 3. + <_> + + <_> + 7 5 6 10 -1. + <_> + 9 5 2 10 3. + <_> + + <_> + 18 1 6 8 -1. + <_> + 18 1 3 8 2. + <_> + + <_> + 0 1 6 7 -1. + <_> + 3 1 3 7 2. + <_> + + <_> + 18 0 6 22 -1. + <_> + 18 0 3 22 2. + <_> + + <_> + 0 0 6 22 -1. + <_> + 3 0 3 22 2. + <_> + + <_> + 16 7 8 16 -1. + <_> + 16 7 4 16 2. + <_> + + <_> + 2 10 19 6 -1. + <_> + 2 12 19 2 3. + <_> + + <_> + 9 9 6 12 -1. + <_> + 9 13 6 4 3. + <_> + + <_> + 2 15 17 6 -1. + <_> + 2 17 17 2 3. + <_> + + <_> + 14 7 3 14 -1. + <_> + 14 14 3 7 2. + <_> + + <_> + 5 6 8 10 -1. + <_> + 5 6 4 5 2. + <_> + 9 11 4 5 2. + <_> + + <_> + 15 8 9 11 -1. + <_> + 18 8 3 11 3. + <_> + + <_> + 0 8 9 11 -1. + <_> + 3 8 3 11 3. + <_> + + <_> + 8 6 10 18 -1. + <_> + 8 15 10 9 2. + <_> + + <_> + 7 7 3 14 -1. + <_> + 7 14 3 7 2. + <_> + + <_> + 0 14 24 8 -1. + <_> + 8 14 8 8 3. + <_> + + <_> + 1 10 18 14 -1. + <_> + 10 10 9 14 2. + <_> + + <_> + 14 12 6 6 -1. + <_> + 14 15 6 3 2. + <_> + + <_> + 7 0 10 16 -1. + <_> + 7 0 5 8 2. + <_> + 12 8 5 8 2. + <_> + + <_> + 10 0 9 6 -1. + <_> + 13 0 3 6 3. + <_> + + <_> + 4 3 16 4 -1. + <_> + 12 3 8 4 2. + <_> + + <_> + 10 0 9 6 -1. + <_> + 13 0 3 6 3. + <_> + + <_> + 1 1 20 4 -1. + <_> + 1 1 10 2 2. + <_> + 11 3 10 2 2. + <_> + + <_> + 10 0 9 6 -1. + <_> + 13 0 3 6 3. + <_> + + <_> + 5 0 9 6 -1. + <_> + 8 0 3 6 3. + <_> + + <_> + 8 18 10 6 -1. + <_> + 8 20 10 2 3. + <_> + + <_> + 6 3 6 9 -1. + <_> + 8 3 2 9 3. + <_> + + <_> + 7 3 12 6 -1. + <_> + 7 5 12 2 3. + <_> + + <_> + 0 10 18 3 -1. + <_> + 0 11 18 1 3. + <_> + + <_> + 1 10 22 3 -1. + <_> + 1 11 22 1 3. + <_> + + <_> + 5 11 8 8 -1. + <_> + 9 11 4 8 2. + <_> + + <_> + 12 11 6 6 -1. + <_> + 12 11 3 6 2. + <_> + + <_> + 6 11 6 6 -1. + <_> + 9 11 3 6 2. + <_> + + <_> + 7 10 11 6 -1. + <_> + 7 12 11 2 3. + <_> + + <_> + 0 13 24 4 -1. + <_> + 0 13 12 2 2. + <_> + 12 15 12 2 2. + <_> + + <_> + 2 4 22 12 -1. + <_> + 13 4 11 6 2. + <_> + 2 10 11 6 2. + <_> + + <_> + 2 0 20 17 -1. + <_> + 12 0 10 17 2. + <_> + + <_> + 14 0 2 24 -1. + <_> + 14 0 1 24 2. + <_> + + <_> + 8 0 2 24 -1. + <_> + 9 0 1 24 2. + <_> + + <_> + 14 1 2 22 -1. + <_> + 14 1 1 22 2. + <_> + + <_> + 8 1 2 22 -1. + <_> + 9 1 1 22 2. + <_> + + <_> + 17 6 3 18 -1. + <_> + 18 6 1 18 3. + <_> + + <_> + 6 14 9 6 -1. + <_> + 6 16 9 2 3. + <_> + + <_> + 13 14 9 4 -1. + <_> + 13 16 9 2 2. + <_> + + <_> + 3 18 18 3 -1. + <_> + 3 19 18 1 3. + <_> + + <_> + 9 4 8 18 -1. + <_> + 13 4 4 9 2. + <_> + 9 13 4 9 2. + <_> + + <_> + 0 17 18 3 -1. + <_> + 0 18 18 1 3. + <_> + + <_> + 0 2 12 4 -1. + <_> + 6 2 6 4 2. + <_> + + <_> + 6 8 14 6 -1. + <_> + 6 11 14 3 2. + <_> + + <_> + 7 5 6 6 -1. + <_> + 10 5 3 6 2. + <_> + + <_> + 10 5 6 16 -1. + <_> + 10 13 6 8 2. + <_> + + <_> + 1 4 9 16 -1. + <_> + 4 4 3 16 3. + <_> + + <_> + 5 0 18 9 -1. + <_> + 5 3 18 3 3. + <_> + + <_> + 9 15 5 8 -1. + <_> + 9 19 5 4 2. + <_> + + <_> + 20 0 4 9 -1. + <_> + 20 0 2 9 2. + <_> + + <_> + 2 0 18 3 -1. + <_> + 2 1 18 1 3. + <_> + + <_> + 5 22 19 2 -1. + <_> + 5 23 19 1 2. + <_> + + <_> + 0 0 4 9 -1. + <_> + 2 0 2 9 2. + <_> + + <_> + 5 6 19 18 -1. + <_> + 5 12 19 6 3. + <_> + + <_> + 0 1 6 9 -1. + <_> + 2 1 2 9 3. + <_> + + <_> + 6 5 14 12 -1. + <_> + 13 5 7 6 2. + <_> + 6 11 7 6 2. + <_> + + <_> + 0 1 20 2 -1. + <_> + 0 2 20 1 2. + <_> + + <_> + 1 2 22 3 -1. + <_> + 1 3 22 1 3. + <_> + + <_> + 2 8 7 9 -1. + <_> + 2 11 7 3 3. + <_> + + <_> + 2 12 22 4 -1. + <_> + 13 12 11 2 2. + <_> + 2 14 11 2 2. + <_> + + <_> + 0 12 22 4 -1. + <_> + 0 12 11 2 2. + <_> + 11 14 11 2 2. + <_> + + <_> + 9 7 6 11 -1. + <_> + 11 7 2 11 3. + <_> + + <_> + 7 1 9 6 -1. + <_> + 10 1 3 6 3. + <_> + + <_> + 11 2 4 10 -1. + <_> + 11 7 4 5 2. + <_> + + <_> + 6 4 12 12 -1. + <_> + 6 10 12 6 2. + <_> + + <_> + 18 1 6 15 -1. + <_> + 18 6 6 5 3. + <_> + + <_> + 3 15 18 3 -1. + <_> + 3 16 18 1 3. + <_> + + <_> + 18 5 6 9 -1. + <_> + 18 8 6 3 3. + <_> + + <_> + 1 5 16 6 -1. + <_> + 1 5 8 3 2. + <_> + 9 8 8 3 2. + <_> + + <_> + 11 0 6 9 -1. + <_> + 13 0 2 9 3. + <_> + + <_> + 0 4 24 14 -1. + <_> + 0 4 12 7 2. + <_> + 12 11 12 7 2. + <_> + + <_> + 13 0 4 13 -1. + <_> + 13 0 2 13 2. + <_> + + <_> + 7 0 4 13 -1. + <_> + 9 0 2 13 2. + <_> + + <_> + 11 6 6 9 -1. + <_> + 13 6 2 9 3. + <_> + + <_> + 8 7 6 9 -1. + <_> + 10 7 2 9 3. + <_> + + <_> + 13 17 9 6 -1. + <_> + 13 19 9 2 3. + <_> + + <_> + 2 18 14 6 -1. + <_> + 2 18 7 3 2. + <_> + 9 21 7 3 2. + <_> + + <_> + 3 18 18 4 -1. + <_> + 12 18 9 2 2. + <_> + 3 20 9 2 2. + <_> + + <_> + 0 20 15 4 -1. + <_> + 5 20 5 4 3. + <_> + + <_> + 9 15 15 9 -1. + <_> + 14 15 5 9 3. + <_> + + <_> + 4 4 16 4 -1. + <_> + 4 6 16 2 2. + <_> + + <_> + 7 6 10 6 -1. + <_> + 7 8 10 2 3. + <_> + + <_> + 0 14 15 10 -1. + <_> + 5 14 5 10 3. + <_> + + <_> + 7 9 10 14 -1. + <_> + 12 9 5 7 2. + <_> + 7 16 5 7 2. + <_> + + <_> + 7 6 6 9 -1. + <_> + 9 6 2 9 3. + <_> + + <_> + 3 6 18 3 -1. + <_> + 3 7 18 1 3. + <_> + + <_> + 0 10 18 3 -1. + <_> + 0 11 18 1 3. + <_> + + <_> + 3 16 18 4 -1. + <_> + 12 16 9 2 2. + <_> + 3 18 9 2 2. + <_> + + <_> + 4 6 14 6 -1. + <_> + 4 6 7 3 2. + <_> + 11 9 7 3 2. + <_> + + <_> + 13 0 2 18 -1. + <_> + 13 0 1 18 2. + <_> + + <_> + 9 0 2 18 -1. + <_> + 10 0 1 18 2. + <_> + + <_> + 5 7 15 10 -1. + <_> + 10 7 5 10 3. + <_> + + <_> + 1 20 21 4 -1. + <_> + 8 20 7 4 3. + <_> + + <_> + 10 5 5 18 -1. + <_> + 10 14 5 9 2. + <_> + + <_> + 0 2 24 6 -1. + <_> + 0 2 12 3 2. + <_> + 12 5 12 3 2. + <_> + + <_> + 1 1 22 8 -1. + <_> + 12 1 11 4 2. + <_> + 1 5 11 4 2. + <_> + + <_> + 4 0 15 9 -1. + <_> + 4 3 15 3 3. + <_> + + <_> + 0 0 24 19 -1. + <_> + 8 0 8 19 3. + <_> + + <_> + 2 21 18 3 -1. + <_> + 11 21 9 3 2. + <_> + + <_> + 9 7 10 4 -1. + <_> + 9 7 5 4 2. + <_> + + <_> + 5 7 10 4 -1. + <_> + 10 7 5 4 2. + <_> + + <_> + 17 8 6 16 -1. + <_> + 20 8 3 8 2. + <_> + 17 16 3 8 2. + <_> + + <_> + 1 15 20 4 -1. + <_> + 1 15 10 2 2. + <_> + 11 17 10 2 2. + <_> + + <_> + 14 15 10 6 -1. + <_> + 14 17 10 2 3. + <_> + + <_> + 3 0 16 9 -1. + <_> + 3 3 16 3 3. + <_> + + <_> + 15 6 7 15 -1. + <_> + 15 11 7 5 3. + <_> + + <_> + 9 1 6 13 -1. + <_> + 11 1 2 13 3. + <_> + + <_> + 17 2 6 14 -1. + <_> + 17 2 3 14 2. + <_> + + <_> + 3 14 12 10 -1. + <_> + 3 14 6 5 2. + <_> + 9 19 6 5 2. + <_> + + <_> + 7 6 10 6 -1. + <_> + 7 8 10 2 3. + <_> + + <_> + 1 2 6 14 -1. + <_> + 4 2 3 14 2. + <_> + + <_> + 10 4 5 12 -1. + <_> + 10 8 5 4 3. + <_> + + <_> + 0 17 24 5 -1. + <_> + 8 17 8 5 3. + <_> + + <_> + 15 7 5 12 -1. + <_> + 15 11 5 4 3. + <_> + + <_> + 3 1 6 12 -1. + <_> + 3 1 3 6 2. + <_> + 6 7 3 6 2. + <_> + + <_> + 12 13 6 6 -1. + <_> + 12 16 6 3 2. + <_> + + <_> + 6 13 6 6 -1. + <_> + 6 16 6 3 2. + <_> + + <_> + 14 6 3 16 -1. + <_> + 14 14 3 8 2. + <_> + + <_> + 1 12 13 6 -1. + <_> + 1 14 13 2 3. + <_> + + <_> + 13 1 4 9 -1. + <_> + 13 1 2 9 2. + <_> + + <_> + 7 0 9 6 -1. + <_> + 10 0 3 6 3. + <_> + + <_> + 12 2 6 9 -1. + <_> + 12 2 3 9 2. + <_> + + <_> + 6 2 6 9 -1. + <_> + 9 2 3 9 2. + <_> + + <_> + 6 18 12 6 -1. + <_> + 6 20 12 2 3. + <_> + + <_> + 7 6 6 9 -1. + <_> + 9 6 2 9 3. + <_> + + <_> + 7 7 12 3 -1. + <_> + 7 7 6 3 2. + <_> + + <_> + 8 3 8 21 -1. + <_> + 8 10 8 7 3. + <_> + + <_> + 7 4 10 12 -1. + <_> + 7 8 10 4 3. + <_> + + <_> + 0 1 6 9 -1. + <_> + 0 4 6 3 3. + <_> + + <_> + 15 2 2 20 -1. + <_> + 15 2 1 20 2. + <_> + + <_> + 0 3 6 9 -1. + <_> + 0 6 6 3 3. + <_> + + <_> + 15 3 2 21 -1. + <_> + 15 3 1 21 2. + <_> + + <_> + 7 0 2 23 -1. + <_> + 8 0 1 23 2. + <_> + + <_> + 15 8 9 4 -1. + <_> + 15 10 9 2 2. + <_> + + <_> + 0 8 9 4 -1. + <_> + 0 10 9 2 2. + <_> + + <_> + 8 14 9 6 -1. + <_> + 8 16 9 2 3. + <_> + + <_> + 0 14 9 6 -1. + <_> + 0 16 9 2 3. + <_> + + <_> + 3 10 18 4 -1. + <_> + 9 10 6 4 3. + <_> + + <_> + 0 0 24 19 -1. + <_> + 8 0 8 19 3. + <_> + + <_> + 9 1 8 12 -1. + <_> + 9 7 8 6 2. + <_> + + <_> + 10 6 4 10 -1. + <_> + 12 6 2 10 2. + <_> + + <_> + 7 9 10 12 -1. + <_> + 12 9 5 6 2. + <_> + 7 15 5 6 2. + <_> + + <_> + 5 0 3 19 -1. + <_> + 6 0 1 19 3. + <_> + + <_> + 14 0 6 10 -1. + <_> + 16 0 2 10 3. + <_> + + <_> + 2 0 6 12 -1. + <_> + 2 0 3 6 2. + <_> + 5 6 3 6 2. + <_> + + <_> + 0 11 24 2 -1. + <_> + 0 12 24 1 2. + <_> + + <_> + 4 9 13 4 -1. + <_> + 4 11 13 2 2. + <_> + + <_> + 9 8 6 9 -1. + <_> + 9 11 6 3 3. + <_> + + <_> + 0 12 16 4 -1. + <_> + 0 14 16 2 2. + <_> + + <_> + 18 12 6 9 -1. + <_> + 18 15 6 3 3. + <_> + + <_> + 0 12 6 9 -1. + <_> + 0 15 6 3 3. + <_> + + <_> + 8 7 10 4 -1. + <_> + 8 7 5 4 2. + <_> + + <_> + 8 7 6 9 -1. + <_> + 10 7 2 9 3. + <_> + + <_> + 11 0 6 9 -1. + <_> + 13 0 2 9 3. + <_> + + <_> + 7 0 6 9 -1. + <_> + 9 0 2 9 3. + <_> + + <_> + 12 3 6 15 -1. + <_> + 14 3 2 15 3. + <_> + + <_> + 6 3 6 15 -1. + <_> + 8 3 2 15 3. + <_> + + <_> + 15 2 9 4 -1. + <_> + 15 4 9 2 2. + <_> + + <_> + 5 10 6 7 -1. + <_> + 8 10 3 7 2. + <_> + + <_> + 9 14 6 10 -1. + <_> + 9 19 6 5 2. + <_> + + <_> + 7 13 5 8 -1. + <_> + 7 17 5 4 2. + <_> + + <_> + 14 5 3 16 -1. + <_> + 14 13 3 8 2. + <_> + + <_> + 2 17 18 3 -1. + <_> + 2 18 18 1 3. + <_> + + <_> + 5 18 19 3 -1. + <_> + 5 19 19 1 3. + <_> + + <_> + 9 0 6 9 -1. + <_> + 11 0 2 9 3. + <_> + + <_> + 12 4 3 18 -1. + <_> + 13 4 1 18 3. + <_> + + <_> + 9 4 3 18 -1. + <_> + 10 4 1 18 3. + <_> + + <_> + 3 3 18 9 -1. + <_> + 9 3 6 9 3. + <_> + + <_> + 6 1 6 14 -1. + <_> + 8 1 2 14 3. + <_> + + <_> + 12 16 9 6 -1. + <_> + 12 19 9 3 2. + <_> + + <_> + 1 3 20 16 -1. + <_> + 1 3 10 8 2. + <_> + 11 11 10 8 2. + <_> + + <_> + 12 5 6 12 -1. + <_> + 15 5 3 6 2. + <_> + 12 11 3 6 2. + <_> + + <_> + 1 2 22 16 -1. + <_> + 1 2 11 8 2. + <_> + 12 10 11 8 2. + <_> + + <_> + 10 14 5 10 -1. + <_> + 10 19 5 5 2. + <_> + + <_> + 3 21 18 3 -1. + <_> + 3 22 18 1 3. + <_> + + <_> + 10 14 6 10 -1. + <_> + 12 14 2 10 3. + <_> + + <_> + 0 2 24 4 -1. + <_> + 8 2 8 4 3. + <_> + + <_> + 6 4 12 9 -1. + <_> + 6 7 12 3 3. + <_> + + <_> + 6 6 12 5 -1. + <_> + 10 6 4 5 3. + <_> + + <_> + 5 8 14 12 -1. + <_> + 5 12 14 4 3. + <_> + + <_> + 4 14 8 10 -1. + <_> + 4 14 4 5 2. + <_> + 8 19 4 5 2. + <_> + + <_> + 11 6 5 14 -1. + <_> + 11 13 5 7 2. + <_> + + <_> + 7 6 3 16 -1. + <_> + 7 14 3 8 2. + <_> + + <_> + 3 7 18 8 -1. + <_> + 9 7 6 8 3. + <_> + + <_> + 2 3 20 2 -1. + <_> + 2 4 20 1 2. + <_> + + <_> + 3 12 19 6 -1. + <_> + 3 14 19 2 3. + <_> + + <_> + 8 6 6 9 -1. + <_> + 10 6 2 9 3. + <_> + + <_> + 16 6 6 14 -1. + <_> + 16 6 3 14 2. + <_> + + <_> + 7 9 6 12 -1. + <_> + 9 9 2 12 3. + <_> + + <_> + 18 6 6 18 -1. + <_> + 21 6 3 9 2. + <_> + 18 15 3 9 2. + <_> + + <_> + 0 6 6 18 -1. + <_> + 0 6 3 9 2. + <_> + 3 15 3 9 2. + <_> + + <_> + 18 2 6 9 -1. + <_> + 18 5 6 3 3. + <_> + + <_> + 3 18 15 6 -1. + <_> + 3 20 15 2 3. + <_> + + <_> + 18 2 6 9 -1. + <_> + 18 5 6 3 3. + <_> + + <_> + 0 2 6 9 -1. + <_> + 0 5 6 3 3. + <_> + + <_> + 5 10 18 2 -1. + <_> + 5 11 18 1 2. + <_> + + <_> + 6 0 12 6 -1. + <_> + 6 2 12 2 3. + <_> + + <_> + 10 0 6 9 -1. + <_> + 12 0 2 9 3. + <_> + + <_> + 8 0 6 9 -1. + <_> + 10 0 2 9 3. + <_> + + <_> + 15 12 9 6 -1. + <_> + 15 14 9 2 3. + <_> + + <_> + 3 6 13 6 -1. + <_> + 3 8 13 2 3. + <_> + + <_> + 15 12 9 6 -1. + <_> + 15 14 9 2 3. + <_> + + <_> + 2 5 6 15 -1. + <_> + 5 5 3 15 2. + <_> + + <_> + 8 8 9 6 -1. + <_> + 11 8 3 6 3. + <_> + + <_> + 8 6 3 14 -1. + <_> + 8 13 3 7 2. + <_> + + <_> + 15 12 9 6 -1. + <_> + 15 14 9 2 3. + <_> + + <_> + 4 12 10 4 -1. + <_> + 9 12 5 4 2. + <_> + + <_> + 13 1 4 19 -1. + <_> + 13 1 2 19 2. + <_> + + <_> + 7 1 4 19 -1. + <_> + 9 1 2 19 2. + <_> + + <_> + 18 9 6 9 -1. + <_> + 18 12 6 3 3. + <_> + + <_> + 1 21 18 3 -1. + <_> + 1 22 18 1 3. + <_> + + <_> + 14 13 10 9 -1. + <_> + 14 16 10 3 3. + <_> + + <_> + 1 13 22 4 -1. + <_> + 1 13 11 2 2. + <_> + 12 15 11 2 2. + <_> + + <_> + 4 6 16 6 -1. + <_> + 12 6 8 3 2. + <_> + 4 9 8 3 2. + <_> + + <_> + 1 0 18 22 -1. + <_> + 1 0 9 11 2. + <_> + 10 11 9 11 2. + <_> + + <_> + 10 7 8 14 -1. + <_> + 14 7 4 7 2. + <_> + 10 14 4 7 2. + <_> + + <_> + 0 4 6 20 -1. + <_> + 0 4 3 10 2. + <_> + 3 14 3 10 2. + <_> + + <_> + 15 0 6 9 -1. + <_> + 17 0 2 9 3. + <_> + + <_> + 3 0 6 9 -1. + <_> + 5 0 2 9 3. + <_> + + <_> + 15 12 6 12 -1. + <_> + 18 12 3 6 2. + <_> + 15 18 3 6 2. + <_> + + <_> + 3 12 6 12 -1. + <_> + 3 12 3 6 2. + <_> + 6 18 3 6 2. + <_> + + <_> + 15 12 9 6 -1. + <_> + 15 14 9 2 3. + <_> + + <_> + 0 12 9 6 -1. + <_> + 0 14 9 2 3. + <_> + + <_> + 4 14 19 3 -1. + <_> + 4 15 19 1 3. + <_> + + <_> + 2 13 19 3 -1. + <_> + 2 14 19 1 3. + <_> + + <_> + 14 15 10 6 -1. + <_> + 14 17 10 2 3. + <_> + + <_> + 6 0 10 12 -1. + <_> + 6 0 5 6 2. + <_> + 11 6 5 6 2. + <_> + + <_> + 17 1 6 12 -1. + <_> + 20 1 3 6 2. + <_> + 17 7 3 6 2. + <_> + + <_> + 1 1 6 12 -1. + <_> + 1 1 3 6 2. + <_> + 4 7 3 6 2. + <_> + + <_> + 16 14 6 9 -1. + <_> + 16 17 6 3 3. + <_> + + <_> + 7 3 9 12 -1. + <_> + 7 9 9 6 2. + <_> + + <_> + 12 1 4 12 -1. + <_> + 12 7 4 6 2. + <_> + + <_> + 4 0 14 8 -1. + <_> + 4 4 14 4 2. + <_> + + <_> + 10 6 6 9 -1. + <_> + 12 6 2 9 3. + <_> + + <_> + 2 10 18 3 -1. + <_> + 8 10 6 3 3. + <_> + + <_> + 15 15 9 6 -1. + <_> + 15 17 9 2 3. + <_> + + <_> + 0 1 21 23 -1. + <_> + 7 1 7 23 3. + <_> + + <_> + 6 9 17 4 -1. + <_> + 6 11 17 2 2. + <_> + + <_> + 1 0 11 18 -1. + <_> + 1 6 11 6 3. + <_> + + <_> + 6 15 13 6 -1. + <_> + 6 17 13 2 3. + <_> + + <_> + 0 15 9 6 -1. + <_> + 0 17 9 2 3. + <_> + + <_> + 8 7 15 4 -1. + <_> + 13 7 5 4 3. + <_> + + <_> + 9 12 6 9 -1. + <_> + 9 15 6 3 3. + <_> + + <_> + 6 8 18 3 -1. + <_> + 12 8 6 3 3. + <_> + + <_> + 0 14 24 4 -1. + <_> + 8 14 8 4 3. + <_> + + <_> + 16 10 3 12 -1. + <_> + 16 16 3 6 2. + <_> + + <_> + 0 3 24 3 -1. + <_> + 0 4 24 1 3. + <_> + + <_> + 14 17 10 6 -1. + <_> + 14 19 10 2 3. + <_> + + <_> + 1 13 18 3 -1. + <_> + 7 13 6 3 3. + <_> + + <_> + 5 0 18 9 -1. + <_> + 5 3 18 3 3. + <_> + + <_> + 4 3 16 9 -1. + <_> + 4 6 16 3 3. + <_> + + <_> + 16 5 3 12 -1. + <_> + 16 11 3 6 2. + <_> + + <_> + 0 7 18 4 -1. + <_> + 6 7 6 4 3. + <_> + + <_> + 10 6 6 9 -1. + <_> + 12 6 2 9 3. + <_> + + <_> + 9 8 6 10 -1. + <_> + 11 8 2 10 3. + <_> + + <_> + 9 15 6 9 -1. + <_> + 11 15 2 9 3. + <_> + + <_> + 3 1 18 21 -1. + <_> + 12 1 9 21 2. + <_> + + <_> + 6 8 12 7 -1. + <_> + 6 8 6 7 2. + <_> + + <_> + 8 5 6 9 -1. + <_> + 10 5 2 9 3. + <_> + + <_> + 0 2 24 4 -1. + <_> + 8 2 8 4 3. + <_> + + <_> + 14 7 5 12 -1. + <_> + 14 11 5 4 3. + <_> + + <_> + 5 7 5 12 -1. + <_> + 5 11 5 4 3. + <_> + + <_> + 9 6 6 9 -1. + <_> + 11 6 2 9 3. + <_> + + <_> + 0 1 6 17 -1. + <_> + 3 1 3 17 2. + <_> + + <_> + 3 1 19 9 -1. + <_> + 3 4 19 3 3. + <_> + + <_> + 3 18 12 6 -1. + <_> + 3 18 6 3 2. + <_> + 9 21 6 3 2. + <_> + + <_> + 20 4 4 19 -1. + <_> + 20 4 2 19 2. + <_> + + <_> + 0 16 10 7 -1. + <_> + 5 16 5 7 2. + <_> + + <_> + 8 7 10 12 -1. + <_> + 13 7 5 6 2. + <_> + 8 13 5 6 2. + <_> + + <_> + 6 7 10 12 -1. + <_> + 6 7 5 6 2. + <_> + 11 13 5 6 2. + <_> + + <_> + 9 2 9 6 -1. + <_> + 12 2 3 6 3. + <_> + + <_> + 1 20 21 4 -1. + <_> + 8 20 7 4 3. + <_> + + <_> + 9 12 9 6 -1. + <_> + 9 14 9 2 3. + <_> + + <_> + 7 2 9 6 -1. + <_> + 10 2 3 6 3. + <_> + + <_> + 13 0 4 14 -1. + <_> + 13 0 2 14 2. + <_> + + <_> + 7 0 4 14 -1. + <_> + 9 0 2 14 2. + <_> + + <_> + 14 15 9 6 -1. + <_> + 14 17 9 2 3. + <_> + + <_> + 2 8 18 5 -1. + <_> + 8 8 6 5 3. + <_> + + <_> + 18 3 6 11 -1. + <_> + 20 3 2 11 3. + <_> + + <_> + 6 5 11 14 -1. + <_> + 6 12 11 7 2. + <_> + + <_> + 18 4 6 9 -1. + <_> + 18 7 6 3 3. + <_> + + <_> + 7 6 9 6 -1. + <_> + 7 8 9 2 3. + <_> + + <_> + 18 4 6 9 -1. + <_> + 18 7 6 3 3. + <_> + + <_> + 0 4 6 9 -1. + <_> + 0 7 6 3 3. + <_> + + <_> + 9 4 9 4 -1. + <_> + 9 6 9 2 2. + <_> + + <_> + 0 22 19 2 -1. + <_> + 0 23 19 1 2. + <_> + + <_> + 17 14 6 9 -1. + <_> + 17 17 6 3 3. + <_> + + <_> + 1 14 6 9 -1. + <_> + 1 17 6 3 3. + <_> + + <_> + 14 11 4 9 -1. + <_> + 14 11 2 9 2. + <_> + + <_> + 6 11 4 9 -1. + <_> + 8 11 2 9 2. + <_> + + <_> + 3 9 18 7 -1. + <_> + 9 9 6 7 3. + <_> + + <_> + 9 12 6 10 -1. + <_> + 9 17 6 5 2. + <_> + + <_> + 12 0 6 9 -1. + <_> + 14 0 2 9 3. + <_> + + <_> + 6 0 6 9 -1. + <_> + 8 0 2 9 3. + <_> + + <_> + 6 17 18 3 -1. + <_> + 6 18 18 1 3. + <_> + + <_> + 1 17 18 3 -1. + <_> + 1 18 18 1 3. + <_> + + <_> + 10 6 11 12 -1. + <_> + 10 12 11 6 2. + <_> + + <_> + 5 6 14 6 -1. + <_> + 5 6 7 3 2. + <_> + 12 9 7 3 2. + <_> + + <_> + 5 4 15 4 -1. + <_> + 5 6 15 2 2. + <_> + + <_> + 0 0 22 2 -1. + <_> + 0 1 22 1 2. + <_> + + <_> + 0 0 24 24 -1. + <_> + 8 0 8 24 3. + <_> + + <_> + 1 15 18 4 -1. + <_> + 10 15 9 4 2. + <_> + + <_> + 6 8 12 9 -1. + <_> + 6 11 12 3 3. + <_> + + <_> + 4 12 7 12 -1. + <_> + 4 16 7 4 3. + <_> + + <_> + 1 2 22 6 -1. + <_> + 12 2 11 3 2. + <_> + 1 5 11 3 2. + <_> + + <_> + 5 20 14 3 -1. + <_> + 12 20 7 3 2. + <_> + + <_> + 0 0 24 16 -1. + <_> + 12 0 12 8 2. + <_> + 0 8 12 8 2. + <_> + + <_> + 3 13 18 4 -1. + <_> + 3 13 9 2 2. + <_> + 12 15 9 2 2. + <_> + + <_> + 2 10 22 2 -1. + <_> + 2 11 22 1 2. + <_> + + <_> + 6 3 11 8 -1. + <_> + 6 7 11 4 2. + <_> + + <_> + 14 5 6 6 -1. + <_> + 14 8 6 3 2. + <_> + + <_> + 0 7 24 6 -1. + <_> + 0 9 24 2 3. + <_> + + <_> + 14 0 10 10 -1. + <_> + 19 0 5 5 2. + <_> + 14 5 5 5 2. + <_> + + <_> + 0 0 10 10 -1. + <_> + 0 0 5 5 2. + <_> + 5 5 5 5 2. + <_> + + <_> + 0 1 24 4 -1. + <_> + 12 1 12 2 2. + <_> + 0 3 12 2 2. + <_> + + <_> + 0 17 18 3 -1. + <_> + 0 18 18 1 3. + <_> + + <_> + 5 15 16 6 -1. + <_> + 13 15 8 3 2. + <_> + 5 18 8 3 2. + <_> + + <_> + 3 15 16 6 -1. + <_> + 3 15 8 3 2. + <_> + 11 18 8 3 2. + <_> + + <_> + 6 16 18 3 -1. + <_> + 6 17 18 1 3. + <_> + + <_> + 0 13 21 10 -1. + <_> + 0 18 21 5 2. + <_> + + <_> + 13 0 6 24 -1. + <_> + 15 0 2 24 3. + <_> + + <_> + 7 4 6 11 -1. + <_> + 9 4 2 11 3. + <_> + + <_> + 9 5 9 6 -1. + <_> + 12 5 3 6 3. + <_> + + <_> + 1 4 2 20 -1. + <_> + 1 14 2 10 2. + <_> + + <_> + 13 0 6 24 -1. + <_> + 15 0 2 24 3. + <_> + + <_> + 5 0 6 24 -1. + <_> + 7 0 2 24 3. + <_> + + <_> + 16 7 6 14 -1. + <_> + 19 7 3 7 2. + <_> + 16 14 3 7 2. + <_> + + <_> + 4 7 4 12 -1. + <_> + 6 7 2 12 2. + <_> + + <_> + 0 5 24 14 -1. + <_> + 8 5 8 14 3. + <_> + + <_> + 5 13 10 6 -1. + <_> + 5 15 10 2 3. + <_> + + <_> + 12 0 6 9 -1. + <_> + 14 0 2 9 3. + <_> + + <_> + 2 7 6 14 -1. + <_> + 2 7 3 7 2. + <_> + 5 14 3 7 2. + <_> + + <_> + 15 2 9 15 -1. + <_> + 18 2 3 15 3. + <_> + + <_> + 0 2 6 9 -1. + <_> + 2 2 2 9 3. + <_> + + <_> + 12 2 10 14 -1. + <_> + 17 2 5 7 2. + <_> + 12 9 5 7 2. + <_> + + <_> + 11 6 2 18 -1. + <_> + 12 6 1 18 2. + <_> + + <_> + 9 5 15 6 -1. + <_> + 14 5 5 6 3. + <_> + + <_> + 8 6 6 10 -1. + <_> + 10 6 2 10 3. + <_> + + <_> + 12 0 6 9 -1. + <_> + 14 0 2 9 3. + <_> + + <_> + 3 3 9 7 -1. + <_> + 6 3 3 7 3. + <_> + + <_> + 6 7 14 3 -1. + <_> + 6 7 7 3 2. + <_> + + <_> + 7 7 8 6 -1. + <_> + 11 7 4 6 2. + <_> + + <_> + 12 7 7 12 -1. + <_> + 12 13 7 6 2. + <_> + + <_> + 10 6 4 18 -1. + <_> + 10 6 2 9 2. + <_> + 12 15 2 9 2. + <_> + + <_> + 16 14 6 9 -1. + <_> + 16 17 6 3 3. + <_> + + <_> + 4 0 6 13 -1. + <_> + 6 0 2 13 3. + <_> + + <_> + 2 2 21 3 -1. + <_> + 9 2 7 3 3. + <_> + + <_> + 5 4 5 12 -1. + <_> + 5 8 5 4 3. + <_> + + <_> + 10 3 4 10 -1. + <_> + 10 8 4 5 2. + <_> + + <_> + 8 4 5 8 -1. + <_> + 8 8 5 4 2. + <_> + + <_> + 6 0 11 9 -1. + <_> + 6 3 11 3 3. + <_> + + <_> + 6 6 12 5 -1. + <_> + 10 6 4 5 3. + <_> + + <_> + 0 0 24 5 -1. + <_> + 8 0 8 5 3. + <_> + + <_> + 1 10 23 6 -1. + <_> + 1 12 23 2 3. + <_> + + <_> + 3 21 18 3 -1. + <_> + 9 21 6 3 3. + <_> + + <_> + 3 6 21 6 -1. + <_> + 3 8 21 2 3. + <_> + + <_> + 0 5 6 12 -1. + <_> + 2 5 2 12 3. + <_> + + <_> + 10 2 4 15 -1. + <_> + 10 7 4 5 3. + <_> + + <_> + 8 7 8 10 -1. + <_> + 8 12 8 5 2. + <_> + + <_> + 5 7 15 12 -1. + <_> + 10 7 5 12 3. + <_> + + <_> + 0 17 10 6 -1. + <_> + 0 19 10 2 3. + <_> + + <_> + 14 18 9 6 -1. + <_> + 14 20 9 2 3. + <_> + + <_> + 9 6 6 16 -1. + <_> + 9 14 6 8 2. + <_> + + <_> + 14 18 9 6 -1. + <_> + 14 20 9 2 3. + <_> + + <_> + 1 18 9 6 -1. + <_> + 1 20 9 2 3. + <_> + + <_> + 15 9 9 6 -1. + <_> + 15 11 9 2 3. + <_> + + <_> + 0 9 9 6 -1. + <_> + 0 11 9 2 3. + <_> + + <_> + 17 3 6 9 -1. + <_> + 19 3 2 9 3. + <_> + + <_> + 2 17 18 3 -1. + <_> + 2 18 18 1 3. + <_> + + <_> + 3 15 21 6 -1. + <_> + 3 17 21 2 3. + <_> + + <_> + 9 17 6 6 -1. + <_> + 9 20 6 3 2. + <_> + + <_> + 18 3 6 9 -1. + <_> + 18 6 6 3 3. + <_> + + <_> + 0 3 6 9 -1. + <_> + 0 6 6 3 3. + <_> + + <_> + 4 0 16 10 -1. + <_> + 12 0 8 5 2. + <_> + 4 5 8 5 2. + <_> + + <_> + 2 0 10 16 -1. + <_> + 2 0 5 8 2. + <_> + 7 8 5 8 2. + <_> + + <_> + 14 0 10 5 -1. + <_> + 14 0 5 5 2. + <_> + + <_> + 0 0 10 5 -1. + <_> + 5 0 5 5 2. + <_> + + <_> + 18 3 6 10 -1. + <_> + 18 3 3 10 2. + <_> + + <_> + 5 11 12 6 -1. + <_> + 5 11 6 3 2. + <_> + 11 14 6 3 2. + <_> + + <_> + 21 0 3 18 -1. + <_> + 22 0 1 18 3. + <_> + + <_> + 6 0 6 9 -1. + <_> + 8 0 2 9 3. + <_> + + <_> + 8 8 9 7 -1. + <_> + 11 8 3 7 3. + <_> + + <_> + 7 12 8 10 -1. + <_> + 7 12 4 5 2. + <_> + 11 17 4 5 2. + <_> + + <_> + 21 0 3 18 -1. + <_> + 22 0 1 18 3. + <_> + + <_> + 10 6 4 9 -1. + <_> + 12 6 2 9 2. + <_> + + <_> + 15 0 9 6 -1. + <_> + 15 2 9 2 3. + <_> + + <_> + 0 2 24 3 -1. + <_> + 0 3 24 1 3. + <_> + + <_> + 11 7 6 9 -1. + <_> + 13 7 2 9 3. + <_> + + <_> + 7 6 6 10 -1. + <_> + 9 6 2 10 3. + <_> + + <_> + 12 1 6 12 -1. + <_> + 14 1 2 12 3. + <_> + + <_> + 6 4 12 12 -1. + <_> + 6 10 12 6 2. + <_> + + <_> + 14 3 2 21 -1. + <_> + 14 3 1 21 2. + <_> + + <_> + 6 1 12 8 -1. + <_> + 6 5 12 4 2. + <_> + + <_> + 3 0 18 8 -1. + <_> + 3 4 18 4 2. + <_> + + <_> + 3 0 18 3 -1. + <_> + 3 1 18 1 3. + <_> + + <_> + 0 13 24 4 -1. + <_> + 12 13 12 2 2. + <_> + 0 15 12 2 2. + <_> + + <_> + 10 5 4 9 -1. + <_> + 12 5 2 9 2. + <_> + + <_> + 11 1 6 9 -1. + <_> + 13 1 2 9 3. + <_> + + <_> + 6 2 6 22 -1. + <_> + 8 2 2 22 3. + <_> + + <_> + 16 10 8 14 -1. + <_> + 20 10 4 7 2. + <_> + 16 17 4 7 2. + <_> + + <_> + 3 4 16 15 -1. + <_> + 3 9 16 5 3. + <_> + + <_> + 16 10 8 14 -1. + <_> + 20 10 4 7 2. + <_> + 16 17 4 7 2. + <_> + + <_> + 0 10 8 14 -1. + <_> + 0 10 4 7 2. + <_> + 4 17 4 7 2. + <_> + + <_> + 10 14 11 6 -1. + <_> + 10 17 11 3 2. + <_> + + <_> + 0 7 24 9 -1. + <_> + 8 7 8 9 3. + <_> + + <_> + 13 1 4 16 -1. + <_> + 13 1 2 16 2. + <_> + + <_> + 7 1 4 16 -1. + <_> + 9 1 2 16 2. + <_> + + <_> + 5 5 16 8 -1. + <_> + 13 5 8 4 2. + <_> + 5 9 8 4 2. + <_> + + <_> + 0 9 6 9 -1. + <_> + 0 12 6 3 3. + <_> + + <_> + 6 16 18 3 -1. + <_> + 6 17 18 1 3. + <_> + + <_> + 3 12 6 9 -1. + <_> + 3 15 6 3 3. + <_> + + <_> + 8 14 9 6 -1. + <_> + 8 16 9 2 3. + <_> + + <_> + 2 13 8 10 -1. + <_> + 2 13 4 5 2. + <_> + 6 18 4 5 2. + <_> + + <_> + 15 5 3 18 -1. + <_> + 15 11 3 6 3. + <_> + + <_> + 3 5 18 3 -1. + <_> + 3 6 18 1 3. + <_> + + <_> + 17 5 6 11 -1. + <_> + 19 5 2 11 3. + <_> + + <_> + 1 5 6 11 -1. + <_> + 3 5 2 11 3. + <_> + + <_> + 19 1 4 9 -1. + <_> + 19 1 2 9 2. + <_> + + <_> + 1 1 4 9 -1. + <_> + 3 1 2 9 2. + <_> + + <_> + 4 15 18 9 -1. + <_> + 4 15 9 9 2. + <_> + + <_> + 6 9 12 4 -1. + <_> + 6 11 12 2 2. + <_> + + <_> + 15 2 9 6 -1. + <_> + 15 4 9 2 3. + <_> + + <_> + 0 2 9 6 -1. + <_> + 0 4 9 2 3. + <_> + + <_> + 15 0 6 17 -1. + <_> + 17 0 2 17 3. + <_> + + <_> + 3 0 6 17 -1. + <_> + 5 0 2 17 3. + <_> + + <_> + 8 17 9 4 -1. + <_> + 8 19 9 2 2. + <_> + + <_> + 6 5 3 18 -1. + <_> + 6 11 3 6 3. + <_> + + <_> + 5 2 14 12 -1. + <_> + 5 8 14 6 2. + <_> + + <_> + 10 2 3 12 -1. + <_> + 10 8 3 6 2. + <_> + + <_> + 10 7 14 15 -1. + <_> + 10 12 14 5 3. + <_> + + <_> + 0 7 14 15 -1. + <_> + 0 12 14 5 3. + <_> + + <_> + 15 0 9 6 -1. + <_> + 15 2 9 2 3. + <_> + + <_> + 0 0 9 6 -1. + <_> + 0 2 9 2 3. + <_> + + <_> + 12 6 6 14 -1. + <_> + 14 6 2 14 3. + <_> + + <_> + 9 7 6 9 -1. + <_> + 11 7 2 9 3. + <_> + + <_> + 12 6 6 15 -1. + <_> + 14 6 2 15 3. + <_> + + <_> + 6 6 6 15 -1. + <_> + 8 6 2 15 3. + <_> + + <_> + 15 3 8 9 -1. + <_> + 15 3 4 9 2. + <_> + + <_> + 0 0 9 21 -1. + <_> + 3 0 3 21 3. + <_> + + <_> + 11 9 8 12 -1. + <_> + 11 13 8 4 3. + <_> + + <_> + 6 7 10 12 -1. + <_> + 6 7 5 6 2. + <_> + 11 13 5 6 2. + <_> + + <_> + 10 6 4 18 -1. + <_> + 12 6 2 9 2. + <_> + 10 15 2 9 2. + <_> + + <_> + 0 0 6 9 -1. + <_> + 0 3 6 3 3. + <_> + + <_> + 3 14 18 3 -1. + <_> + 3 15 18 1 3. + <_> + + <_> + 3 14 8 10 -1. + <_> + 3 14 4 5 2. + <_> + 7 19 4 5 2. + <_> + + <_> + 0 12 24 4 -1. + <_> + 12 12 12 2 2. + <_> + 0 14 12 2 2. + <_> + + <_> + 0 2 3 20 -1. + <_> + 1 2 1 20 3. + <_> + + <_> + 12 16 10 8 -1. + <_> + 17 16 5 4 2. + <_> + 12 20 5 4 2. + <_> + + <_> + 2 16 10 8 -1. + <_> + 2 16 5 4 2. + <_> + 7 20 5 4 2. + <_> + + <_> + 7 0 10 9 -1. + <_> + 7 3 10 3 3. + <_> + + <_> + 0 0 24 3 -1. + <_> + 8 0 8 3 3. + <_> + + <_> + 3 8 15 4 -1. + <_> + 3 10 15 2 2. + <_> + + <_> + 6 5 12 6 -1. + <_> + 10 5 4 6 3. + <_> + + <_> + 5 13 14 6 -1. + <_> + 5 16 14 3 2. + <_> + + <_> + 11 14 4 10 -1. + <_> + 11 19 4 5 2. + <_> + + <_> + 0 6 6 7 -1. + <_> + 3 6 3 7 2. + <_> + + <_> + 18 0 6 6 -1. + <_> + 18 0 3 6 2. + <_> + + <_> + 3 1 18 3 -1. + <_> + 3 2 18 1 3. + <_> + + <_> + 9 6 14 18 -1. + <_> + 9 12 14 6 3. + <_> + + <_> + 0 0 6 6 -1. + <_> + 3 0 3 6 2. + <_> + + <_> + 13 11 6 6 -1. + <_> + 13 11 3 6 2. + <_> + + <_> + 0 20 24 3 -1. + <_> + 8 20 8 3 3. + <_> + + <_> + 13 11 6 7 -1. + <_> + 13 11 3 7 2. + <_> + + <_> + 4 12 10 6 -1. + <_> + 4 14 10 2 3. + <_> + + <_> + 13 11 6 6 -1. + <_> + 13 11 3 6 2. + <_> + + <_> + 5 11 6 7 -1. + <_> + 8 11 3 7 2. + <_> + + <_> + 7 4 11 12 -1. + <_> + 7 8 11 4 3. + <_> + + <_> + 6 15 10 4 -1. + <_> + 6 17 10 2 2. + <_> + + <_> + 14 0 6 9 -1. + <_> + 16 0 2 9 3. + <_> + + <_> + 4 0 6 9 -1. + <_> + 6 0 2 9 3. + <_> + + <_> + 11 2 4 15 -1. + <_> + 11 7 4 5 3. + <_> + + <_> + 0 0 20 3 -1. + <_> + 0 1 20 1 3. + <_> + + <_> + 13 18 10 6 -1. + <_> + 13 20 10 2 3. + <_> + + <_> + 2 7 6 11 -1. + <_> + 5 7 3 11 2. + <_> + + <_> + 10 14 10 9 -1. + <_> + 10 17 10 3 3. + <_> + + <_> + 8 2 4 9 -1. + <_> + 10 2 2 9 2. + <_> + + <_> + 14 3 10 4 -1. + <_> + 14 3 5 4 2. + <_> + + <_> + 6 6 12 6 -1. + <_> + 6 6 6 3 2. + <_> + 12 9 6 3 2. + <_> + + <_> + 8 8 8 10 -1. + <_> + 12 8 4 5 2. + <_> + 8 13 4 5 2. + <_> + + <_> + 7 4 4 16 -1. + <_> + 7 12 4 8 2. + <_> + + <_> + 8 8 9 4 -1. + <_> + 8 10 9 2 2. + <_> + + <_> + 5 2 14 9 -1. + <_> + 5 5 14 3 3. + <_> + + <_> + 3 16 19 8 -1. + <_> + 3 20 19 4 2. + <_> + + <_> + 0 0 10 8 -1. + <_> + 5 0 5 8 2. + <_> + + <_> + 5 2 16 18 -1. + <_> + 5 2 8 18 2. + <_> + + <_> + 0 11 24 11 -1. + <_> + 8 11 8 11 3. + <_> + + <_> + 3 3 18 5 -1. + <_> + 3 3 9 5 2. + <_> + + <_> + 1 16 18 3 -1. + <_> + 1 17 18 1 3. + <_> + + <_> + 5 17 18 3 -1. + <_> + 5 18 18 1 3. + <_> + + <_> + 1 13 9 6 -1. + <_> + 1 15 9 2 3. + <_> + + <_> + 1 9 23 10 -1. + <_> + 1 14 23 5 2. + <_> + + <_> + 3 7 18 3 -1. + <_> + 3 8 18 1 3. + <_> + + <_> + 6 8 12 3 -1. + <_> + 6 8 6 3 2. + <_> + + <_> + 6 2 3 22 -1. + <_> + 7 2 1 22 3. + <_> + + <_> + 14 17 10 6 -1. + <_> + 14 19 10 2 3. + <_> + + <_> + 1 18 10 6 -1. + <_> + 1 20 10 2 3. + <_> + + <_> + 11 3 6 12 -1. + <_> + 13 3 2 12 3. + <_> + + <_> + 10 6 4 9 -1. + <_> + 12 6 2 9 2. + <_> + + <_> + 11 0 6 9 -1. + <_> + 13 0 2 9 3. + <_> + + <_> + 7 0 6 9 -1. + <_> + 9 0 2 9 3. + <_> + + <_> + 12 10 9 6 -1. + <_> + 15 10 3 6 3. + <_> + + <_> + 2 11 6 9 -1. + <_> + 5 11 3 9 2. + <_> + + <_> + 14 5 3 19 -1. + <_> + 15 5 1 19 3. + <_> + + <_> + 6 6 9 6 -1. + <_> + 6 8 9 2 3. + <_> + + <_> + 14 5 3 19 -1. + <_> + 15 5 1 19 3. + <_> + + <_> + 0 3 6 9 -1. + <_> + 0 6 6 3 3. + <_> + + <_> + 5 21 18 3 -1. + <_> + 5 22 18 1 3. + <_> + + <_> + 1 10 18 4 -1. + <_> + 7 10 6 4 3. + <_> + + <_> + 13 4 8 10 -1. + <_> + 17 4 4 5 2. + <_> + 13 9 4 5 2. + <_> + + <_> + 7 8 9 6 -1. + <_> + 10 8 3 6 3. + <_> + + <_> + 12 9 9 8 -1. + <_> + 15 9 3 8 3. + <_> + + <_> + 0 6 5 12 -1. + <_> + 0 10 5 4 3. + <_> + + <_> + 7 6 14 6 -1. + <_> + 14 6 7 3 2. + <_> + 7 9 7 3 2. + <_> + + <_> + 7 5 3 19 -1. + <_> + 8 5 1 19 3. + <_> + + <_> + 8 4 15 20 -1. + <_> + 13 4 5 20 3. + <_> + + <_> + 1 4 15 20 -1. + <_> + 6 4 5 20 3. + <_> + + <_> + 13 10 6 6 -1. + <_> + 13 10 3 6 2. + <_> + + <_> + 5 10 6 6 -1. + <_> + 8 10 3 6 2. + <_> + + <_> + 14 2 6 14 -1. + <_> + 17 2 3 7 2. + <_> + 14 9 3 7 2. + <_> + + <_> + 4 2 6 14 -1. + <_> + 4 2 3 7 2. + <_> + 7 9 3 7 2. + <_> + + <_> + 12 4 6 7 -1. + <_> + 12 4 3 7 2. + <_> + + <_> + 9 4 6 9 -1. + <_> + 11 4 2 9 3. + <_> + + <_> + 11 4 8 10 -1. + <_> + 11 4 4 10 2. + <_> + + <_> + 5 4 8 10 -1. + <_> + 9 4 4 10 2. + <_> + + <_> + 8 18 10 6 -1. + <_> + 8 20 10 2 3. + <_> + + <_> + 1 18 21 6 -1. + <_> + 1 20 21 2 3. + <_> + + <_> + 9 2 12 6 -1. + <_> + 9 2 6 6 2. + <_> + + <_> + 3 2 12 6 -1. + <_> + 9 2 6 6 2. + <_> + + <_> + 12 5 12 6 -1. + <_> + 18 5 6 3 2. + <_> + 12 8 6 3 2. + <_> + + <_> + 8 8 6 9 -1. + <_> + 8 11 6 3 3. + <_> + + <_> + 2 7 20 6 -1. + <_> + 2 9 20 2 3. + <_> + + <_> + 0 5 12 6 -1. + <_> + 0 5 6 3 2. + <_> + 6 8 6 3 2. + <_> + + <_> + 14 14 8 10 -1. + <_> + 18 14 4 5 2. + <_> + 14 19 4 5 2. + <_> + + <_> + 2 14 8 10 -1. + <_> + 2 14 4 5 2. + <_> + 6 19 4 5 2. + <_> + + <_> + 2 11 20 13 -1. + <_> + 2 11 10 13 2. + <_> + + <_> + 6 9 12 5 -1. + <_> + 12 9 6 5 2. + <_> + + <_> + 5 6 16 6 -1. + <_> + 13 6 8 3 2. + <_> + 5 9 8 3 2. + <_> + + <_> + 1 19 9 4 -1. + <_> + 1 21 9 2 2. + <_> + + <_> + 7 5 12 5 -1. + <_> + 11 5 4 5 3. + <_> + + <_> + 3 5 14 12 -1. + <_> + 3 5 7 6 2. + <_> + 10 11 7 6 2. + <_> + + <_> + 9 4 9 6 -1. + <_> + 12 4 3 6 3. + <_> + + <_> + 2 6 19 3 -1. + <_> + 2 7 19 1 3. + <_> + + <_> + 18 10 6 9 -1. + <_> + 18 13 6 3 3. + <_> + + <_> + 3 7 18 2 -1. + <_> + 3 8 18 1 2. + <_> + + <_> + 20 2 4 18 -1. + <_> + 22 2 2 9 2. + <_> + 20 11 2 9 2. + <_> + + <_> + 2 18 20 3 -1. + <_> + 2 19 20 1 3. + <_> + + <_> + 1 9 22 3 -1. + <_> + 1 10 22 1 3. + <_> + + <_> + 0 2 4 18 -1. + <_> + 0 2 2 9 2. + <_> + 2 11 2 9 2. + <_> + + <_> + 19 0 4 23 -1. + <_> + 19 0 2 23 2. + <_> + + <_> + 0 3 6 19 -1. + <_> + 3 3 3 19 2. + <_> + + <_> + 18 2 6 9 -1. + <_> + 20 2 2 9 3. + <_> + + <_> + 0 5 10 6 -1. + <_> + 0 7 10 2 3. + <_> + + <_> + 7 0 12 12 -1. + <_> + 13 0 6 6 2. + <_> + 7 6 6 6 2. + <_> + + <_> + 0 3 24 6 -1. + <_> + 0 3 12 3 2. + <_> + 12 6 12 3 2. + <_> + + <_> + 10 14 4 10 -1. + <_> + 10 19 4 5 2. + <_> + + <_> + 8 9 4 15 -1. + <_> + 8 14 4 5 3. + <_> + + <_> + 4 11 17 6 -1. + <_> + 4 14 17 3 2. + <_> + + <_> + 2 5 18 8 -1. + <_> + 2 5 9 4 2. + <_> + 11 9 9 4 2. + <_> + + <_> + 7 6 14 6 -1. + <_> + 14 6 7 3 2. + <_> + 7 9 7 3 2. + <_> + + <_> + 3 6 14 6 -1. + <_> + 3 6 7 3 2. + <_> + 10 9 7 3 2. + <_> + + <_> + 16 5 3 18 -1. + <_> + 17 5 1 18 3. + <_> + + <_> + 5 5 3 18 -1. + <_> + 6 5 1 18 3. + <_> + + <_> + 10 10 14 4 -1. + <_> + 10 12 14 2 2. + <_> + + <_> + 4 10 9 4 -1. + <_> + 4 12 9 2 2. + <_> + + <_> + 2 0 18 9 -1. + <_> + 2 3 18 3 3. + <_> + + <_> + 6 3 12 8 -1. + <_> + 10 3 4 8 3. + <_> + + <_> + 1 1 8 5 -1. + <_> + 5 1 4 5 2. + <_> + + <_> + 12 7 7 8 -1. + <_> + 12 11 7 4 2. + <_> + + <_> + 0 12 22 4 -1. + <_> + 0 14 22 2 2. + <_> + + <_> + 15 6 4 15 -1. + <_> + 15 11 4 5 3. + <_> + + <_> + 5 7 7 8 -1. + <_> + 5 11 7 4 2. + <_> + + <_> + 8 18 9 4 -1. + <_> + 8 20 9 2 2. + <_> + + <_> + 1 2 22 4 -1. + <_> + 1 4 22 2 2. + <_> + + <_> + 17 3 6 17 -1. + <_> + 19 3 2 17 3. + <_> + + <_> + 8 2 8 18 -1. + <_> + 8 11 8 9 2. + <_> + + <_> + 17 0 6 12 -1. + <_> + 20 0 3 6 2. + <_> + 17 6 3 6 2. + <_> + + <_> + 7 0 6 9 -1. + <_> + 9 0 2 9 3. + <_> + + <_> + 15 5 9 12 -1. + <_> + 15 11 9 6 2. + <_> + + <_> + 2 22 18 2 -1. + <_> + 2 23 18 1 2. + <_> + + <_> + 10 10 12 6 -1. + <_> + 16 10 6 3 2. + <_> + 10 13 6 3 2. + <_> + + <_> + 0 1 4 11 -1. + <_> + 2 1 2 11 2. + <_> + + <_> + 20 0 4 10 -1. + <_> + 20 0 2 10 2. + <_> + + <_> + 1 3 6 17 -1. + <_> + 3 3 2 17 3. + <_> + + <_> + 15 15 9 6 -1. + <_> + 15 17 9 2 3. + <_> + + <_> + 0 13 8 9 -1. + <_> + 0 16 8 3 3. + <_> + + <_> + 16 8 6 12 -1. + <_> + 16 12 6 4 3. + <_> + + <_> + 2 8 6 12 -1. + <_> + 2 12 6 4 3. + <_> + + <_> + 10 2 4 15 -1. + <_> + 10 7 4 5 3. + <_> + + <_> + 1 5 19 3 -1. + <_> + 1 6 19 1 3. + <_> + + <_> + 11 8 9 7 -1. + <_> + 14 8 3 7 3. + <_> + + <_> + 3 8 12 9 -1. + <_> + 3 11 12 3 3. + <_> + + <_> + 3 6 18 3 -1. + <_> + 3 7 18 1 3. + <_> + + <_> + 10 0 4 12 -1. + <_> + 10 6 4 6 2. + <_> + + <_> + 3 9 18 14 -1. + <_> + 3 9 9 14 2. + <_> + + <_> + 0 0 4 9 -1. + <_> + 2 0 2 9 2. + <_> + + <_> + 12 5 4 18 -1. + <_> + 12 5 2 18 2. + <_> + + <_> + 8 5 4 18 -1. + <_> + 10 5 2 18 2. + <_> + + <_> + 10 5 6 10 -1. + <_> + 12 5 2 10 3. + <_> + + <_> + 9 4 4 11 -1. + <_> + 11 4 2 11 2. + <_> + + <_> + 4 16 18 3 -1. + <_> + 4 17 18 1 3. + <_> + + <_> + 0 16 20 3 -1. + <_> + 0 17 20 1 3. + <_> + + <_> + 9 9 6 12 -1. + <_> + 9 13 6 4 3. + <_> + + <_> + 8 13 8 8 -1. + <_> + 8 17 8 4 2. + <_> + + <_> + 13 10 3 12 -1. + <_> + 13 16 3 6 2. + <_> + + <_> + 5 9 14 14 -1. + <_> + 5 9 7 7 2. + <_> + 12 16 7 7 2. + <_> + + <_> + 0 0 24 10 -1. + <_> + 12 0 12 5 2. + <_> + 0 5 12 5 2. + <_> + + <_> + 1 11 18 2 -1. + <_> + 1 12 18 1 2. + <_> + + <_> + 19 5 5 12 -1. + <_> + 19 9 5 4 3. + <_> + + <_> + 0 5 5 12 -1. + <_> + 0 9 5 4 3. + <_> + + <_> + 16 6 8 18 -1. + <_> + 20 6 4 9 2. + <_> + 16 15 4 9 2. + <_> + + <_> + 0 6 8 18 -1. + <_> + 0 6 4 9 2. + <_> + 4 15 4 9 2. + <_> + + <_> + 12 5 12 12 -1. + <_> + 18 5 6 6 2. + <_> + 12 11 6 6 2. + <_> + + <_> + 7 6 6 9 -1. + <_> + 9 6 2 9 3. + <_> + + <_> + 9 13 6 11 -1. + <_> + 11 13 2 11 3. + <_> + + <_> + 0 5 12 12 -1. + <_> + 0 5 6 6 2. + <_> + 6 11 6 6 2. + <_> + + <_> + 1 2 23 3 -1. + <_> + 1 3 23 1 3. + <_> + + <_> + 1 15 19 3 -1. + <_> + 1 16 19 1 3. + <_> + + <_> + 13 17 11 4 -1. + <_> + 13 19 11 2 2. + <_> + + <_> + 0 13 8 5 -1. + <_> + 4 13 4 5 2. + <_> + + <_> + 12 10 10 4 -1. + <_> + 12 10 5 4 2. + <_> + + <_> + 4 6 9 9 -1. + <_> + 4 9 9 3 3. + <_> + + <_> + 15 14 9 6 -1. + <_> + 15 16 9 2 3. + <_> + + <_> + 1 12 9 6 -1. + <_> + 1 14 9 2 3. + <_> + + <_> + 3 10 20 8 -1. + <_> + 13 10 10 4 2. + <_> + 3 14 10 4 2. + <_> + + <_> + 2 0 9 18 -1. + <_> + 5 0 3 18 3. + <_> + + <_> + 13 11 9 10 -1. + <_> + 16 11 3 10 3. + <_> + + <_> + 1 2 8 5 -1. + <_> + 5 2 4 5 2. + <_> + + <_> + 3 4 21 6 -1. + <_> + 10 4 7 6 3. + <_> + + <_> + 7 0 10 14 -1. + <_> + 7 0 5 7 2. + <_> + 12 7 5 7 2. + <_> + + <_> + 12 17 12 4 -1. + <_> + 12 19 12 2 2. + <_> + + <_> + 0 6 23 4 -1. + <_> + 0 8 23 2 2. + <_> + + <_> + 13 10 8 10 -1. + <_> + 17 10 4 5 2. + <_> + 13 15 4 5 2. + <_> + + <_> + 0 16 18 3 -1. + <_> + 0 17 18 1 3. + <_> + + <_> + 15 16 9 4 -1. + <_> + 15 18 9 2 2. + <_> + + <_> + 0 16 9 4 -1. + <_> + 0 18 9 2 2. + <_> + + <_> + 13 11 6 6 -1. + <_> + 13 11 3 6 2. + <_> + + <_> + 5 11 6 6 -1. + <_> + 8 11 3 6 2. + <_> + + <_> + 0 3 24 6 -1. + <_> + 12 3 12 3 2. + <_> + 0 6 12 3 2. + <_> + + <_> + 2 4 18 3 -1. + <_> + 2 5 18 1 3. + <_> + + <_> + 0 0 24 4 -1. + <_> + 12 0 12 2 2. + <_> + 0 2 12 2 2. + <_> + + <_> + 1 16 18 3 -1. + <_> + 1 17 18 1 3. + <_> + + <_> + 15 15 9 6 -1. + <_> + 15 17 9 2 3. + <_> + + <_> + 0 15 9 6 -1. + <_> + 0 17 9 2 3. + <_> + + <_> + 6 17 18 3 -1. + <_> + 6 18 18 1 3. + <_> + + <_> + 8 8 6 10 -1. + <_> + 10 8 2 10 3. + <_> + + <_> + 10 6 6 9 -1. + <_> + 12 6 2 9 3. + <_> + + <_> + 8 8 5 8 -1. + <_> + 8 12 5 4 2. + <_> + + <_> + 12 8 6 8 -1. + <_> + 12 12 6 4 2. + <_> + + <_> + 6 5 6 11 -1. + <_> + 8 5 2 11 3. + <_> + + <_> + 13 6 8 9 -1. + <_> + 13 9 8 3 3. + <_> + + <_> + 1 7 21 6 -1. + <_> + 1 9 21 2 3. + <_> + + <_> + 15 5 3 12 -1. + <_> + 15 11 3 6 2. + <_> + + <_> + 6 9 11 12 -1. + <_> + 6 13 11 4 3. + <_> + + <_> + 13 8 10 8 -1. + <_> + 18 8 5 4 2. + <_> + 13 12 5 4 2. + <_> + + <_> + 5 8 12 3 -1. + <_> + 11 8 6 3 2. + <_> + + <_> + 6 11 18 4 -1. + <_> + 12 11 6 4 3. + <_> + + <_> + 0 0 22 22 -1. + <_> + 0 11 22 11 2. + <_> + + <_> + 11 2 6 8 -1. + <_> + 11 6 6 4 2. + <_> + + <_> + 9 0 6 9 -1. + <_> + 11 0 2 9 3. + <_> + + <_> + 10 0 6 9 -1. + <_> + 12 0 2 9 3. + <_> + + <_> + 8 3 6 14 -1. + <_> + 8 3 3 7 2. + <_> + 11 10 3 7 2. + <_> + + <_> + 3 10 18 8 -1. + <_> + 9 10 6 8 3. + <_> + + <_> + 10 0 3 14 -1. + <_> + 10 7 3 7 2. + <_> + + <_> + 4 3 16 20 -1. + <_> + 4 13 16 10 2. + <_> + + <_> + 9 4 6 10 -1. + <_> + 11 4 2 10 3. + <_> + + <_> + 5 0 16 4 -1. + <_> + 5 2 16 2 2. + <_> + + <_> + 2 5 18 4 -1. + <_> + 8 5 6 4 3. + <_> + + <_> + 13 0 6 9 -1. + <_> + 15 0 2 9 3. + <_> + + <_> + 8 4 8 5 -1. + <_> + 12 4 4 5 2. + <_> + + <_> + 12 10 10 4 -1. + <_> + 12 10 5 4 2. + <_> + + <_> + 2 10 10 4 -1. + <_> + 7 10 5 4 2. + <_> + + <_> + 7 11 12 5 -1. + <_> + 11 11 4 5 3. + <_> + + <_> + 3 10 8 10 -1. + <_> + 3 10 4 5 2. + <_> + 7 15 4 5 2. + <_> + + <_> + 11 12 9 8 -1. + <_> + 14 12 3 8 3. + <_> + + <_> + 0 21 24 3 -1. + <_> + 8 21 8 3 3. + <_> + + <_> + 3 20 18 4 -1. + <_> + 9 20 6 4 3. + <_> + + <_> + 1 15 9 6 -1. + <_> + 1 17 9 2 3. + <_> + + <_> + 11 17 10 4 -1. + <_> + 11 19 10 2 2. + <_> + + <_> + 9 12 4 12 -1. + <_> + 9 18 4 6 2. + <_> + + <_> + 9 6 9 6 -1. + <_> + 12 6 3 6 3. + <_> + + <_> + 1 13 6 9 -1. + <_> + 1 16 6 3 3. + <_> + + <_> + 6 16 12 4 -1. + <_> + 6 18 12 2 2. + <_> + + <_> + 1 5 20 3 -1. + <_> + 1 6 20 1 3. + <_> + + <_> + 8 1 9 9 -1. + <_> + 8 4 9 3 3. + <_> + + <_> + 2 19 9 4 -1. + <_> + 2 21 9 2 2. + <_> + + <_> + 11 1 4 18 -1. + <_> + 11 7 4 6 3. + <_> + + <_> + 7 2 8 12 -1. + <_> + 7 2 4 6 2. + <_> + 11 8 4 6 2. + <_> + + <_> + 11 10 9 8 -1. + <_> + 14 10 3 8 3. + <_> + + <_> + 5 11 12 5 -1. + <_> + 9 11 4 5 3. + <_> + + <_> + 11 9 9 6 -1. + <_> + 14 9 3 6 3. + <_> + + <_> + 5 10 6 9 -1. + <_> + 7 10 2 9 3. + <_> + + <_> + 4 7 5 12 -1. + <_> + 4 11 5 4 3. + <_> + + <_> + 2 0 21 6 -1. + <_> + 9 0 7 6 3. + <_> + + <_> + 7 6 10 6 -1. + <_> + 7 8 10 2 3. + <_> + + <_> + 9 0 6 15 -1. + <_> + 11 0 2 15 3. + <_> + + <_> + 2 2 18 2 -1. + <_> + 2 3 18 1 2. + <_> + + <_> + 8 17 8 6 -1. + <_> + 8 20 8 3 2. + <_> + + <_> + 3 0 18 2 -1. + <_> + 3 1 18 1 2. + <_> + + <_> + 8 0 9 6 -1. + <_> + 11 0 3 6 3. + <_> + + <_> + 0 17 18 3 -1. + <_> + 0 18 18 1 3. + <_> + + <_> + 6 7 12 5 -1. + <_> + 10 7 4 5 3. + <_> + + <_> + 0 3 6 9 -1. + <_> + 2 3 2 9 3. + <_> + + <_> + 20 2 4 9 -1. + <_> + 20 2 2 9 2. + <_> + + <_> + 0 2 4 9 -1. + <_> + 2 2 2 9 2. + <_> + + <_> + 0 1 24 4 -1. + <_> + 12 1 12 2 2. + <_> + 0 3 12 2 2. + <_> + + <_> + 0 16 9 6 -1. + <_> + 0 18 9 2 3. + <_> + + <_> + 14 13 9 6 -1. + <_> + 14 15 9 2 3. + <_> + + <_> + 0 15 19 3 -1. + <_> + 0 16 19 1 3. + <_> + + <_> + 1 5 22 12 -1. + <_> + 12 5 11 6 2. + <_> + 1 11 11 6 2. + <_> + + <_> + 5 13 6 6 -1. + <_> + 8 13 3 6 2. + <_> + + <_> + 4 2 20 3 -1. + <_> + 4 3 20 1 3. + <_> + + <_> + 8 14 6 10 -1. + <_> + 10 14 2 10 3. + <_> + + <_> + 6 12 16 6 -1. + <_> + 14 12 8 3 2. + <_> + 6 15 8 3 2. + <_> + + <_> + 2 13 8 9 -1. + <_> + 2 16 8 3 3. + <_> + + <_> + 11 8 6 14 -1. + <_> + 14 8 3 7 2. + <_> + 11 15 3 7 2. + <_> + + <_> + 2 12 16 6 -1. + <_> + 2 12 8 3 2. + <_> + 10 15 8 3 2. + <_> + + <_> + 5 16 16 8 -1. + <_> + 5 20 16 4 2. + <_> + + <_> + 9 1 4 12 -1. + <_> + 9 7 4 6 2. + <_> + + <_> + 8 2 8 10 -1. + <_> + 12 2 4 5 2. + <_> + 8 7 4 5 2. + <_> + + <_> + 6 6 12 6 -1. + <_> + 6 6 6 3 2. + <_> + 12 9 6 3 2. + <_> + + <_> + 10 7 6 9 -1. + <_> + 12 7 2 9 3. + <_> + + <_> + 0 0 8 12 -1. + <_> + 0 0 4 6 2. + <_> + 4 6 4 6 2. + <_> + + <_> + 18 8 6 9 -1. + <_> + 18 11 6 3 3. + <_> + + <_> + 2 12 6 6 -1. + <_> + 5 12 3 6 2. + <_> + + <_> + 3 21 21 3 -1. + <_> + 10 21 7 3 3. + <_> + + <_> + 2 0 16 6 -1. + <_> + 2 3 16 3 2. + <_> + + <_> + 13 6 7 6 -1. + <_> + 13 9 7 3 2. + <_> + + <_> + 6 4 4 14 -1. + <_> + 6 11 4 7 2. + <_> + + <_> + 9 7 6 9 -1. + <_> + 11 7 2 9 3. + <_> + + <_> + 7 8 6 14 -1. + <_> + 7 8 3 7 2. + <_> + 10 15 3 7 2. + <_> + + <_> + 18 8 4 16 -1. + <_> + 18 16 4 8 2. + <_> + + <_> + 9 14 6 10 -1. + <_> + 11 14 2 10 3. + <_> + + <_> + 6 11 12 5 -1. + <_> + 10 11 4 5 3. + <_> + + <_> + 0 12 23 3 -1. + <_> + 0 13 23 1 3. + <_> + + <_> + 13 0 6 12 -1. + <_> + 15 0 2 12 3. + <_> + + <_> + 0 10 12 5 -1. + <_> + 4 10 4 5 3. + <_> + + <_> + 13 2 10 4 -1. + <_> + 13 4 10 2 2. + <_> + + <_> + 5 0 6 12 -1. + <_> + 7 0 2 12 3. + <_> + + <_> + 11 6 9 6 -1. + <_> + 14 6 3 6 3. + <_> + + <_> + 4 6 9 6 -1. + <_> + 7 6 3 6 3. + <_> + + <_> + 6 11 18 13 -1. + <_> + 12 11 6 13 3. + <_> + + <_> + 0 11 18 13 -1. + <_> + 6 11 6 13 3. + <_> + + <_> + 12 16 12 6 -1. + <_> + 16 16 4 6 3. + <_> + + <_> + 0 6 21 3 -1. + <_> + 0 7 21 1 3. + <_> + + <_> + 12 16 12 6 -1. + <_> + 16 16 4 6 3. + <_> + + <_> + 5 7 6 14 -1. + <_> + 5 14 6 7 2. + <_> + + <_> + 5 10 19 2 -1. + <_> + 5 11 19 1 2. + <_> + + <_> + 5 4 14 4 -1. + <_> + 5 6 14 2 2. + <_> + + <_> + 3 18 18 4 -1. + <_> + 9 18 6 4 3. + <_> + + <_> + 7 0 4 9 -1. + <_> + 9 0 2 9 2. + <_> + + <_> + 13 3 11 4 -1. + <_> + 13 5 11 2 2. + <_> + + <_> + 2 0 9 6 -1. + <_> + 5 0 3 6 3. + <_> + + <_> + 19 1 4 23 -1. + <_> + 19 1 2 23 2. + <_> + + <_> + 1 1 4 23 -1. + <_> + 3 1 2 23 2. + <_> + + <_> + 5 16 18 3 -1. + <_> + 5 17 18 1 3. + <_> + + <_> + 0 3 11 4 -1. + <_> + 0 5 11 2 2. + <_> + + <_> + 2 16 20 3 -1. + <_> + 2 17 20 1 3. + <_> + + <_> + 5 3 13 4 -1. + <_> + 5 5 13 2 2. + <_> + + <_> + 1 9 22 15 -1. + <_> + 1 9 11 15 2. + <_> + + <_> + 3 4 14 3 -1. + <_> + 10 4 7 3 2. + <_> + + <_> + 8 7 10 4 -1. + <_> + 8 7 5 4 2. + <_> + + <_> + 6 7 10 4 -1. + <_> + 11 7 5 4 2. + <_> + + <_> + 10 4 6 9 -1. + <_> + 12 4 2 9 3. + <_> + + <_> + 1 12 9 6 -1. + <_> + 4 12 3 6 3. + <_> + + <_> + 8 3 8 10 -1. + <_> + 12 3 4 5 2. + <_> + 8 8 4 5 2. + <_> + + <_> + 3 6 16 6 -1. + <_> + 3 6 8 3 2. + <_> + 11 9 8 3 2. + <_> + + <_> + 5 6 14 6 -1. + <_> + 5 9 14 3 2. + <_> + + <_> + 4 3 9 6 -1. + <_> + 4 5 9 2 3. + <_> + + <_> + 6 3 18 2 -1. + <_> + 6 4 18 1 2. + <_> + + <_> + 7 6 9 6 -1. + <_> + 10 6 3 6 3. + <_> + + <_> + 0 1 24 3 -1. + <_> + 0 2 24 1 3. + <_> + + <_> + 0 17 10 6 -1. + <_> + 0 19 10 2 3. + <_> + + <_> + 3 18 18 3 -1. + <_> + 3 19 18 1 3. + <_> + + <_> + 2 5 6 16 -1. + <_> + 2 5 3 8 2. + <_> + 5 13 3 8 2. + <_> + + <_> + 7 6 11 6 -1. + <_> + 7 8 11 2 3. + <_> + + <_> + 5 2 12 22 -1. + <_> + 5 13 12 11 2. + <_> + + <_> + 10 7 4 10 -1. + <_> + 10 12 4 5 2. + <_> + + <_> + 9 0 4 18 -1. + <_> + 9 6 4 6 3. + <_> + + <_> + 18 8 6 9 -1. + <_> + 18 11 6 3 3. + <_> + + <_> + 4 7 15 10 -1. + <_> + 9 7 5 10 3. + <_> + + <_> + 10 5 6 9 -1. + <_> + 12 5 2 9 3. + <_> + + <_> + 9 9 6 10 -1. + <_> + 11 9 2 10 3. + <_> + + <_> + 11 14 6 10 -1. + <_> + 13 14 2 10 3. + <_> + + <_> + 7 14 6 10 -1. + <_> + 9 14 2 10 3. + <_> + + <_> + 4 8 16 9 -1. + <_> + 4 11 16 3 3. + <_> + + <_> + 2 11 20 3 -1. + <_> + 2 12 20 1 3. + <_> + + <_> + 13 0 4 13 -1. + <_> + 13 0 2 13 2. + <_> + + <_> + 7 0 4 13 -1. + <_> + 9 0 2 13 2. + <_> + + <_> + 3 1 18 7 -1. + <_> + 9 1 6 7 3. + <_> + + <_> + 1 11 6 9 -1. + <_> + 1 14 6 3 3. + <_> + + <_> + 8 18 9 6 -1. + <_> + 8 20 9 2 3. + <_> + + <_> + 3 9 15 6 -1. + <_> + 3 11 15 2 3. + <_> + + <_> + 5 10 19 2 -1. + <_> + 5 11 19 1 2. + <_> + + <_> + 8 6 7 16 -1. + <_> + 8 14 7 8 2. + <_> + + <_> + 9 14 9 6 -1. + <_> + 9 16 9 2 3. + <_> + + <_> + 0 7 8 12 -1. + <_> + 0 11 8 4 3. + <_> + + <_> + 6 4 18 3 -1. + <_> + 6 5 18 1 3. + <_> + + <_> + 0 16 12 6 -1. + <_> + 4 16 4 6 3. + <_> + + <_> + 13 13 9 4 -1. + <_> + 13 15 9 2 2. + <_> + + <_> + 5 8 14 14 -1. + <_> + 5 8 7 7 2. + <_> + 12 15 7 7 2. + <_> + + <_> + 1 16 22 6 -1. + <_> + 12 16 11 3 2. + <_> + 1 19 11 3 2. + <_> + + <_> + 9 0 6 9 -1. + <_> + 11 0 2 9 3. + <_> + + <_> + 9 5 10 10 -1. + <_> + 14 5 5 5 2. + <_> + 9 10 5 5 2. + <_> + + <_> + 5 5 10 10 -1. + <_> + 5 5 5 5 2. + <_> + 10 10 5 5 2. + <_> + + <_> + 4 6 16 6 -1. + <_> + 12 6 8 3 2. + <_> + 4 9 8 3 2. + <_> + + <_> + 0 7 6 9 -1. + <_> + 0 10 6 3 3. + <_> + + <_> + 16 10 8 14 -1. + <_> + 20 10 4 7 2. + <_> + 16 17 4 7 2. + <_> + + <_> + 9 12 6 12 -1. + <_> + 9 18 6 6 2. + <_> + + <_> + 8 10 8 12 -1. + <_> + 12 10 4 6 2. + <_> + 8 16 4 6 2. + <_> + + <_> + 8 0 4 9 -1. + <_> + 10 0 2 9 2. + <_> + + <_> + 10 4 8 16 -1. + <_> + 14 4 4 8 2. + <_> + 10 12 4 8 2. + <_> + + <_> + 7 10 10 6 -1. + <_> + 7 12 10 2 3. + <_> + + <_> + 5 6 14 14 -1. + <_> + 12 6 7 7 2. + <_> + 5 13 7 7 2. + <_> + + <_> + 2 11 20 2 -1. + <_> + 2 12 20 1 2. + <_> + + <_> + 18 8 4 16 -1. + <_> + 18 16 4 8 2. + <_> + + <_> + 1 11 12 10 -1. + <_> + 1 11 6 5 2. + <_> + 7 16 6 5 2. + <_> + + <_> + 6 9 12 4 -1. + <_> + 6 11 12 2 2. + <_> + + <_> + 9 12 6 7 -1. + <_> + 12 12 3 7 2. + <_> + + <_> + 10 4 8 16 -1. + <_> + 14 4 4 8 2. + <_> + 10 12 4 8 2. + <_> + + <_> + 6 4 8 16 -1. + <_> + 6 4 4 8 2. + <_> + 10 12 4 8 2. + <_> + + <_> + 8 9 9 6 -1. + <_> + 11 9 3 6 3. + <_> + + <_> + 1 5 16 12 -1. + <_> + 1 5 8 6 2. + <_> + 9 11 8 6 2. + <_> + + <_> + 9 9 6 8 -1. + <_> + 9 9 3 8 2. + <_> + + <_> + 6 0 3 18 -1. + <_> + 7 0 1 18 3. + <_> + + <_> + 17 9 5 14 -1. + <_> + 17 16 5 7 2. + <_> + + <_> + 2 9 5 14 -1. + <_> + 2 16 5 7 2. + <_> + + <_> + 7 4 10 6 -1. + <_> + 7 7 10 3 2. + <_> + + <_> + 1 3 23 18 -1. + <_> + 1 9 23 6 3. + <_> + + <_> + 1 1 21 3 -1. + <_> + 8 1 7 3 3. + <_> + + <_> + 9 6 6 9 -1. + <_> + 11 6 2 9 3. + <_> + + <_> + 3 18 12 6 -1. + <_> + 3 18 6 3 2. + <_> + 9 21 6 3 2. + <_> + + <_> + 16 8 8 16 -1. + <_> + 20 8 4 8 2. + <_> + 16 16 4 8 2. + <_> + + <_> + 0 19 24 4 -1. + <_> + 8 19 8 4 3. + <_> + + <_> + 16 8 8 16 -1. + <_> + 20 8 4 8 2. + <_> + 16 16 4 8 2. + <_> + + <_> + 0 8 8 16 -1. + <_> + 0 8 4 8 2. + <_> + 4 16 4 8 2. + <_> + + <_> + 8 12 8 10 -1. + <_> + 8 17 8 5 2. + <_> + + <_> + 5 7 5 8 -1. + <_> + 5 11 5 4 2. + <_> + + <_> + 4 1 19 2 -1. + <_> + 4 2 19 1 2. + <_> + + <_> + 0 12 24 9 -1. + <_> + 8 12 8 9 3. + <_> + + <_> + 6 0 13 8 -1. + <_> + 6 4 13 4 2. + <_> + + <_> + 0 0 24 3 -1. + <_> + 0 1 24 1 3. + <_> + + <_> + 20 3 4 11 -1. + <_> + 20 3 2 11 2. + <_> + + <_> + 8 6 6 9 -1. + <_> + 10 6 2 9 3. + <_> + + <_> + 6 11 12 8 -1. + <_> + 12 11 6 4 2. + <_> + 6 15 6 4 2. + <_> + + <_> + 0 8 12 6 -1. + <_> + 0 8 6 3 2. + <_> + 6 11 6 3 2. + <_> + + <_> + 6 17 18 3 -1. + <_> + 6 18 18 1 3. + <_> + + <_> + 0 14 9 6 -1. + <_> + 0 16 9 2 3. + <_> + + <_> + 20 3 4 9 -1. + <_> + 20 3 2 9 2. + <_> + + <_> + 0 3 4 9 -1. + <_> + 2 3 2 9 2. + <_> + + <_> + 15 0 9 19 -1. + <_> + 18 0 3 19 3. + <_> + + <_> + 0 0 9 19 -1. + <_> + 3 0 3 19 3. + <_> + + <_> + 13 11 6 8 -1. + <_> + 13 11 3 8 2. + <_> + + <_> + 5 11 6 8 -1. + <_> + 8 11 3 8 2. + <_> + + <_> + 5 11 19 3 -1. + <_> + 5 12 19 1 3. + <_> + + <_> + 3 20 18 4 -1. + <_> + 9 20 6 4 3. + <_> + + <_> + 6 6 16 6 -1. + <_> + 6 8 16 2 3. + <_> + + <_> + 6 0 9 6 -1. + <_> + 9 0 3 6 3. + <_> + + <_> + 10 3 4 14 -1. + <_> + 10 10 4 7 2. + <_> + + <_> + 1 5 15 12 -1. + <_> + 1 11 15 6 2. + <_> + + <_> + 11 12 8 5 -1. + <_> + 11 12 4 5 2. + <_> + + <_> + 5 0 6 9 -1. + <_> + 7 0 2 9 3. + <_> + + <_> + 12 0 6 9 -1. + <_> + 14 0 2 9 3. + <_> + + <_> + 5 5 12 8 -1. + <_> + 5 5 6 4 2. + <_> + 11 9 6 4 2. + <_> + + <_> + 13 12 11 6 -1. + <_> + 13 14 11 2 3. + <_> + + <_> + 0 13 21 3 -1. + <_> + 0 14 21 1 3. + <_> + + <_> + 8 1 8 12 -1. + <_> + 12 1 4 6 2. + <_> + 8 7 4 6 2. + <_> + + <_> + 1 0 6 12 -1. + <_> + 1 0 3 6 2. + <_> + 4 6 3 6 2. + <_> + + <_> + 2 2 21 2 -1. + <_> + 2 3 21 1 2. + <_> + + <_> + 2 2 19 3 -1. + <_> + 2 3 19 1 3. + <_> + + <_> + 17 10 6 14 -1. + <_> + 20 10 3 7 2. + <_> + 17 17 3 7 2. + <_> + + <_> + 1 10 6 14 -1. + <_> + 1 10 3 7 2. + <_> + 4 17 3 7 2. + <_> + + <_> + 7 6 14 14 -1. + <_> + 14 6 7 7 2. + <_> + 7 13 7 7 2. + <_> + + <_> + 0 12 9 6 -1. + <_> + 0 14 9 2 3. + <_> + + <_> + 15 14 8 9 -1. + <_> + 15 17 8 3 3. + <_> + + <_> + 1 1 22 4 -1. + <_> + 1 1 11 2 2. + <_> + 12 3 11 2 2. + <_> + + <_> + 9 11 9 6 -1. + <_> + 9 13 9 2 3. + <_> + + <_> + 0 15 18 3 -1. + <_> + 0 16 18 1 3. + <_> + + <_> + 16 14 7 9 -1. + <_> + 16 17 7 3 3. + <_> + + <_> + 4 3 16 4 -1. + <_> + 12 3 8 4 2. + <_> + + <_> + 7 6 12 5 -1. + <_> + 7 6 6 5 2. + <_> + + <_> + 9 6 4 9 -1. + <_> + 11 6 2 9 2. + <_> + + <_> + 12 1 4 10 -1. + <_> + 12 1 2 10 2. + <_> + + <_> + 8 1 4 10 -1. + <_> + 10 1 2 10 2. + <_> + + <_> + 15 15 6 9 -1. + <_> + 15 18 6 3 3. + <_> + + <_> + 3 15 6 9 -1. + <_> + 3 18 6 3 3. + <_> + + <_> + 15 1 3 19 -1. + <_> + 16 1 1 19 3. + <_> + + <_> + 1 3 6 9 -1. + <_> + 3 3 2 9 3. + <_> + + <_> + 15 0 3 19 -1. + <_> + 16 0 1 19 3. + <_> + + <_> + 6 3 12 4 -1. + <_> + 12 3 6 4 2. + <_> + + <_> + 10 5 4 9 -1. + <_> + 10 5 2 9 2. + <_> + + <_> + 6 0 3 19 -1. + <_> + 7 0 1 19 3. + <_> + + <_> + 11 1 3 12 -1. + <_> + 11 7 3 6 2. + <_> + + <_> + 6 7 10 5 -1. + <_> + 11 7 5 5 2. + <_> + + <_> + 11 3 3 18 -1. + <_> + 12 3 1 18 3. + <_> + + <_> + 9 3 6 12 -1. + <_> + 11 3 2 12 3. + <_> + + <_> + 3 7 19 3 -1. + <_> + 3 8 19 1 3. + <_> + + <_> + 2 7 18 3 -1. + <_> + 2 8 18 1 3. + <_> + + <_> + 3 13 18 4 -1. + <_> + 12 13 9 2 2. + <_> + 3 15 9 2 2. + <_> + + <_> + 3 5 6 9 -1. + <_> + 5 5 2 9 3. + <_> + + <_> + 4 1 20 4 -1. + <_> + 14 1 10 2 2. + <_> + 4 3 10 2 2. + <_> + + <_> + 0 1 20 4 -1. + <_> + 0 1 10 2 2. + <_> + 10 3 10 2 2. + <_> + + <_> + 10 15 6 6 -1. + <_> + 10 15 3 6 2. + <_> + + <_> + 0 2 24 8 -1. + <_> + 8 2 8 8 3. + <_> + + <_> + 5 5 18 3 -1. + <_> + 5 6 18 1 3. + <_> + + <_> + 8 15 6 6 -1. + <_> + 11 15 3 6 2. + <_> + + <_> + 11 12 8 5 -1. + <_> + 11 12 4 5 2. + <_> + + <_> + 5 12 8 5 -1. + <_> + 9 12 4 5 2. + <_> + + <_> + 5 0 14 6 -1. + <_> + 5 2 14 2 3. + <_> + + <_> + 10 2 4 15 -1. + <_> + 10 7 4 5 3. + <_> + + <_> + 10 7 5 12 -1. + <_> + 10 11 5 4 3. + <_> + + <_> + 7 9 8 14 -1. + <_> + 7 9 4 7 2. + <_> + 11 16 4 7 2. + <_> + + <_> + 1 5 22 6 -1. + <_> + 12 5 11 3 2. + <_> + 1 8 11 3 2. + <_> + + <_> + 0 5 6 6 -1. + <_> + 0 8 6 3 2. + <_> + + <_> + 12 17 9 4 -1. + <_> + 12 19 9 2 2. + <_> + + <_> + 2 18 19 3 -1. + <_> + 2 19 19 1 3. + <_> + + <_> + 12 17 9 4 -1. + <_> + 12 19 9 2 2. + <_> + + <_> + 1 17 18 3 -1. + <_> + 1 18 18 1 3. + <_> + + <_> + 12 17 9 4 -1. + <_> + 12 19 9 2 2. + <_> + + <_> + 0 0 24 3 -1. + <_> + 0 1 24 1 3. + <_> + + <_> + 5 0 14 4 -1. + <_> + 5 2 14 2 2. + <_> + + <_> + 6 14 9 6 -1. + <_> + 6 16 9 2 3. + <_> + + <_> + 14 13 6 9 -1. + <_> + 14 16 6 3 3. + <_> + + <_> + 5 20 13 4 -1. + <_> + 5 22 13 2 2. + <_> + + <_> + 9 9 6 12 -1. + <_> + 9 13 6 4 3. + <_> + + <_> + 1 10 21 3 -1. + <_> + 8 10 7 3 3. + <_> + + <_> + 8 8 9 6 -1. + <_> + 11 8 3 6 3. + <_> + + <_> + 3 10 9 7 -1. + <_> + 6 10 3 7 3. + <_> + + <_> + 12 10 10 8 -1. + <_> + 17 10 5 4 2. + <_> + 12 14 5 4 2. + <_> + + <_> + 0 15 24 3 -1. + <_> + 8 15 8 3 3. + <_> + + <_> + 8 5 9 6 -1. + <_> + 8 7 9 2 3. + <_> + + <_> + 4 13 6 9 -1. + <_> + 4 16 6 3 3. + <_> + + <_> + 12 17 9 4 -1. + <_> + 12 19 9 2 2. + <_> + + <_> + 9 12 6 6 -1. + <_> + 9 15 6 3 2. + <_> + + <_> + 9 9 14 10 -1. + <_> + 16 9 7 5 2. + <_> + 9 14 7 5 2. + <_> + + <_> + 1 9 14 10 -1. + <_> + 1 9 7 5 2. + <_> + 8 14 7 5 2. + <_> + + <_> + 8 7 9 17 -1. + <_> + 11 7 3 17 3. + <_> + + <_> + 3 4 6 20 -1. + <_> + 3 4 3 10 2. + <_> + 6 14 3 10 2. + <_> + + <_> + 7 8 10 4 -1. + <_> + 7 8 5 4 2. + <_> + + <_> + 10 7 4 9 -1. + <_> + 12 7 2 9 2. + <_> + + <_> + 10 15 6 9 -1. + <_> + 12 15 2 9 3. + <_> + + <_> + 3 8 6 16 -1. + <_> + 3 8 3 8 2. + <_> + 6 16 3 8 2. + <_> + + <_> + 12 17 9 4 -1. + <_> + 12 19 9 2 2. + <_> + + <_> + 3 17 9 4 -1. + <_> + 3 19 9 2 2. + <_> + + <_> + 10 1 9 6 -1. + <_> + 13 1 3 6 3. + <_> + + <_> + 5 7 4 10 -1. + <_> + 5 12 4 5 2. + <_> + + <_> + 7 5 12 6 -1. + <_> + 11 5 4 6 3. + <_> + + <_> + 6 4 9 8 -1. + <_> + 9 4 3 8 3. + <_> + + <_> + 12 16 10 8 -1. + <_> + 17 16 5 4 2. + <_> + 12 20 5 4 2. + <_> + + <_> + 2 16 10 8 -1. + <_> + 2 16 5 4 2. + <_> + 7 20 5 4 2. + <_> + + <_> + 0 0 24 4 -1. + <_> + 12 0 12 2 2. + <_> + 0 2 12 2 2. + <_> + + <_> + 0 6 9 6 -1. + <_> + 0 8 9 2 3. + <_> + + <_> + 0 4 24 6 -1. + <_> + 12 4 12 3 2. + <_> + 0 7 12 3 2. + <_> + + <_> + 5 0 11 4 -1. + <_> + 5 2 11 2 2. + <_> + + <_> + 1 1 22 4 -1. + <_> + 12 1 11 2 2. + <_> + 1 3 11 2 2. + <_> + + <_> + 9 6 6 18 -1. + <_> + 9 15 6 9 2. + <_> + + <_> + 2 9 20 4 -1. + <_> + 2 11 20 2 2. + <_> + + <_> + 5 2 14 14 -1. + <_> + 5 9 14 7 2. + <_> + + <_> + 4 2 16 6 -1. + <_> + 4 5 16 3 2. + <_> + + <_> + 2 3 19 3 -1. + <_> + 2 4 19 1 3. + <_> + + <_> + 7 1 10 4 -1. + <_> + 7 3 10 2 2. + <_> + + <_> + 0 9 4 15 -1. + <_> + 0 14 4 5 3. + <_> + + <_> + 2 10 21 3 -1. + <_> + 2 11 21 1 3. + <_> + + <_> + 3 0 6 6 -1. + <_> + 6 0 3 6 2. + <_> + + <_> + 6 4 14 9 -1. + <_> + 6 7 14 3 3. + <_> + + <_> + 9 1 6 9 -1. + <_> + 11 1 2 9 3. + <_> + + <_> + 15 8 9 9 -1. + <_> + 15 11 9 3 3. + <_> + + <_> + 8 0 4 21 -1. + <_> + 8 7 4 7 3. + <_> + + <_> + 3 22 19 2 -1. + <_> + 3 23 19 1 2. + <_> + + <_> + 2 15 20 3 -1. + <_> + 2 16 20 1 3. + <_> + + <_> + 19 0 4 13 -1. + <_> + 19 0 2 13 2. + <_> + + <_> + 1 7 8 8 -1. + <_> + 1 11 8 4 2. + <_> + + <_> + 14 14 6 9 -1. + <_> + 14 17 6 3 3. + <_> + + <_> + 4 14 6 9 -1. + <_> + 4 17 6 3 3. + <_> + + <_> + 14 5 4 10 -1. + <_> + 14 5 2 10 2. + <_> + + <_> + 6 5 4 10 -1. + <_> + 8 5 2 10 2. + <_> + + <_> + 14 5 6 6 -1. + <_> + 14 8 6 3 2. + <_> + + <_> + 4 5 6 6 -1. + <_> + 4 8 6 3 2. + <_> + + <_> + 0 2 24 21 -1. + <_> + 8 2 8 21 3. + <_> + + <_> + 1 2 6 13 -1. + <_> + 3 2 2 13 3. + <_> + + <_> + 20 0 4 21 -1. + <_> + 20 0 2 21 2. + <_> + + <_> + 0 4 4 20 -1. + <_> + 2 4 2 20 2. + <_> + + <_> + 8 16 9 6 -1. + <_> + 8 18 9 2 3. + <_> + + <_> + 7 0 6 9 -1. + <_> + 9 0 2 9 3. + <_> + + <_> + 16 12 7 9 -1. + <_> + 16 15 7 3 3. + <_> + + <_> + 5 21 14 3 -1. + <_> + 12 21 7 3 2. + <_> + + <_> + 11 5 6 9 -1. + <_> + 11 5 3 9 2. + <_> + + <_> + 10 5 4 10 -1. + <_> + 12 5 2 10 2. + <_> + + <_> + 10 6 6 9 -1. + <_> + 12 6 2 9 3. + <_> + + <_> + 7 5 6 9 -1. + <_> + 10 5 3 9 2. + <_> + + <_> + 14 14 10 4 -1. + <_> + 14 16 10 2 2. + <_> + + <_> + 5 5 14 14 -1. + <_> + 5 5 7 7 2. + <_> + 12 12 7 7 2. + <_> + + <_> + 12 8 12 6 -1. + <_> + 18 8 6 3 2. + <_> + 12 11 6 3 2. + <_> + + <_> + 6 6 12 12 -1. + <_> + 6 6 6 6 2. + <_> + 12 12 6 6 2. + <_> + + <_> + 11 13 6 10 -1. + <_> + 13 13 2 10 3. + <_> + + <_> + 1 10 20 8 -1. + <_> + 1 10 10 4 2. + <_> + 11 14 10 4 2. + <_> + + <_> + 15 13 9 6 -1. + <_> + 15 15 9 2 3. + <_> + + <_> + 9 0 6 9 -1. + <_> + 9 3 6 3 3. + <_> + + <_> + 10 1 5 14 -1. + <_> + 10 8 5 7 2. + <_> + + <_> + 3 4 16 6 -1. + <_> + 3 6 16 2 3. + <_> + + <_> + 16 3 8 9 -1. + <_> + 16 6 8 3 3. + <_> + + <_> + 7 13 6 10 -1. + <_> + 9 13 2 10 3. + <_> + + <_> + 15 13 9 6 -1. + <_> + 15 15 9 2 3. + <_> + + <_> + 0 13 9 6 -1. + <_> + 0 15 9 2 3. + <_> + + <_> + 13 16 9 6 -1. + <_> + 13 18 9 2 3. + <_> + + <_> + 2 16 9 6 -1. + <_> + 2 18 9 2 3. + <_> + + <_> + 5 16 18 3 -1. + <_> + 5 17 18 1 3. + <_> + + <_> + 1 16 18 3 -1. + <_> + 1 17 18 1 3. + <_> + + <_> + 5 0 18 3 -1. + <_> + 5 1 18 1 3. + <_> + + <_> + 1 1 19 2 -1. + <_> + 1 2 19 1 2. + <_> + + <_> + 14 2 6 11 -1. + <_> + 16 2 2 11 3. + <_> + + <_> + 4 15 15 6 -1. + <_> + 9 15 5 6 3. + <_> + + <_> + 14 2 6 11 -1. + <_> + 16 2 2 11 3. + <_> + + <_> + 4 2 6 11 -1. + <_> + 6 2 2 11 3. + <_> + + <_> + 18 2 6 9 -1. + <_> + 18 5 6 3 3. + <_> + + <_> + 1 2 22 4 -1. + <_> + 1 2 11 2 2. + <_> + 12 4 11 2 2. + <_> + + <_> + 2 0 21 12 -1. + <_> + 9 0 7 12 3. + <_> + + <_> + 0 12 18 3 -1. + <_> + 0 13 18 1 3. + <_> + + <_> + 12 2 6 9 -1. + <_> + 14 2 2 9 3. + <_> + + <_> + 3 10 18 3 -1. + <_> + 3 11 18 1 3. + <_> + + <_> + 16 3 8 9 -1. + <_> + 16 6 8 3 3. + <_> + + <_> + 3 7 18 3 -1. + <_> + 3 8 18 1 3. + <_> + + <_> + 9 11 6 9 -1. + <_> + 11 11 2 9 3. + <_> + + <_> + 9 8 6 9 -1. + <_> + 11 8 2 9 3. + <_> + + <_> + 15 0 2 18 -1. + <_> + 15 0 1 18 2. + <_> + + <_> + 7 0 2 18 -1. + <_> + 8 0 1 18 2. + <_> + + <_> + 17 3 7 9 -1. + <_> + 17 6 7 3 3. + <_> + + <_> + 3 18 9 6 -1. + <_> + 3 20 9 2 3. + <_> + + <_> + 3 18 21 3 -1. + <_> + 3 19 21 1 3. + <_> + + <_> + 0 3 7 9 -1. + <_> + 0 6 7 3 3. + <_> + + <_> + 2 7 22 3 -1. + <_> + 2 8 22 1 3. + <_> + + <_> + 0 3 24 16 -1. + <_> + 0 3 12 8 2. + <_> + 12 11 12 8 2. + <_> + + <_> + 13 17 9 4 -1. + <_> + 13 19 9 2 2. + <_> + + <_> + 5 5 12 8 -1. + <_> + 5 5 6 4 2. + <_> + 11 9 6 4 2. + <_> + + <_> + 5 6 14 6 -1. + <_> + 12 6 7 3 2. + <_> + 5 9 7 3 2. + <_> + + <_> + 5 16 14 6 -1. + <_> + 5 16 7 3 2. + <_> + 12 19 7 3 2. + <_> + + <_> + 18 2 6 9 -1. + <_> + 18 5 6 3 3. + <_> + + <_> + 0 2 6 9 -1. + <_> + 0 5 6 3 3. + <_> + + <_> + 3 4 20 10 -1. + <_> + 13 4 10 5 2. + <_> + 3 9 10 5 2. + <_> + + <_> + 2 13 9 8 -1. + <_> + 5 13 3 8 3. + <_> + + <_> + 2 1 21 15 -1. + <_> + 9 1 7 15 3. + <_> + + <_> + 5 12 14 8 -1. + <_> + 12 12 7 8 2. + <_> + + <_> + 6 7 12 4 -1. + <_> + 6 7 6 4 2. + <_> + + <_> + 6 5 9 6 -1. + <_> + 9 5 3 6 3. + <_> + + <_> + 13 11 6 6 -1. + <_> + 13 11 3 6 2. + <_> + + <_> + 5 11 6 6 -1. + <_> + 8 11 3 6 2. + <_> + + <_> + 6 4 18 2 -1. + <_> + 6 5 18 1 2. + <_> + + <_> + 0 2 6 11 -1. + <_> + 2 2 2 11 3. + <_> + + <_> + 18 0 6 15 -1. + <_> + 20 0 2 15 3. + <_> + + <_> + 0 0 6 13 -1. + <_> + 2 0 2 13 3. + <_> + + <_> + 12 0 6 9 -1. + <_> + 14 0 2 9 3. + <_> + + <_> + 6 0 6 9 -1. + <_> + 8 0 2 9 3. + <_> + + <_> + 0 2 24 4 -1. + <_> + 8 2 8 4 3. + <_> + + <_> + 3 13 18 4 -1. + <_> + 12 13 9 4 2. + <_> + + <_> + 9 7 10 4 -1. + <_> + 9 7 5 4 2. + <_> + + <_> + 5 8 12 3 -1. + <_> + 11 8 6 3 2. + <_> + + <_> + 4 14 19 3 -1. + <_> + 4 15 19 1 3. + <_> + + <_> + 10 0 4 20 -1. + <_> + 10 10 4 10 2. + <_> + + <_> + 8 15 9 6 -1. + <_> + 8 17 9 2 3. + <_> + + <_> + 2 9 15 4 -1. + <_> + 7 9 5 4 3. + <_> + + <_> + 8 4 12 7 -1. + <_> + 12 4 4 7 3. + <_> + + <_> + 0 10 6 9 -1. + <_> + 0 13 6 3 3. + <_> + + <_> + 18 5 6 9 -1. + <_> + 18 8 6 3 3. + <_> + + <_> + 0 18 16 6 -1. + <_> + 0 18 8 3 2. + <_> + 8 21 8 3 2. + <_> + + <_> + 9 18 14 6 -1. + <_> + 16 18 7 3 2. + <_> + 9 21 7 3 2. + <_> + + <_> + 1 20 20 4 -1. + <_> + 1 20 10 2 2. + <_> + 11 22 10 2 2. + <_> + + <_> + 2 8 20 6 -1. + <_> + 12 8 10 3 2. + <_> + 2 11 10 3 2. + <_> + + <_> + 7 8 6 9 -1. + <_> + 9 8 2 9 3. + <_> + + <_> + 8 5 12 8 -1. + <_> + 12 5 4 8 3. + <_> + + <_> + 4 5 12 8 -1. + <_> + 8 5 4 8 3. + <_> + + <_> + 10 6 6 9 -1. + <_> + 12 6 2 9 3. + <_> + + <_> + 2 0 6 16 -1. + <_> + 4 0 2 16 3. + <_> + + <_> + 15 4 6 12 -1. + <_> + 15 8 6 4 3. + <_> + + <_> + 3 4 6 12 -1. + <_> + 3 8 6 4 3. + <_> + + <_> + 15 12 9 6 -1. + <_> + 15 14 9 2 3. + <_> + + <_> + 4 0 15 22 -1. + <_> + 4 11 15 11 2. + <_> + + <_> + 15 12 9 6 -1. + <_> + 15 14 9 2 3. + <_> + + <_> + 0 12 9 6 -1. + <_> + 0 14 9 2 3. + <_> + + <_> + 15 15 9 6 -1. + <_> + 15 17 9 2 3. + <_> + + <_> + 0 15 9 6 -1. + <_> + 0 17 9 2 3. + <_> + + <_> + 10 0 8 10 -1. + <_> + 14 0 4 5 2. + <_> + 10 5 4 5 2. + <_> + + <_> + 1 0 4 16 -1. + <_> + 3 0 2 16 2. + <_> + + <_> + 7 6 10 6 -1. + <_> + 7 8 10 2 3. + <_> + + <_> + 10 12 4 10 -1. + <_> + 10 17 4 5 2. + <_> + + <_> + 8 4 10 6 -1. + <_> + 8 6 10 2 3. + <_> + + <_> + 3 22 18 2 -1. + <_> + 12 22 9 2 2. + <_> + + <_> + 7 7 11 6 -1. + <_> + 7 9 11 2 3. + <_> + + <_> + 0 0 12 10 -1. + <_> + 0 0 6 5 2. + <_> + 6 5 6 5 2. + <_> + + <_> + 10 1 12 6 -1. + <_> + 16 1 6 3 2. + <_> + 10 4 6 3 2. + <_> + + <_> + 7 16 9 4 -1. + <_> + 7 18 9 2 2. + <_> + + <_> + 5 7 15 16 -1. + <_> + 10 7 5 16 3. + <_> + + <_> + 5 10 12 13 -1. + <_> + 11 10 6 13 2. + <_> + + <_> + 6 2 12 6 -1. + <_> + 12 2 6 3 2. + <_> + 6 5 6 3 2. + <_> + + <_> + 3 9 12 9 -1. + <_> + 3 12 12 3 3. + <_> + + <_> + 16 2 8 6 -1. + <_> + 16 5 8 3 2. + <_> + + <_> + 0 2 8 6 -1. + <_> + 0 5 8 3 2. + <_> + + <_> + 0 3 24 11 -1. + <_> + 0 3 12 11 2. + <_> + + <_> + 0 13 8 10 -1. + <_> + 0 13 4 5 2. + <_> + 4 18 4 5 2. + <_> + + <_> + 10 14 4 10 -1. + <_> + 10 19 4 5 2. + <_> + + <_> + 10 2 4 21 -1. + <_> + 10 9 4 7 3. + <_> + + <_> + 4 4 15 9 -1. + <_> + 4 7 15 3 3. + <_> + + <_> + 0 1 24 6 -1. + <_> + 8 1 8 6 3. + <_> + + <_> + 9 6 5 16 -1. + <_> + 9 14 5 8 2. + <_> + + <_> + 3 21 18 3 -1. + <_> + 9 21 6 3 3. + <_> + + <_> + 6 5 3 12 -1. + <_> + 6 11 3 6 2. + <_> + + <_> + 11 6 4 9 -1. + <_> + 11 6 2 9 2. + <_> + + <_> + 5 6 9 8 -1. + <_> + 8 6 3 8 3. + <_> + + <_> + 4 3 20 2 -1. + <_> + 4 4 20 1 2. + <_> + + <_> + 2 10 18 3 -1. + <_> + 8 10 6 3 3. + <_> + + <_> + 7 15 10 6 -1. + <_> + 7 17 10 2 3. + <_> + + <_> + 1 4 4 18 -1. + <_> + 1 4 2 9 2. + <_> + 3 13 2 9 2. + <_> + + <_> + 13 0 6 9 -1. + <_> + 15 0 2 9 3. + <_> + + <_> + 5 0 6 9 -1. + <_> + 7 0 2 9 3. + <_> + + <_> + 11 0 6 9 -1. + <_> + 13 0 2 9 3. + <_> + + <_> + 6 7 9 6 -1. + <_> + 9 7 3 6 3. + <_> + + <_> + 3 0 18 2 -1. + <_> + 3 1 18 1 2. + <_> + + <_> + 0 10 20 4 -1. + <_> + 0 10 10 2 2. + <_> + 10 12 10 2 2. + <_> + + <_> + 10 2 4 12 -1. + <_> + 10 8 4 6 2. + <_> + + <_> + 6 5 6 12 -1. + <_> + 6 5 3 6 2. + <_> + 9 11 3 6 2. + <_> + + <_> + 6 0 18 22 -1. + <_> + 15 0 9 11 2. + <_> + 6 11 9 11 2. + <_> + + <_> + 0 0 18 22 -1. + <_> + 0 0 9 11 2. + <_> + 9 11 9 11 2. + <_> + + <_> + 18 2 6 11 -1. + <_> + 20 2 2 11 3. + <_> + + <_> + 0 2 6 11 -1. + <_> + 2 2 2 11 3. + <_> + + <_> + 11 0 6 9 -1. + <_> + 13 0 2 9 3. + <_> + + <_> + 0 0 20 3 -1. + <_> + 0 1 20 1 3. + <_> + + <_> + 2 2 20 2 -1. + <_> + 2 3 20 1 2. + <_> + + <_> + 1 10 18 2 -1. + <_> + 1 11 18 1 2. + <_> + + <_> + 18 7 6 9 -1. + <_> + 18 10 6 3 3. + <_> + + <_> + 0 0 22 9 -1. + <_> + 0 3 22 3 3. + <_> + + <_> + 17 3 6 9 -1. + <_> + 17 6 6 3 3. + <_> + + <_> + 0 7 6 9 -1. + <_> + 0 10 6 3 3. + <_> + + <_> + 0 6 24 6 -1. + <_> + 0 8 24 2 3. + <_> + + <_> + 0 2 6 10 -1. + <_> + 2 2 2 10 3. + <_> + + <_> + 10 6 6 9 -1. + <_> + 12 6 2 9 3. + <_> + + <_> + 7 0 6 9 -1. + <_> + 9 0 2 9 3. + <_> + + <_> + 15 0 6 9 -1. + <_> + 17 0 2 9 3. + <_> + + <_> + 3 0 6 9 -1. + <_> + 5 0 2 9 3. + <_> + + <_> + 15 17 9 6 -1. + <_> + 15 19 9 2 3. + <_> + + <_> + 0 17 18 3 -1. + <_> + 0 18 18 1 3. + <_> + + <_> + 15 14 9 6 -1. + <_> + 15 16 9 2 3. + <_> + + <_> + 0 15 23 6 -1. + <_> + 0 17 23 2 3. + <_> + + <_> + 5 15 18 3 -1. + <_> + 5 16 18 1 3. + <_> + + <_> + 0 14 9 6 -1. + <_> + 0 16 9 2 3. + <_> + + <_> + 9 8 8 10 -1. + <_> + 13 8 4 5 2. + <_> + 9 13 4 5 2. + <_> + + <_> + 3 7 15 6 -1. + <_> + 8 7 5 6 3. + <_> + + <_> + 9 8 8 10 -1. + <_> + 13 8 4 5 2. + <_> + 9 13 4 5 2. + <_> + + <_> + 5 0 6 12 -1. + <_> + 8 0 3 12 2. + <_> + + <_> + 9 8 8 10 -1. + <_> + 13 8 4 5 2. + <_> + 9 13 4 5 2. + <_> + + <_> + 8 5 6 9 -1. + <_> + 10 5 2 9 3. + <_> + + <_> + 10 6 4 18 -1. + <_> + 12 6 2 9 2. + <_> + 10 15 2 9 2. + <_> + + <_> + 5 7 12 4 -1. + <_> + 11 7 6 4 2. + <_> + + <_> + 9 8 8 10 -1. + <_> + 13 8 4 5 2. + <_> + 9 13 4 5 2. + <_> + + <_> + 7 8 8 10 -1. + <_> + 7 8 4 5 2. + <_> + 11 13 4 5 2. + <_> + + <_> + 11 10 6 14 -1. + <_> + 14 10 3 7 2. + <_> + 11 17 3 7 2. + <_> + + <_> + 9 5 6 19 -1. + <_> + 12 5 3 19 2. + <_> + + <_> + 6 12 12 6 -1. + <_> + 12 12 6 3 2. + <_> + 6 15 6 3 2. + <_> + + <_> + 1 9 18 6 -1. + <_> + 1 9 9 3 2. + <_> + 10 12 9 3 2. + <_> + + <_> + 16 14 8 10 -1. + <_> + 20 14 4 5 2. + <_> + 16 19 4 5 2. + <_> + + <_> + 0 9 22 8 -1. + <_> + 0 9 11 4 2. + <_> + 11 13 11 4 2. + <_> + + <_> + 8 18 12 6 -1. + <_> + 14 18 6 3 2. + <_> + 8 21 6 3 2. + <_> + + <_> + 0 6 20 18 -1. + <_> + 0 6 10 9 2. + <_> + 10 15 10 9 2. + <_> + + <_> + 3 6 20 12 -1. + <_> + 13 6 10 6 2. + <_> + 3 12 10 6 2. + <_> + + <_> + 0 16 10 8 -1. + <_> + 0 16 5 4 2. + <_> + 5 20 5 4 2. + <_> + + <_> + 6 16 18 3 -1. + <_> + 6 17 18 1 3. + <_> + + <_> + 0 11 19 3 -1. + <_> + 0 12 19 1 3. + <_> + + <_> + 14 6 6 9 -1. + <_> + 14 9 6 3 3. + <_> + + <_> + 1 7 22 4 -1. + <_> + 1 7 11 2 2. + <_> + 12 9 11 2 2. + <_> + + <_> + 13 6 7 12 -1. + <_> + 13 10 7 4 3. + <_> + + <_> + 4 7 11 9 -1. + <_> + 4 10 11 3 3. + <_> + + <_> + 12 10 10 8 -1. + <_> + 17 10 5 4 2. + <_> + 12 14 5 4 2. + <_> + + <_> + 2 12 9 7 -1. + <_> + 5 12 3 7 3. + <_> + + <_> + 16 14 6 9 -1. + <_> + 16 17 6 3 3. + <_> + + <_> + 3 12 6 12 -1. + <_> + 3 16 6 4 3. + <_> + + <_> + 14 13 6 6 -1. + <_> + 14 16 6 3 2. + <_> + + <_> + 8 0 6 9 -1. + <_> + 10 0 2 9 3. + <_> + + <_> + 9 1 6 23 -1. + <_> + 11 1 2 23 3. + <_> + + <_> + 0 16 9 6 -1. + <_> + 0 18 9 2 3. + <_> + + <_> + 4 17 18 3 -1. + <_> + 4 18 18 1 3. + <_> + + <_> + 5 2 13 14 -1. + <_> + 5 9 13 7 2. + <_> + + <_> + 15 0 8 12 -1. + <_> + 19 0 4 6 2. + <_> + 15 6 4 6 2. + <_> + + <_> + 0 0 8 12 -1. + <_> + 0 0 4 6 2. + <_> + 4 6 4 6 2. + <_> + + <_> + 8 2 8 7 -1. + <_> + 8 2 4 7 2. + <_> + + <_> + 1 1 6 9 -1. + <_> + 3 1 2 9 3. + <_> + + <_> + 14 8 6 12 -1. + <_> + 17 8 3 6 2. + <_> + 14 14 3 6 2. + <_> + + <_> + 4 8 6 12 -1. + <_> + 4 8 3 6 2. + <_> + 7 14 3 6 2. + <_> + + <_> + 16 5 5 15 -1. + <_> + 16 10 5 5 3. + <_> + + <_> + 3 5 5 15 -1. + <_> + 3 10 5 5 3. + <_> + + <_> + 18 4 6 9 -1. + <_> + 18 7 6 3 3. + <_> + + <_> + 1 7 6 15 -1. + <_> + 1 12 6 5 3. + <_> + + <_> + 11 15 12 8 -1. + <_> + 17 15 6 4 2. + <_> + 11 19 6 4 2. + <_> + + <_> + 0 2 24 4 -1. + <_> + 0 2 12 2 2. + <_> + 12 4 12 2 2. + <_> + + <_> + 15 1 2 19 -1. + <_> + 15 1 1 19 2. + <_> + + <_> + 7 1 2 19 -1. + <_> + 8 1 1 19 2. + <_> + + <_> + 22 1 2 20 -1. + <_> + 22 1 1 20 2. + <_> + + <_> + 0 1 2 20 -1. + <_> + 1 1 1 20 2. + <_> + + <_> + 18 11 6 12 -1. + <_> + 20 11 2 12 3. + <_> + + <_> + 0 11 6 12 -1. + <_> + 2 11 2 12 3. + <_> + + <_> + 3 6 18 14 -1. + <_> + 3 13 18 7 2. + <_> + + <_> + 6 10 7 8 -1. + <_> + 6 14 7 4 2. + <_> + + <_> + 7 9 12 12 -1. + <_> + 7 13 12 4 3. + <_> + + <_> + 2 18 18 5 -1. + <_> + 11 18 9 5 2. + <_> + + <_> + 4 21 20 3 -1. + <_> + 4 22 20 1 3. + <_> + + <_> + 9 12 6 12 -1. + <_> + 9 12 3 6 2. + <_> + 12 18 3 6 2. + <_> + + <_> + 4 6 18 3 -1. + <_> + 4 7 18 1 3. + <_> + + <_> + 3 6 18 3 -1. + <_> + 3 7 18 1 3. + <_> + + <_> + 18 4 6 9 -1. + <_> + 18 7 6 3 3. + <_> + + <_> + 2 12 9 6 -1. + <_> + 2 14 9 2 3. + <_> + + <_> + 4 14 18 4 -1. + <_> + 13 14 9 2 2. + <_> + 4 16 9 2 2. + <_> + + <_> + 7 7 6 14 -1. + <_> + 7 7 3 7 2. + <_> + 10 14 3 7 2. + <_> + + <_> + 7 13 12 6 -1. + <_> + 13 13 6 3 2. + <_> + 7 16 6 3 2. + <_> + + <_> + 6 7 12 9 -1. + <_> + 10 7 4 9 3. + <_> + + <_> + 12 12 6 6 -1. + <_> + 12 12 3 6 2. + <_> + + <_> + 0 2 4 10 -1. + <_> + 0 7 4 5 2. + <_> + + <_> + 8 0 9 6 -1. + <_> + 11 0 3 6 3. + <_> + + <_> + 2 9 12 6 -1. + <_> + 2 12 12 3 2. + <_> + + <_> + 13 10 6 9 -1. + <_> + 13 13 6 3 3. + <_> + + <_> + 5 10 6 9 -1. + <_> + 5 13 6 3 3. + <_> + + <_> + 9 15 9 6 -1. + <_> + 9 17 9 2 3. + <_> + + <_> + 5 16 12 6 -1. + <_> + 5 19 12 3 2. + <_> + + <_> + 3 2 20 3 -1. + <_> + 3 3 20 1 3. + <_> + + <_> + 2 5 12 6 -1. + <_> + 6 5 4 6 3. + <_> + + <_> + 11 0 3 24 -1. + <_> + 12 0 1 24 3. + <_> + + <_> + 3 16 15 4 -1. + <_> + 8 16 5 4 3. + <_> + + <_> + 9 12 6 12 -1. + <_> + 9 18 6 6 2. + <_> + + <_> + 1 15 12 8 -1. + <_> + 1 15 6 4 2. + <_> + 7 19 6 4 2. + <_> + + <_> + 15 10 8 14 -1. + <_> + 19 10 4 7 2. + <_> + 15 17 4 7 2. + <_> + + <_> + 1 9 8 14 -1. + <_> + 1 9 4 7 2. + <_> + 5 16 4 7 2. + <_> + + <_> + 9 11 9 10 -1. + <_> + 9 16 9 5 2. + <_> + + <_> + 6 7 12 6 -1. + <_> + 6 9 12 2 3. + <_> + + <_> + 10 15 6 9 -1. + <_> + 12 15 2 9 3. + <_> + + <_> + 7 8 9 7 -1. + <_> + 10 8 3 7 3. + <_> + + <_> + 10 4 8 10 -1. + <_> + 14 4 4 5 2. + <_> + 10 9 4 5 2. + <_> + + <_> + 4 6 6 9 -1. + <_> + 4 9 6 3 3. + <_> + + <_> + 0 6 24 12 -1. + <_> + 8 6 8 12 3. + <_> + + <_> + 3 7 6 14 -1. + <_> + 6 7 3 14 2. + <_> + + <_> + 19 8 5 8 -1. + <_> + 19 12 5 4 2. + <_> + + <_> + 0 8 5 8 -1. + <_> + 0 12 5 4 2. + <_> + + <_> + 17 3 6 6 -1. + <_> + 17 6 6 3 2. + <_> + + <_> + 1 3 6 6 -1. + <_> + 1 6 6 3 2. + <_> + + <_> + 18 2 6 9 -1. + <_> + 18 5 6 3 3. + <_> + + <_> + 0 2 6 9 -1. + <_> + 0 5 6 3 3. + <_> + + <_> + 3 3 18 6 -1. + <_> + 3 5 18 2 3. + <_> + + <_> + 2 3 9 6 -1. + <_> + 2 5 9 2 3. + <_> + + <_> + 9 3 10 8 -1. + <_> + 14 3 5 4 2. + <_> + 9 7 5 4 2. + <_> + + <_> + 5 3 10 8 -1. + <_> + 5 3 5 4 2. + <_> + 10 7 5 4 2. + <_> + + <_> + 10 11 6 12 -1. + <_> + 10 11 3 12 2. + <_> + + <_> + 8 11 6 11 -1. + <_> + 11 11 3 11 2. + <_> + + <_> + 7 8 10 4 -1. + <_> + 7 8 5 4 2. + <_> + + <_> + 9 6 6 7 -1. + <_> + 12 6 3 7 2. + <_> + + <_> + 5 18 18 3 -1. + <_> + 5 19 18 1 3. + <_> + + <_> + 8 4 6 9 -1. + <_> + 10 4 2 9 3. + <_> + + <_> + 8 1 9 7 -1. + <_> + 11 1 3 7 3. + <_> + + <_> + 6 11 6 6 -1. + <_> + 9 11 3 6 2. + <_> + + <_> + 14 12 4 11 -1. + <_> + 14 12 2 11 2. + <_> + + <_> + 6 12 4 11 -1. + <_> + 8 12 2 11 2. + <_> + + <_> + 8 0 12 18 -1. + <_> + 12 0 4 18 3. + <_> + + <_> + 2 12 10 5 -1. + <_> + 7 12 5 5 2. + <_> + + <_> + 2 20 22 3 -1. + <_> + 2 21 22 1 3. + <_> + + <_> + 0 4 2 20 -1. + <_> + 1 4 1 20 2. + <_> + + <_> + 0 2 24 4 -1. + <_> + 8 2 8 4 3. + <_> + + <_> + 7 8 10 4 -1. + <_> + 7 10 10 2 2. + <_> + + <_> + 6 7 8 10 -1. + <_> + 6 7 4 5 2. + <_> + 10 12 4 5 2. + <_> + + <_> + 14 0 6 14 -1. + <_> + 17 0 3 7 2. + <_> + 14 7 3 7 2. + <_> + + <_> + 4 11 5 8 -1. + <_> + 4 15 5 4 2. + <_> + + <_> + 2 0 20 9 -1. + <_> + 2 3 20 3 3. + <_> + + <_> + 6 7 12 8 -1. + <_> + 6 7 6 4 2. + <_> + 12 11 6 4 2. + <_> + + <_> + 9 17 6 6 -1. + <_> + 9 20 6 3 2. + <_> + + <_> + 7 10 10 4 -1. + <_> + 7 12 10 2 2. + <_> + + <_> + 6 5 12 9 -1. + <_> + 10 5 4 9 3. + <_> + + <_> + 5 11 6 8 -1. + <_> + 8 11 3 8 2. + <_> + + <_> + 18 4 4 17 -1. + <_> + 18 4 2 17 2. + <_> + + <_> + 0 0 6 6 -1. + <_> + 3 0 3 6 2. + <_> + + <_> + 18 4 4 17 -1. + <_> + 18 4 2 17 2. + <_> + + <_> + 2 4 4 17 -1. + <_> + 4 4 2 17 2. + <_> + + <_> + 5 18 19 3 -1. + <_> + 5 19 19 1 3. + <_> + + <_> + 11 0 2 18 -1. + <_> + 11 9 2 9 2. + <_> + + <_> + 15 4 2 18 -1. + <_> + 15 13 2 9 2. + <_> + + <_> + 7 4 2 18 -1. + <_> + 7 13 2 9 2. + <_> + + <_> + 7 11 10 8 -1. + <_> + 12 11 5 4 2. + <_> + 7 15 5 4 2. + <_> + + <_> + 10 6 4 9 -1. + <_> + 12 6 2 9 2. + <_> + + <_> + 10 0 6 9 -1. + <_> + 12 0 2 9 3. + <_> + + <_> + 2 9 16 8 -1. + <_> + 2 9 8 4 2. + <_> + 10 13 8 4 2. + <_> + + <_> + 14 15 6 9 -1. + <_> + 14 18 6 3 3. + <_> + + <_> + 8 7 6 9 -1. + <_> + 10 7 2 9 3. + <_> + + <_> + 14 15 6 9 -1. + <_> + 14 18 6 3 3. + <_> + + <_> + 3 12 12 6 -1. + <_> + 3 14 12 2 3. + <_> + + <_> + 14 12 9 6 -1. + <_> + 14 14 9 2 3. + <_> + + <_> + 1 12 9 6 -1. + <_> + 1 14 9 2 3. + <_> + + <_> + 3 7 18 3 -1. + <_> + 3 8 18 1 3. + <_> + + <_> + 1 7 22 6 -1. + <_> + 1 9 22 2 3. + <_> + + <_> + 18 4 6 6 -1. + <_> + 18 7 6 3 2. + <_> + + <_> + 0 4 6 6 -1. + <_> + 0 7 6 3 2. + <_> + + <_> + 5 11 16 6 -1. + <_> + 5 14 16 3 2. + <_> + + <_> + 6 16 9 4 -1. + <_> + 6 18 9 2 2. + <_> + + <_> + 14 15 6 9 -1. + <_> + 14 18 6 3 3. + <_> + + <_> + 4 15 6 9 -1. + <_> + 4 18 6 3 3. + <_> + + <_> + 15 1 6 23 -1. + <_> + 17 1 2 23 3. + <_> + + <_> + 0 21 24 3 -1. + <_> + 8 21 8 3 3. + <_> + + <_> + 0 20 24 4 -1. + <_> + 8 20 8 4 3. + <_> + + <_> + 3 1 6 23 -1. + <_> + 5 1 2 23 3. + <_> + + <_> + 3 17 18 3 -1. + <_> + 3 18 18 1 3. + <_> + + <_> + 0 16 18 3 -1. + <_> + 0 17 18 1 3. + <_> + + <_> + 1 16 22 4 -1. + <_> + 12 16 11 2 2. + <_> + 1 18 11 2 2. + <_> + + <_> + 0 16 9 6 -1. + <_> + 0 18 9 2 3. + <_> + + <_> + 2 10 21 3 -1. + <_> + 9 10 7 3 3. + <_> + + <_> + 2 18 12 6 -1. + <_> + 2 18 6 3 2. + <_> + 8 21 6 3 2. + <_> + + <_> + 0 5 24 4 -1. + <_> + 0 7 24 2 2. + <_> + + <_> + 10 2 4 15 -1. + <_> + 10 7 4 5 3. + <_> + + <_> + 10 7 6 12 -1. + <_> + 10 13 6 6 2. + <_> + + <_> + 6 6 6 9 -1. + <_> + 8 6 2 9 3. + <_> + + <_> + 11 0 6 9 -1. + <_> + 13 0 2 9 3. + <_> + + <_> + 9 7 6 9 -1. + <_> + 11 7 2 9 3. + <_> + + <_> + 2 1 20 3 -1. + <_> + 2 2 20 1 3. + <_> + + <_> + 1 18 12 6 -1. + <_> + 1 18 6 3 2. + <_> + 7 21 6 3 2. + <_> + + <_> + 13 2 4 13 -1. + <_> + 13 2 2 13 2. + <_> + + <_> + 6 7 12 4 -1. + <_> + 12 7 6 4 2. + <_> + + <_> + 10 1 4 13 -1. + <_> + 10 1 2 13 2. + <_> + + <_> + 6 0 3 18 -1. + <_> + 7 0 1 18 3. + <_> + + <_> + 14 3 10 5 -1. + <_> + 14 3 5 5 2. + <_> + + <_> + 6 15 12 8 -1. + <_> + 10 15 4 8 3. + <_> + + <_> + 9 10 6 9 -1. + <_> + 11 10 2 9 3. + <_> + + <_> + 8 3 4 9 -1. + <_> + 10 3 2 9 2. + <_> + + <_> + 17 0 6 14 -1. + <_> + 20 0 3 7 2. + <_> + 17 7 3 7 2. + <_> + + <_> + 1 0 6 14 -1. + <_> + 1 0 3 7 2. + <_> + 4 7 3 7 2. + <_> + + <_> + 14 0 6 16 -1. + <_> + 17 0 3 8 2. + <_> + 14 8 3 8 2. + <_> + + <_> + 7 4 4 10 -1. + <_> + 9 4 2 10 2. + <_> + + <_> + 3 17 18 6 -1. + <_> + 12 17 9 3 2. + <_> + 3 20 9 3 2. + <_> + + <_> + 1 20 22 4 -1. + <_> + 12 20 11 4 2. + <_> + + <_> + 14 3 10 5 -1. + <_> + 14 3 5 5 2. + <_> + + <_> + 0 3 10 5 -1. + <_> + 5 3 5 5 2. + <_> + + <_> + 12 6 12 16 -1. + <_> + 16 6 4 16 3. + <_> + + <_> + 0 6 12 16 -1. + <_> + 4 6 4 16 3. + <_> + + <_> + 10 9 5 15 -1. + <_> + 10 14 5 5 3. + <_> + + <_> + 1 18 21 2 -1. + <_> + 1 19 21 1 2. + <_> + + <_> + 15 0 9 6 -1. + <_> + 15 2 9 2 3. + <_> + + <_> + 6 1 12 4 -1. + <_> + 12 1 6 4 2. + <_> + + <_> + 6 0 12 12 -1. + <_> + 12 0 6 6 2. + <_> + 6 6 6 6 2. + <_> + + <_> + 8 10 8 12 -1. + <_> + 8 10 4 6 2. + <_> + 12 16 4 6 2. + <_> + + <_> + 14 16 10 8 -1. + <_> + 19 16 5 4 2. + <_> + 14 20 5 4 2. + <_> + + <_> + 0 16 10 8 -1. + <_> + 0 16 5 4 2. + <_> + 5 20 5 4 2. + <_> + + <_> + 10 12 12 5 -1. + <_> + 14 12 4 5 3. + <_> + + <_> + 6 16 10 8 -1. + <_> + 6 16 5 4 2. + <_> + 11 20 5 4 2. + <_> + + <_> + 7 6 12 6 -1. + <_> + 13 6 6 3 2. + <_> + 7 9 6 3 2. + <_> + + <_> + 9 6 4 18 -1. + <_> + 9 6 2 9 2. + <_> + 11 15 2 9 2. + <_> + + <_> + 10 9 6 14 -1. + <_> + 13 9 3 7 2. + <_> + 10 16 3 7 2. + <_> + + <_> + 8 9 6 14 -1. + <_> + 8 9 3 7 2. + <_> + 11 16 3 7 2. + <_> + + <_> + 7 4 11 12 -1. + <_> + 7 10 11 6 2. + <_> + + <_> + 4 8 6 16 -1. + <_> + 4 8 3 8 2. + <_> + 7 16 3 8 2. + <_> + + <_> + 17 3 4 21 -1. + <_> + 17 10 4 7 3. + <_> + + <_> + 3 3 4 21 -1. + <_> + 3 10 4 7 3. + <_> + + <_> + 10 1 8 18 -1. + <_> + 14 1 4 9 2. + <_> + 10 10 4 9 2. + <_> + + <_> + 2 5 16 8 -1. + <_> + 2 5 8 4 2. + <_> + 10 9 8 4 2. + <_> + + <_> + 3 6 18 12 -1. + <_> + 3 10 18 4 3. + <_> + + <_> + 4 10 16 12 -1. + <_> + 4 14 16 4 3. + <_> + + <_> + 15 4 8 20 -1. + <_> + 19 4 4 10 2. + <_> + 15 14 4 10 2. + <_> + + <_> + 7 2 9 6 -1. + <_> + 10 2 3 6 3. + <_> + + <_> + 15 4 8 20 -1. + <_> + 19 4 4 10 2. + <_> + 15 14 4 10 2. + <_> + + <_> + 1 4 8 20 -1. + <_> + 1 4 4 10 2. + <_> + 5 14 4 10 2. + <_> + + <_> + 11 8 8 14 -1. + <_> + 15 8 4 7 2. + <_> + 11 15 4 7 2. + <_> + + <_> + 5 8 8 14 -1. + <_> + 5 8 4 7 2. + <_> + 9 15 4 7 2. + <_> + + <_> + 10 13 5 8 -1. + <_> + 10 17 5 4 2. + <_> + + <_> + 4 13 7 9 -1. + <_> + 4 16 7 3 3. + <_> + + <_> + 0 13 24 10 -1. + <_> + 0 18 24 5 2. + <_> + + <_> + 4 2 8 11 -1. + <_> + 8 2 4 11 2. + <_> + + <_> + 10 2 8 16 -1. + <_> + 14 2 4 8 2. + <_> + 10 10 4 8 2. + <_> + + <_> + 0 2 24 6 -1. + <_> + 0 2 12 3 2. + <_> + 12 5 12 3 2. + <_> + + <_> + 6 0 12 9 -1. + <_> + 6 3 12 3 3. + <_> + + <_> + 1 2 12 12 -1. + <_> + 1 2 6 6 2. + <_> + 7 8 6 6 2. + <_> + + <_> + 18 5 6 9 -1. + <_> + 18 8 6 3 3. + <_> + + <_> + 4 3 8 10 -1. + <_> + 4 3 4 5 2. + <_> + 8 8 4 5 2. + <_> + + <_> + 6 21 18 3 -1. + <_> + 6 22 18 1 3. + <_> + + <_> + 1 10 18 2 -1. + <_> + 1 11 18 1 2. + <_> + + <_> + 1 10 22 3 -1. + <_> + 1 11 22 1 3. + <_> + + <_> + 2 8 12 9 -1. + <_> + 2 11 12 3 3. + <_> + + <_> + 12 8 12 6 -1. + <_> + 18 8 6 3 2. + <_> + 12 11 6 3 2. + <_> + + <_> + 0 8 12 6 -1. + <_> + 0 8 6 3 2. + <_> + 6 11 6 3 2. + <_> + + <_> + 10 15 6 9 -1. + <_> + 12 15 2 9 3. + <_> + + <_> + 7 13 9 6 -1. + <_> + 7 15 9 2 3. + <_> + + <_> + 9 8 7 12 -1. + <_> + 9 14 7 6 2. + <_> + + <_> + 4 13 9 6 -1. + <_> + 7 13 3 6 3. + <_> + + <_> + 6 15 18 4 -1. + <_> + 12 15 6 4 3. + <_> + + <_> + 5 4 4 16 -1. + <_> + 7 4 2 16 2. + <_> + + <_> + 10 15 6 9 -1. + <_> + 12 15 2 9 3. + <_> + + <_> + 8 15 6 9 -1. + <_> + 10 15 2 9 3. + <_> + + <_> + 9 11 12 10 -1. + <_> + 15 11 6 5 2. + <_> + 9 16 6 5 2. + <_> + + <_> + 3 6 14 6 -1. + <_> + 3 8 14 2 3. + <_> + + <_> + 4 2 17 8 -1. + <_> + 4 6 17 4 2. + <_> + + <_> + 6 2 12 21 -1. + <_> + 6 9 12 7 3. + <_> + + <_> + 8 1 9 9 -1. + <_> + 8 4 9 3 3. + <_> + + <_> + 0 7 24 3 -1. + <_> + 12 7 12 3 2. + <_> + + <_> + 11 6 9 10 -1. + <_> + 11 11 9 5 2. + <_> + + <_> + 2 11 18 3 -1. + <_> + 2 12 18 1 3. + <_> + + <_> + 8 16 9 4 -1. + <_> + 8 18 9 2 2. + <_> + + <_> + 0 0 9 6 -1. + <_> + 0 2 9 2 3. + <_> + + <_> + 0 11 24 6 -1. + <_> + 0 13 24 2 3. + <_> + + <_> + 2 9 20 6 -1. + <_> + 2 12 20 3 2. + <_> + + <_> + 4 5 16 12 -1. + <_> + 12 5 8 6 2. + <_> + 4 11 8 6 2. + <_> + + <_> + 10 2 4 15 -1. + <_> + 10 7 4 5 3. + <_> + + <_> + 7 3 10 4 -1. + <_> + 7 5 10 2 2. + <_> + + <_> + 9 15 6 8 -1. + <_> + 9 19 6 4 2. + <_> + + <_> + 17 0 7 10 -1. + <_> + 17 5 7 5 2. + <_> + + <_> + 0 0 7 10 -1. + <_> + 0 5 7 5 2. + <_> + + <_> + 16 1 6 12 -1. + <_> + 19 1 3 6 2. + <_> + 16 7 3 6 2. + <_> + + <_> + 1 0 19 8 -1. + <_> + 1 4 19 4 2. + <_> + + <_> + 12 2 9 4 -1. + <_> + 12 4 9 2 2. + <_> + + <_> + 3 2 9 4 -1. + <_> + 3 4 9 2 2. + <_> + + <_> + 12 2 10 6 -1. + <_> + 12 4 10 2 3. + <_> + + <_> + 3 4 18 2 -1. + <_> + 12 4 9 2 2. + <_> + + <_> + 12 1 4 9 -1. + <_> + 12 1 2 9 2. + <_> + + <_> + 8 1 4 9 -1. + <_> + 10 1 2 9 2. + <_> + + <_> + 10 5 8 10 -1. + <_> + 14 5 4 5 2. + <_> + 10 10 4 5 2. + <_> + + <_> + 6 4 12 13 -1. + <_> + 10 4 4 13 3. + <_> + + <_> + 13 5 6 6 -1. + <_> + 13 5 3 6 2. + <_> + + <_> + 1 5 12 3 -1. + <_> + 7 5 6 3 2. + <_> + + <_> + 7 5 10 6 -1. + <_> + 7 7 10 2 3. + <_> + + <_> + 2 0 21 5 -1. + <_> + 9 0 7 5 3. + <_> + + <_> + 0 8 9 9 -1. + <_> + 0 11 9 3 3. + <_> + + <_> + 9 6 6 9 -1. + <_> + 11 6 2 9 3. + <_> + + <_> + 0 3 6 7 -1. + <_> + 3 3 3 7 2. + <_> + + <_> + 9 18 12 6 -1. + <_> + 15 18 6 3 2. + <_> + 9 21 6 3 2. + <_> + + <_> + 2 8 20 6 -1. + <_> + 2 8 10 3 2. + <_> + 12 11 10 3 2. + <_> + + <_> + 13 2 10 4 -1. + <_> + 13 4 10 2 2. + <_> + + <_> + 4 5 5 18 -1. + <_> + 4 11 5 6 3. + <_> + + <_> + 20 4 4 9 -1. + <_> + 20 4 2 9 2. + <_> + + <_> + 8 6 8 14 -1. + <_> + 8 13 8 7 2. + <_> + + <_> + 0 1 24 6 -1. + <_> + 12 1 12 3 2. + <_> + 0 4 12 3 2. + <_> + + <_> + 0 4 4 9 -1. + <_> + 2 4 2 9 2. + <_> + + <_> + 3 6 18 3 -1. + <_> + 3 7 18 1 3. + <_> + + <_> + 3 17 16 6 -1. + <_> + 3 19 16 2 3. + <_> + + <_> + 13 6 6 9 -1. + <_> + 13 9 6 3 3. + <_> + + <_> + 5 6 14 6 -1. + <_> + 5 6 7 3 2. + <_> + 12 9 7 3 2. + <_> + + <_> + 13 5 8 10 -1. + <_> + 17 5 4 5 2. + <_> + 13 10 4 5 2. + <_> + + <_> + 2 2 20 3 -1. + <_> + 2 3 20 1 3. + <_> + + <_> + 9 2 9 6 -1. + <_> + 12 2 3 6 3. + <_> + + <_> + 8 6 6 9 -1. + <_> + 10 6 2 9 3. + <_> + + <_> + 12 3 4 11 -1. + <_> + 12 3 2 11 2. + <_> + + <_> + 8 3 4 11 -1. + <_> + 10 3 2 11 2. + <_> + + <_> + 8 3 8 10 -1. + <_> + 12 3 4 5 2. + <_> + 8 8 4 5 2. + <_> + + <_> + 11 1 2 18 -1. + <_> + 12 1 1 18 2. + <_> + + <_> + 9 2 9 6 -1. + <_> + 12 2 3 6 3. + <_> + + <_> + 0 2 19 3 -1. + <_> + 0 3 19 1 3. + <_> + + <_> + 9 14 9 6 -1. + <_> + 9 16 9 2 3. + <_> + + <_> + 1 8 18 5 -1. + <_> + 7 8 6 5 3. + <_> + + <_> + 12 0 6 9 -1. + <_> + 14 0 2 9 3. + <_> + + <_> + 6 0 6 9 -1. + <_> + 8 0 2 9 3. + <_> + + <_> + 13 6 4 15 -1. + <_> + 13 11 4 5 3. + <_> + + <_> + 1 5 18 3 -1. + <_> + 1 6 18 1 3. + <_> + + <_> + 9 7 14 6 -1. + <_> + 9 9 14 2 3. + <_> + + <_> + 2 16 18 3 -1. + <_> + 2 17 18 1 3. + <_> + + <_> + 15 17 9 6 -1. + <_> + 15 19 9 2 3. + <_> + + <_> + 0 8 12 6 -1. + <_> + 0 8 6 3 2. + <_> + 6 11 6 3 2. + <_> + + <_> + 9 13 7 8 -1. + <_> + 9 17 7 4 2. + <_> + + <_> + 2 17 20 3 -1. + <_> + 2 18 20 1 3. + <_> + + <_> + 15 17 9 6 -1. + <_> + 15 19 9 2 3. + <_> + + <_> + 4 0 15 4 -1. + <_> + 4 2 15 2 2. + <_> + + <_> + 17 2 6 6 -1. + <_> + 17 5 6 3 2. + <_> + + <_> + 0 3 6 9 -1. + <_> + 0 6 6 3 3. + <_> + + <_> + 15 17 9 6 -1. + <_> + 15 19 9 2 3. + <_> + + <_> + 0 17 9 6 -1. + <_> + 0 19 9 2 3. + <_> + + <_> + 9 18 12 6 -1. + <_> + 15 18 6 3 2. + <_> + 9 21 6 3 2. + <_> + + <_> + 3 15 6 9 -1. + <_> + 3 18 6 3 3. + <_> + + <_> + 16 13 8 10 -1. + <_> + 20 13 4 5 2. + <_> + 16 18 4 5 2. + <_> + + <_> + 0 14 24 4 -1. + <_> + 8 14 8 4 3. + <_> + + <_> + 13 18 6 6 -1. + <_> + 13 18 3 6 2. + <_> + + <_> + 0 13 8 10 -1. + <_> + 0 13 4 5 2. + <_> + 4 18 4 5 2. + <_> + + <_> + 0 14 24 6 -1. + <_> + 0 17 24 3 2. + <_> + + <_> + 5 2 12 8 -1. + <_> + 5 2 6 4 2. + <_> + 11 6 6 4 2. + <_> + + <_> + 8 9 9 6 -1. + <_> + 11 9 3 6 3. + <_> + + <_> + 4 3 16 4 -1. + <_> + 4 5 16 2 2. + <_> + + <_> + 10 2 4 10 -1. + <_> + 10 7 4 5 2. + <_> + + <_> + 8 4 5 8 -1. + <_> + 8 8 5 4 2. + <_> + + <_> + 11 5 9 12 -1. + <_> + 11 9 9 4 3. + <_> + + <_> + 4 5 9 12 -1. + <_> + 4 9 9 4 3. + <_> + + <_> + 14 6 6 9 -1. + <_> + 14 9 6 3 3. + <_> + + <_> + 2 4 20 12 -1. + <_> + 2 8 20 4 3. + <_> + + <_> + 4 4 17 16 -1. + <_> + 4 12 17 8 2. + <_> + + <_> + 8 7 7 6 -1. + <_> + 8 10 7 3 2. + <_> + + <_> + 1 9 23 2 -1. + <_> + 1 10 23 1 2. + <_> + + <_> + 7 0 6 9 -1. + <_> + 9 0 2 9 3. + <_> + + <_> + 13 3 4 9 -1. + <_> + 13 3 2 9 2. + <_> + + <_> + 8 1 6 13 -1. + <_> + 10 1 2 13 3. + <_> + + <_> + 4 22 18 2 -1. + <_> + 4 23 18 1 2. + <_> + + <_> + 3 10 9 6 -1. + <_> + 6 10 3 6 3. + <_> + + <_> + 14 0 2 24 -1. + <_> + 14 0 1 24 2. + <_> + + <_> + 8 0 2 24 -1. + <_> + 9 0 1 24 2. + <_> + + <_> + 3 2 18 10 -1. + <_> + 9 2 6 10 3. + <_> + + <_> + 4 13 15 6 -1. + <_> + 9 13 5 6 3. + <_> + + <_> + 3 21 18 3 -1. + <_> + 9 21 6 3 3. + <_> + + <_> + 9 1 4 11 -1. + <_> + 11 1 2 11 2. + <_> + + <_> + 9 7 10 4 -1. + <_> + 9 7 5 4 2. + <_> + + <_> + 7 0 10 18 -1. + <_> + 12 0 5 18 2. + <_> + + <_> + 12 1 6 16 -1. + <_> + 14 1 2 16 3. + <_> + + <_> + 6 1 6 16 -1. + <_> + 8 1 2 16 3. + <_> + + <_> + 18 2 6 6 -1. + <_> + 18 5 6 3 2. + <_> + + <_> + 3 5 18 2 -1. + <_> + 3 6 18 1 2. + <_> + + <_> + 18 2 6 6 -1. + <_> + 18 5 6 3 2. + <_> + + <_> + 0 2 6 6 -1. + <_> + 0 5 6 3 2. + <_> + + <_> + 13 11 11 6 -1. + <_> + 13 13 11 2 3. + <_> + + <_> + 5 7 10 4 -1. + <_> + 10 7 5 4 2. + <_> + + <_> + 11 9 10 7 -1. + <_> + 11 9 5 7 2. + <_> + + <_> + 3 9 10 7 -1. + <_> + 8 9 5 7 2. + <_> + + <_> + 16 4 6 6 -1. + <_> + 16 4 3 6 2. + <_> + + <_> + 5 6 10 8 -1. + <_> + 5 6 5 4 2. + <_> + 10 10 5 4 2. + <_> + + <_> + 7 21 16 3 -1. + <_> + 7 21 8 3 2. + <_> + + <_> + 1 21 16 3 -1. + <_> + 9 21 8 3 2. + <_> + + <_> + 2 5 22 14 -1. + <_> + 13 5 11 7 2. + <_> + 2 12 11 7 2. + <_> + + <_> + 3 10 8 10 -1. + <_> + 3 10 4 5 2. + <_> + 7 15 4 5 2. + <_> + + <_> + 17 0 6 12 -1. + <_> + 20 0 3 6 2. + <_> + 17 6 3 6 2. + <_> + + <_> + 5 2 6 18 -1. + <_> + 7 2 2 18 3. + <_> + + <_> + 13 0 6 9 -1. + <_> + 15 0 2 9 3. + <_> + + <_> + 0 12 7 9 -1. + <_> + 0 15 7 3 3. + <_> + + <_> + 15 13 8 10 -1. + <_> + 19 13 4 5 2. + <_> + 15 18 4 5 2. + <_> + + <_> + 1 0 6 12 -1. + <_> + 1 0 3 6 2. + <_> + 4 6 3 6 2. + <_> + + <_> + 12 1 3 12 -1. + <_> + 12 7 3 6 2. + <_> + + <_> + 1 13 8 10 -1. + <_> + 1 13 4 5 2. + <_> + 5 18 4 5 2. + <_> + + <_> + 3 21 19 2 -1. + <_> + 3 22 19 1 2. + <_> + + <_> + 6 3 4 13 -1. + <_> + 8 3 2 13 2. + <_> + + <_> + 5 10 18 3 -1. + <_> + 5 11 18 1 3. + <_> + + <_> + 9 3 5 12 -1. + <_> + 9 7 5 4 3. + <_> + + <_> + 11 2 4 15 -1. + <_> + 11 7 4 5 3. + <_> + + <_> + 4 1 16 4 -1. + <_> + 4 3 16 2 2. + <_> + + <_> + 6 0 18 3 -1. + <_> + 6 1 18 1 3. + <_> + + <_> + 5 1 10 8 -1. + <_> + 5 1 5 4 2. + <_> + 10 5 5 4 2. + <_> + + <_> + 11 18 12 6 -1. + <_> + 17 18 6 3 2. + <_> + 11 21 6 3 2. + <_> + + <_> + 5 15 12 3 -1. + <_> + 11 15 6 3 2. + <_> + + <_> + 1 10 22 4 -1. + <_> + 1 10 11 4 2. + <_> + + <_> + 7 9 9 6 -1. + <_> + 10 9 3 6 3. + <_> + + <_> + 6 11 12 5 -1. + <_> + 10 11 4 5 3. + <_> + + <_> + 6 7 10 7 -1. + <_> + 11 7 5 7 2. + <_> + + <_> + 11 2 8 10 -1. + <_> + 11 2 4 10 2. + <_> + + <_> + 5 2 8 10 -1. + <_> + 9 2 4 10 2. + <_> + + <_> + 6 4 18 6 -1. + <_> + 15 4 9 3 2. + <_> + 6 7 9 3 2. + <_> + + <_> + 0 5 10 9 -1. + <_> + 0 8 10 3 3. + <_> + + <_> + 2 7 21 6 -1. + <_> + 2 9 21 2 3. + <_> + + <_> + 0 4 22 16 -1. + <_> + 0 4 11 8 2. + <_> + 11 12 11 8 2. + <_> + + <_> + 9 0 6 22 -1. + <_> + 9 11 6 11 2. + <_> + + <_> + 9 1 3 12 -1. + <_> + 9 7 3 6 2. + <_> + + <_> + 12 0 12 18 -1. + <_> + 18 0 6 9 2. + <_> + 12 9 6 9 2. + <_> + + <_> + 0 0 12 18 -1. + <_> + 0 0 6 9 2. + <_> + 6 9 6 9 2. + <_> + + <_> + 1 1 22 4 -1. + <_> + 12 1 11 2 2. + <_> + 1 3 11 2 2. + <_> + + <_> + 3 0 18 4 -1. + <_> + 3 2 18 2 2. + <_> + + <_> + 2 5 22 6 -1. + <_> + 2 7 22 2 3. + <_> + + <_> + 5 0 6 9 -1. + <_> + 5 3 6 3 3. + <_> + + <_> + 10 14 6 9 -1. + <_> + 12 14 2 9 3. + <_> + + <_> + 8 14 6 9 -1. + <_> + 10 14 2 9 3. + <_> + + <_> + 5 18 18 3 -1. + <_> + 5 19 18 1 3. + <_> + + <_> + 6 0 6 13 -1. + <_> + 9 0 3 13 2. + <_> + + <_> + 7 4 12 4 -1. + <_> + 7 4 6 4 2. + <_> + + <_> + 5 2 12 6 -1. + <_> + 9 2 4 6 3. + <_> + + <_> + 4 1 18 3 -1. + <_> + 4 2 18 1 3. + <_> + + <_> + 0 8 6 12 -1. + <_> + 0 12 6 4 3. + <_> + + <_> + 9 15 6 9 -1. + <_> + 11 15 2 9 3. + <_> + + <_> + 9 10 6 13 -1. + <_> + 11 10 2 13 3. + <_> + + <_> + 6 17 18 2 -1. + <_> + 6 18 18 1 2. + <_> + + <_> + 9 4 6 9 -1. + <_> + 11 4 2 9 3. + <_> + + <_> + 10 0 6 9 -1. + <_> + 12 0 2 9 3. + <_> + + <_> + 5 6 10 8 -1. + <_> + 5 6 5 4 2. + <_> + 10 10 5 4 2. + <_> + + <_> + 14 9 5 8 -1. + <_> + 14 13 5 4 2. + <_> + + <_> + 5 9 5 8 -1. + <_> + 5 13 5 4 2. + <_> + + <_> + 14 11 9 6 -1. + <_> + 14 13 9 2 3. + <_> + + <_> + 0 2 23 15 -1. + <_> + 0 7 23 5 3. + <_> + + <_> + 16 0 8 12 -1. + <_> + 16 6 8 6 2. + <_> + + <_> + 4 15 6 9 -1. + <_> + 4 18 6 3 3. + <_> + + <_> + 8 18 9 4 -1. + <_> + 8 20 9 2 2. + <_> + + <_> + 0 17 18 3 -1. + <_> + 0 18 18 1 3. + <_> + + <_> + 13 11 11 6 -1. + <_> + 13 13 11 2 3. + <_> + + <_> + 0 11 11 6 -1. + <_> + 0 13 11 2 3. + <_> + + <_> + 0 9 24 6 -1. + <_> + 12 9 12 3 2. + <_> + 0 12 12 3 2. + <_> + + <_> + 6 16 8 8 -1. + <_> + 6 20 8 4 2. + <_> + + <_> + 10 16 14 6 -1. + <_> + 10 18 14 2 3. + <_> + + <_> + 1 1 21 3 -1. + <_> + 1 2 21 1 3. + <_> + + <_> + 0 2 24 3 -1. + <_> + 0 2 12 3 2. + <_> + + <_> + 2 15 8 5 -1. + <_> + 6 15 4 5 2. + <_> + + <_> + 2 11 21 3 -1. + <_> + 9 11 7 3 3. + <_> + + <_> + 1 18 12 6 -1. + <_> + 1 18 6 3 2. + <_> + 7 21 6 3 2. + <_> + + <_> + 10 14 4 10 -1. + <_> + 10 19 4 5 2. + <_> + + <_> + 7 7 4 10 -1. + <_> + 7 12 4 5 2. + <_> + + <_> + 9 8 6 12 -1. + <_> + 9 12 6 4 3. + <_> + + <_> + 7 1 9 6 -1. + <_> + 10 1 3 6 3. + <_> + + <_> + 3 14 19 2 -1. + <_> + 3 15 19 1 2. + <_> + + <_> + 7 7 10 10 -1. + <_> + 7 7 5 5 2. + <_> + 12 12 5 5 2. + <_> + + <_> + 3 12 18 12 -1. + <_> + 3 12 9 12 2. + <_> + + <_> + 8 0 6 12 -1. + <_> + 10 0 2 12 3. + <_> + + <_> + 3 0 17 9 -1. + <_> + 3 3 17 3 3. + <_> + + <_> + 6 0 12 11 -1. + <_> + 10 0 4 11 3. + <_> + + <_> + 1 0 6 13 -1. + <_> + 4 0 3 13 2. + <_> + + <_> + 5 8 16 6 -1. + <_> + 5 11 16 3 2. + <_> + + <_> + 8 8 5 12 -1. + <_> + 8 14 5 6 2. + <_> + + <_> + 3 21 18 3 -1. + <_> + 9 21 6 3 3. + <_> + + <_> + 0 0 6 6 -1. + <_> + 3 0 3 6 2. + <_> + + <_> + 2 0 20 3 -1. + <_> + 2 1 20 1 3. + <_> + + <_> + 4 6 15 10 -1. + <_> + 9 6 5 10 3. + <_> + + <_> + 9 6 6 9 -1. + <_> + 11 6 2 9 3. + <_> + + <_> + 9 0 6 9 -1. + <_> + 11 0 2 9 3. + <_> + + <_> + 14 0 6 9 -1. + <_> + 16 0 2 9 3. + <_> + + <_> + 7 16 9 6 -1. + <_> + 7 18 9 2 3. + <_> + + <_> + 14 0 6 9 -1. + <_> + 16 0 2 9 3. + <_> + + <_> + 4 0 6 9 -1. + <_> + 6 0 2 9 3. + <_> + + <_> + 17 1 6 16 -1. + <_> + 19 1 2 16 3. + <_> + + <_> + 1 1 6 16 -1. + <_> + 3 1 2 16 3. + <_> + + <_> + 14 13 6 9 -1. + <_> + 14 16 6 3 3. + <_> + + <_> + 0 0 6 9 -1. + <_> + 0 3 6 3 3. + <_> + + <_> + 9 5 6 6 -1. + <_> + 9 5 3 6 2. + <_> + + <_> + 3 10 9 6 -1. + <_> + 6 10 3 6 3. + <_> + + <_> + 14 7 3 16 -1. + <_> + 14 15 3 8 2. + <_> + + <_> + 4 10 14 12 -1. + <_> + 4 10 7 6 2. + <_> + 11 16 7 6 2. + <_> + + <_> + 7 6 12 6 -1. + <_> + 7 8 12 2 3. + <_> + + <_> + 7 2 4 20 -1. + <_> + 9 2 2 20 2. + <_> + + <_> + 14 13 6 9 -1. + <_> + 14 16 6 3 3. + <_> + + <_> + 10 6 4 9 -1. + <_> + 12 6 2 9 2. + <_> + + <_> + 14 13 6 9 -1. + <_> + 14 16 6 3 3. + <_> + + <_> + 5 20 14 4 -1. + <_> + 5 22 14 2 2. + <_> + + <_> + 4 4 16 12 -1. + <_> + 4 10 16 6 2. + <_> + + <_> + 9 6 6 9 -1. + <_> + 11 6 2 9 3. + <_> + + <_> + 3 0 21 4 -1. + <_> + 3 2 21 2 2. + <_> + + <_> + 4 13 6 9 -1. + <_> + 4 16 6 3 3. + <_> + + <_> + 16 16 5 8 -1. + <_> + 16 20 5 4 2. + <_> + + <_> + 4 0 16 16 -1. + <_> + 4 0 8 8 2. + <_> + 12 8 8 8 2. + <_> + + <_> + 6 6 14 6 -1. + <_> + 13 6 7 3 2. + <_> + 6 9 7 3 2. + <_> + + <_> + 10 5 4 15 -1. + <_> + 10 10 4 5 3. + <_> + + <_> + 9 15 12 8 -1. + <_> + 15 15 6 4 2. + <_> + 9 19 6 4 2. + <_> + + <_> + 6 7 12 4 -1. + <_> + 12 7 6 4 2. + <_> + + <_> + 5 6 14 6 -1. + <_> + 12 6 7 3 2. + <_> + 5 9 7 3 2. + <_> + + <_> + 3 6 18 10 -1. + <_> + 3 6 9 5 2. + <_> + 12 11 9 5 2. + <_> + + <_> + 6 0 18 21 -1. + <_> + 12 0 6 21 3. + <_> + + <_> + 0 0 24 21 -1. + <_> + 8 0 8 21 3. + <_> + + <_> + 6 18 18 3 -1. + <_> + 6 19 18 1 3. + <_> + + <_> + 0 15 9 6 -1. + <_> + 0 17 9 2 3. + <_> + + <_> + 4 3 19 2 -1. + <_> + 4 4 19 1 2. + <_> + + <_> + 0 3 24 2 -1. + <_> + 0 4 24 1 2. + <_> + + <_> + 15 14 9 4 -1. + <_> + 15 16 9 2 2. + <_> + + <_> + 0 14 9 4 -1. + <_> + 0 16 9 2 2. + <_> + + <_> + 6 15 18 2 -1. + <_> + 6 16 18 1 2. + <_> + + <_> + 3 17 18 3 -1. + <_> + 3 18 18 1 3. + <_> + + <_> + 12 0 3 23 -1. + <_> + 13 0 1 23 3. + <_> + + <_> + 6 0 8 6 -1. + <_> + 6 3 8 3 2. + <_> + + <_> + 6 16 18 3 -1. + <_> + 6 17 18 1 3. + <_> + + <_> + 9 0 3 23 -1. + <_> + 10 0 1 23 3. + <_> + + <_> + 10 7 4 10 -1. + <_> + 10 12 4 5 2. + <_> + + <_> + 7 8 10 12 -1. + <_> + 7 12 10 4 3. + <_> + + <_> + 14 9 6 14 -1. + <_> + 17 9 3 7 2. + <_> + 14 16 3 7 2. + <_> + + <_> + 2 0 10 9 -1. + <_> + 2 3 10 3 3. + <_> + + <_> + 11 1 5 12 -1. + <_> + 11 7 5 6 2. + <_> + + <_> + 1 4 12 10 -1. + <_> + 1 4 6 5 2. + <_> + 7 9 6 5 2. + <_> + + <_> + 15 1 9 4 -1. + <_> + 15 3 9 2 2. + <_> + + <_> + 1 2 8 10 -1. + <_> + 1 2 4 5 2. + <_> + 5 7 4 5 2. + <_> + + <_> + 10 1 5 12 -1. + <_> + 10 5 5 4 3. + <_> + + <_> + 4 0 14 24 -1. + <_> + 11 0 7 24 2. + <_> + + <_> + 7 17 10 4 -1. + <_> + 7 19 10 2 2. + <_> + + <_> + 10 14 4 10 -1. + <_> + 10 19 4 5 2. + <_> + + <_> + 13 15 6 9 -1. + <_> + 15 15 2 9 3. + <_> + + <_> + 3 21 18 3 -1. + <_> + 3 22 18 1 3. + <_> + + <_> + 13 15 6 9 -1. + <_> + 15 15 2 9 3. + <_> + + <_> + 5 15 6 9 -1. + <_> + 7 15 2 9 3. + <_> + + <_> + 10 6 4 18 -1. + <_> + 12 6 2 9 2. + <_> + 10 15 2 9 2. + <_> + + <_> + 7 3 6 11 -1. + <_> + 9 3 2 11 3. + <_> + + <_> + 15 1 9 4 -1. + <_> + 15 3 9 2 2. + <_> + + <_> + 5 4 14 8 -1. + <_> + 5 8 14 4 2. + <_> + + <_> + 8 1 15 9 -1. + <_> + 8 4 15 3 3. + <_> + + <_> + 7 2 8 10 -1. + <_> + 7 2 4 5 2. + <_> + 11 7 4 5 2. + <_> + + <_> + 12 2 6 12 -1. + <_> + 12 2 3 12 2. + <_> + + <_> + 6 2 6 12 -1. + <_> + 9 2 3 12 2. + <_> + + <_> + 7 7 12 4 -1. + <_> + 7 7 6 4 2. + <_> + + <_> + 6 3 12 10 -1. + <_> + 10 3 4 10 3. + <_> + + <_> + 5 6 16 6 -1. + <_> + 13 6 8 3 2. + <_> + 5 9 8 3 2. + <_> + + <_> + 3 1 18 9 -1. + <_> + 9 1 6 9 3. + <_> + + <_> + 3 8 18 5 -1. + <_> + 9 8 6 5 3. + <_> + + <_> + 0 0 24 22 -1. + <_> + 0 0 12 11 2. + <_> + 12 11 12 11 2. + <_> + + <_> + 14 16 9 6 -1. + <_> + 14 18 9 2 3. + <_> + + <_> + 0 16 24 8 -1. + <_> + 0 20 24 4 2. + <_> + + <_> + 1 19 22 4 -1. + <_> + 12 19 11 2 2. + <_> + 1 21 11 2 2. + <_> + + <_> + 1 16 9 6 -1. + <_> + 1 18 9 2 3. + <_> + + <_> + 7 8 10 4 -1. + <_> + 7 8 5 4 2. + <_> + + <_> + 9 15 6 9 -1. + <_> + 11 15 2 9 3. + <_> + + <_> + 10 18 12 6 -1. + <_> + 16 18 6 3 2. + <_> + 10 21 6 3 2. + <_> + + <_> + 2 18 12 6 -1. + <_> + 2 18 6 3 2. + <_> + 8 21 6 3 2. + <_> + + <_> + 8 3 16 9 -1. + <_> + 8 6 16 3 3. + <_> + + <_> + 0 5 10 6 -1. + <_> + 0 7 10 2 3. + <_> + + <_> + 5 5 18 3 -1. + <_> + 5 6 18 1 3. + <_> + + <_> + 2 6 9 6 -1. + <_> + 2 9 9 3 2. + <_> + + <_> + 14 2 10 9 -1. + <_> + 14 5 10 3 3. + <_> + + <_> + 3 6 18 3 -1. + <_> + 3 7 18 1 3. + <_> + + <_> + 9 2 15 6 -1. + <_> + 9 4 15 2 3. + <_> + + <_> + 4 8 15 6 -1. + <_> + 4 10 15 2 3. + <_> + + <_> + 0 5 24 4 -1. + <_> + 12 5 12 2 2. + <_> + 0 7 12 2 2. + <_> + + <_> + 7 8 6 12 -1. + <_> + 9 8 2 12 3. + <_> + + <_> + 11 0 6 9 -1. + <_> + 13 0 2 9 3. + <_> + + <_> + 0 12 6 12 -1. + <_> + 0 12 3 6 2. + <_> + 3 18 3 6 2. + <_> + + <_> + 14 12 10 6 -1. + <_> + 14 14 10 2 3. + <_> + + <_> + 2 7 18 9 -1. + <_> + 2 10 18 3 3. + <_> + + <_> + 11 14 10 9 -1. + <_> + 11 17 10 3 3. + <_> + + <_> + 7 6 10 8 -1. + <_> + 7 6 5 4 2. + <_> + 12 10 5 4 2. + <_> + + <_> + 6 6 14 6 -1. + <_> + 13 6 7 3 2. + <_> + 6 9 7 3 2. + <_> + + <_> + 4 13 9 7 -1. + <_> + 7 13 3 7 3. + <_> + + <_> + 14 10 6 12 -1. + <_> + 17 10 3 6 2. + <_> + 14 16 3 6 2. + <_> + + <_> + 4 10 6 12 -1. + <_> + 4 10 3 6 2. + <_> + 7 16 3 6 2. + <_> + + <_> + 13 9 8 6 -1. + <_> + 13 9 4 6 2. + <_> + + <_> + 8 3 4 14 -1. + <_> + 10 3 2 14 2. + <_> + + <_> + 17 0 3 18 -1. + <_> + 18 0 1 18 3. + <_> + + <_> + 4 12 16 12 -1. + <_> + 12 12 8 12 2. + <_> + + <_> + 15 0 6 14 -1. + <_> + 17 0 2 14 3. + <_> + + <_> + 3 0 6 14 -1. + <_> + 5 0 2 14 3. + <_> + + <_> + 12 2 12 20 -1. + <_> + 16 2 4 20 3. + <_> + + <_> + 0 2 12 20 -1. + <_> + 4 2 4 20 3. + <_> + + <_> + 16 0 6 17 -1. + <_> + 18 0 2 17 3. + <_> + + <_> + 2 0 6 17 -1. + <_> + 4 0 2 17 3. + <_> + + <_> + 15 6 9 6 -1. + <_> + 15 8 9 2 3. + <_> + + <_> + 0 6 9 6 -1. + <_> + 0 8 9 2 3. + <_> + + <_> + 18 1 6 13 -1. + <_> + 20 1 2 13 3. + <_> + + <_> + 0 1 6 13 -1. + <_> + 2 1 2 13 3. + <_> + + <_> + 16 0 4 9 -1. + <_> + 16 0 2 9 2. + <_> + + <_> + 5 10 12 7 -1. + <_> + 9 10 4 7 3. + <_> + + <_> + 12 9 12 6 -1. + <_> + 12 11 12 2 3. + <_> + + <_> + 0 9 12 6 -1. + <_> + 0 11 12 2 3. + <_> + + <_> + 5 7 14 9 -1. + <_> + 5 10 14 3 3. + <_> + + <_> + 0 15 20 3 -1. + <_> + 0 16 20 1 3. + <_> + + <_> + 8 10 8 10 -1. + <_> + 12 10 4 5 2. + <_> + 8 15 4 5 2. + <_> + + <_> + 5 4 13 9 -1. + <_> + 5 7 13 3 3. + <_> + + <_> + 10 2 6 18 -1. + <_> + 10 8 6 6 3. + <_> + + <_> + 6 0 6 9 -1. + <_> + 8 0 2 9 3. + <_> + + <_> + 6 9 12 4 -1. + <_> + 6 11 12 2 2. + <_> + + <_> + 3 2 15 12 -1. + <_> + 3 6 15 4 3. + <_> + + <_> + 12 0 12 5 -1. + <_> + 16 0 4 5 3. + <_> + + <_> + 0 15 18 3 -1. + <_> + 6 15 6 3 3. + <_> + + <_> + 0 14 24 5 -1. + <_> + 8 14 8 5 3. + <_> + + <_> + 5 1 3 18 -1. + <_> + 6 1 1 18 3. + <_> + + <_> + 10 0 4 14 -1. + <_> + 10 0 2 14 2. + <_> + + <_> + 9 3 4 9 -1. + <_> + 11 3 2 9 2. + <_> + + <_> + 8 2 12 6 -1. + <_> + 14 2 6 3 2. + <_> + 8 5 6 3 2. + <_> + + <_> + 0 4 17 4 -1. + <_> + 0 6 17 2 2. + <_> + + <_> + 16 16 5 8 -1. + <_> + 16 20 5 4 2. + <_> + + <_> + 3 16 5 8 -1. + <_> + 3 20 5 4 2. + <_> + + <_> + 6 18 18 2 -1. + <_> + 6 19 18 1 2. + <_> + + <_> + 0 0 12 5 -1. + <_> + 4 0 4 5 3. + <_> + + <_> + 14 3 6 12 -1. + <_> + 17 3 3 6 2. + <_> + 14 9 3 6 2. + <_> + + <_> + 0 12 6 12 -1. + <_> + 2 12 2 12 3. + <_> + + <_> + 2 3 21 3 -1. + <_> + 2 4 21 1 3. + <_> + + <_> + 4 3 6 12 -1. + <_> + 4 3 3 6 2. + <_> + 7 9 3 6 2. + <_> + + <_> + 12 8 12 6 -1. + <_> + 18 8 6 3 2. + <_> + 12 11 6 3 2. + <_> + + <_> + 0 15 16 9 -1. + <_> + 8 15 8 9 2. + <_> + + <_> + 6 13 18 5 -1. + <_> + 6 13 9 5 2. + <_> + + <_> + 1 6 15 6 -1. + <_> + 6 6 5 6 3. + <_> + + <_> + 11 9 9 6 -1. + <_> + 14 9 3 6 3. + <_> + + <_> + 3 0 15 11 -1. + <_> + 8 0 5 11 3. + <_> + + <_> + 15 3 3 18 -1. + <_> + 15 9 3 6 3. + <_> + + <_> + 6 3 3 18 -1. + <_> + 6 9 3 6 3. + <_> + + <_> + 9 5 10 8 -1. + <_> + 14 5 5 4 2. + <_> + 9 9 5 4 2. + <_> + + <_> + 4 4 16 8 -1. + <_> + 4 4 8 4 2. + <_> + 12 8 8 4 2. + <_> + + <_> + 7 7 12 3 -1. + <_> + 7 7 6 3 2. + <_> + + <_> + 5 0 9 13 -1. + <_> + 8 0 3 13 3. + <_> + + <_> + 11 0 6 9 -1. + <_> + 13 0 2 9 3. + <_> + + <_> + 7 0 6 9 -1. + <_> + 9 0 2 9 3. + <_> + + <_> + 8 1 10 9 -1. + <_> + 8 4 10 3 3. + <_> + + <_> + 0 2 18 2 -1. + <_> + 0 3 18 1 2. + <_> + + <_> + 10 13 14 6 -1. + <_> + 17 13 7 3 2. + <_> + 10 16 7 3 2. + <_> + + <_> + 0 13 14 6 -1. + <_> + 0 13 7 3 2. + <_> + 7 16 7 3 2. + <_> + + <_> + 20 2 3 21 -1. + <_> + 21 2 1 21 3. + <_> + + <_> + 0 9 5 12 -1. + <_> + 0 13 5 4 3. + <_> + + <_> + 12 6 12 6 -1. + <_> + 12 8 12 2 3. + <_> + + <_> + 1 8 20 3 -1. + <_> + 1 9 20 1 3. + <_> + + <_> + 5 7 19 3 -1. + <_> + 5 8 19 1 3. + <_> + + <_> + 1 12 9 6 -1. + <_> + 1 14 9 2 3. + <_> + + <_> + 6 10 14 12 -1. + <_> + 6 14 14 4 3. + <_> + + <_> + 5 6 14 18 -1. + <_> + 5 12 14 6 3. + <_> + + <_> + 11 12 9 7 -1. + <_> + 14 12 3 7 3. + <_> + + <_> + 1 15 18 4 -1. + <_> + 1 17 18 2 2. + <_> + + <_> + 11 14 6 9 -1. + <_> + 11 17 6 3 3. + <_> + + <_> + 0 8 18 4 -1. + <_> + 0 8 9 2 2. + <_> + 9 10 9 2 2. + <_> + + <_> + 3 10 20 6 -1. + <_> + 13 10 10 3 2. + <_> + 3 13 10 3 2. + <_> + + <_> + 1 10 20 6 -1. + <_> + 1 10 10 3 2. + <_> + 11 13 10 3 2. + <_> + + <_> + 0 9 24 2 -1. + <_> + 0 9 12 2 2. + <_> + + <_> + 1 12 20 8 -1. + <_> + 1 12 10 4 2. + <_> + 11 16 10 4 2. + <_> + + <_> + 11 12 9 7 -1. + <_> + 14 12 3 7 3. + <_> + + <_> + 4 12 9 7 -1. + <_> + 7 12 3 7 3. + <_> + + <_> + 12 12 8 5 -1. + <_> + 12 12 4 5 2. + <_> + + <_> + 4 12 8 5 -1. + <_> + 8 12 4 5 2. + <_> + + <_> + 13 10 4 10 -1. + <_> + 13 10 2 10 2. + <_> + + <_> + 1 15 20 2 -1. + <_> + 11 15 10 2 2. + <_> + + <_> + 9 10 6 6 -1. + <_> + 9 10 3 6 2. + <_> + + <_> + 0 1 21 3 -1. + <_> + 7 1 7 3 3. + <_> + + <_> + 6 4 13 9 -1. + <_> + 6 7 13 3 3. + <_> + + <_> + 6 5 12 5 -1. + <_> + 10 5 4 5 3. + <_> + + <_> + 10 10 10 6 -1. + <_> + 10 12 10 2 3. + <_> + + <_> + 6 12 5 8 -1. + <_> + 6 16 5 4 2. + <_> + + <_> + 13 0 6 9 -1. + <_> + 15 0 2 9 3. + <_> + + <_> + 2 10 18 6 -1. + <_> + 8 10 6 6 3. + <_> + + <_> + 11 2 9 4 -1. + <_> + 11 4 9 2 2. + <_> + + <_> + 1 20 21 3 -1. + <_> + 8 20 7 3 3. + <_> + + <_> + 1 10 22 2 -1. + <_> + 1 11 22 1 2. + <_> + + <_> + 0 17 18 3 -1. + <_> + 0 18 18 1 3. + <_> + + <_> + 13 0 6 9 -1. + <_> + 15 0 2 9 3. + <_> + + <_> + 5 0 6 9 -1. + <_> + 7 0 2 9 3. + <_> + + <_> + 18 2 6 20 -1. + <_> + 20 2 2 20 3. + <_> + + <_> + 0 2 6 20 -1. + <_> + 2 2 2 20 3. + <_> + + <_> + 11 7 6 14 -1. + <_> + 14 7 3 7 2. + <_> + 11 14 3 7 2. + <_> + + <_> + 0 1 4 9 -1. + <_> + 2 1 2 9 2. + <_> + + <_> + 12 14 9 4 -1. + <_> + 12 16 9 2 2. + <_> + + <_> + 1 13 9 4 -1. + <_> + 1 15 9 2 2. + <_> + + <_> + 7 6 15 6 -1. + <_> + 7 8 15 2 3. + <_> + + <_> + 8 2 3 18 -1. + <_> + 8 8 3 6 3. + <_> + + <_> + 6 6 12 6 -1. + <_> + 12 6 6 3 2. + <_> + 6 9 6 3 2. + <_> + + <_> + 2 19 20 4 -1. + <_> + 2 19 10 2 2. + <_> + 12 21 10 2 2. + <_> + + <_> + 14 15 6 9 -1. + <_> + 14 18 6 3 3. + <_> + + <_> + 3 5 18 14 -1. + <_> + 3 5 9 7 2. + <_> + 12 12 9 7 2. + <_> + + <_> + 15 6 4 18 -1. + <_> + 17 6 2 9 2. + <_> + 15 15 2 9 2. + <_> + + <_> + 5 6 4 18 -1. + <_> + 5 6 2 9 2. + <_> + 7 15 2 9 2. + <_> + + <_> + 11 0 6 9 -1. + <_> + 13 0 2 9 3. + <_> + + <_> + 7 0 6 9 -1. + <_> + 9 0 2 9 3. + <_> + + <_> + 11 5 6 9 -1. + <_> + 13 5 2 9 3. + <_> + + <_> + 9 5 6 6 -1. + <_> + 12 5 3 6 2. + <_> + + <_> + 4 1 16 6 -1. + <_> + 12 1 8 3 2. + <_> + 4 4 8 3 2. + <_> + + <_> + 9 13 6 11 -1. + <_> + 11 13 2 11 3. + <_> + + <_> + 17 1 6 12 -1. + <_> + 20 1 3 6 2. + <_> + 17 7 3 6 2. + <_> + + <_> + 1 17 18 3 -1. + <_> + 1 18 18 1 3. + <_> + + <_> + 7 13 10 8 -1. + <_> + 7 17 10 4 2. + <_> + + <_> + 6 18 10 6 -1. + <_> + 6 20 10 2 3. + <_> + + <_> + 9 14 9 4 -1. + <_> + 9 16 9 2 2. + <_> + + <_> + 1 1 6 12 -1. + <_> + 1 1 3 6 2. + <_> + 4 7 3 6 2. + <_> + + <_> + 19 4 5 12 -1. + <_> + 19 8 5 4 3. + <_> + + <_> + 0 0 8 8 -1. + <_> + 4 0 4 8 2. + <_> + + <_> + 3 5 19 3 -1. + <_> + 3 6 19 1 3. + <_> + + <_> + 1 5 12 6 -1. + <_> + 1 5 6 3 2. + <_> + 7 8 6 3 2. + <_> + + <_> + 2 1 21 8 -1. + <_> + 9 1 7 8 3. + <_> + + <_> + 4 1 16 8 -1. + <_> + 4 5 16 4 2. + <_> + + <_> + 6 0 18 3 -1. + <_> + 6 1 18 1 3. + <_> + + <_> + 4 4 10 14 -1. + <_> + 4 11 10 7 2. + <_> + + <_> + 15 6 4 10 -1. + <_> + 15 11 4 5 2. + <_> + + <_> + 3 18 18 3 -1. + <_> + 9 18 6 3 3. + <_> + + <_> + 8 18 12 6 -1. + <_> + 12 18 4 6 3. + <_> + + <_> + 3 15 6 9 -1. + <_> + 6 15 3 9 2. + <_> + + <_> + 15 7 6 8 -1. + <_> + 15 11 6 4 2. + <_> + + <_> + 3 7 6 8 -1. + <_> + 3 11 6 4 2. + <_> + + <_> + 5 9 18 6 -1. + <_> + 14 9 9 3 2. + <_> + 5 12 9 3 2. + <_> + + <_> + 1 13 12 6 -1. + <_> + 1 15 12 2 3. + <_> + + <_> + 14 15 10 6 -1. + <_> + 14 17 10 2 3. + <_> + + <_> + 0 15 10 6 -1. + <_> + 0 17 10 2 3. + <_> + + <_> + 15 13 6 9 -1. + <_> + 15 16 6 3 3. + <_> + + <_> + 3 13 6 9 -1. + <_> + 3 16 6 3 3. + <_> + + <_> + 9 5 8 8 -1. + <_> + 9 5 4 8 2. + <_> + + <_> + 1 18 12 6 -1. + <_> + 1 18 6 3 2. + <_> + 7 21 6 3 2. + <_> + + <_> + 13 19 10 4 -1. + <_> + 13 21 10 2 2. + <_> + + <_> + 1 19 10 4 -1. + <_> + 1 21 10 2 2. + <_> + + <_> + 6 19 18 3 -1. + <_> + 6 20 18 1 3. + <_> + + <_> + 8 14 4 10 -1. + <_> + 8 19 4 5 2. + <_> + + <_> + 0 0 24 6 -1. + <_> + 0 2 24 2 3. + <_> + + <_> + 0 1 6 9 -1. + <_> + 0 4 6 3 3. + <_> + + <_> + 4 9 20 6 -1. + <_> + 14 9 10 3 2. + <_> + 4 12 10 3 2. + <_> + + <_> + 1 15 19 8 -1. + <_> + 1 19 19 4 2. + <_> + + <_> + 14 0 10 6 -1. + <_> + 14 2 10 2 3. + <_> + + <_> + 1 10 21 14 -1. + <_> + 8 10 7 14 3. + <_> + + <_> + 10 10 8 8 -1. + <_> + 10 10 4 8 2. + <_> + + <_> + 6 8 10 4 -1. + <_> + 11 8 5 4 2. + <_> + + <_> + 10 5 4 9 -1. + <_> + 10 5 2 9 2. + <_> + + <_> + 7 5 6 10 -1. + <_> + 9 5 2 10 3. + <_> + + <_> + 14 4 4 13 -1. + <_> + 14 4 2 13 2. + <_> + + <_> + 6 4 4 13 -1. + <_> + 8 4 2 13 2. + <_> + + <_> + 8 7 9 6 -1. + <_> + 11 7 3 6 3. + <_> + + <_> + 3 6 16 6 -1. + <_> + 3 6 8 3 2. + <_> + 11 9 8 3 2. + <_> + + <_> + 5 4 16 14 -1. + <_> + 13 4 8 7 2. + <_> + 5 11 8 7 2. + <_> + + <_> + 0 0 24 4 -1. + <_> + 0 0 12 2 2. + <_> + 12 2 12 2 2. + <_> + + <_> + 9 1 9 6 -1. + <_> + 12 1 3 6 3. + <_> + + <_> + 4 1 14 4 -1. + <_> + 11 1 7 4 2. + <_> + + <_> + 10 14 7 9 -1. + <_> + 10 17 7 3 3. + <_> + + <_> + 8 3 8 10 -1. + <_> + 8 3 4 5 2. + <_> + 12 8 4 5 2. + <_> + + <_> + 7 3 12 5 -1. + <_> + 11 3 4 5 3. + <_> + + <_> + 8 2 4 13 -1. + <_> + 10 2 2 13 2. + <_> + + <_> + 11 2 3 19 -1. + <_> + 12 2 1 19 3. + <_> + + <_> + 7 7 9 6 -1. + <_> + 10 7 3 6 3. + <_> + + <_> + 4 22 20 2 -1. + <_> + 4 22 10 2 2. + <_> + + <_> + 0 16 24 4 -1. + <_> + 0 16 12 2 2. + <_> + 12 18 12 2 2. + <_> + + <_> + 7 3 12 5 -1. + <_> + 11 3 4 5 3. + <_> + + <_> + 1 10 8 14 -1. + <_> + 1 10 4 7 2. + <_> + 5 17 4 7 2. + <_> + + <_> + 11 16 6 6 -1. + <_> + 11 19 6 3 2. + <_> + + <_> + 6 0 10 24 -1. + <_> + 6 0 5 12 2. + <_> + 11 12 5 12 2. + <_> + + <_> + 7 5 14 14 -1. + <_> + 14 5 7 7 2. + <_> + 7 12 7 7 2. + <_> + + <_> + 7 8 10 8 -1. + <_> + 7 8 5 4 2. + <_> + 12 12 5 4 2. + <_> + + <_> + 9 1 9 6 -1. + <_> + 12 1 3 6 3. + <_> + + <_> + 0 6 24 3 -1. + <_> + 12 6 12 3 2. + <_> + + <_> + 7 3 12 5 -1. + <_> + 11 3 4 5 3. + <_> + + <_> + 1 13 22 4 -1. + <_> + 1 13 11 2 2. + <_> + 12 15 11 2 2. + <_> + + <_> + 9 12 12 6 -1. + <_> + 9 14 12 2 3. + <_> + + <_> + 0 5 9 6 -1. + <_> + 0 7 9 2 3. + <_> + + <_> + 1 5 23 6 -1. + <_> + 1 7 23 2 3. + <_> + + <_> + 1 6 19 12 -1. + <_> + 1 10 19 4 3. + <_> + + <_> + 9 1 6 21 -1. + <_> + 9 8 6 7 3. + <_> + + <_> + 3 19 18 3 -1. + <_> + 9 19 6 3 3. + <_> + + <_> + 9 14 6 9 -1. + <_> + 11 14 2 9 3. + <_> + + <_> + 9 6 4 12 -1. + <_> + 11 6 2 12 2. + <_> + + <_> + 16 0 6 9 -1. + <_> + 18 0 2 9 3. + <_> + + <_> + 2 0 6 9 -1. + <_> + 4 0 2 9 3. + <_> + + <_> + 13 1 4 22 -1. + <_> + 15 1 2 11 2. + <_> + 13 12 2 11 2. + <_> + + <_> + 1 8 8 12 -1. + <_> + 1 14 8 6 2. + <_> + + <_> + 14 7 7 9 -1. + <_> + 14 10 7 3 3. + <_> + + <_> + 3 12 18 4 -1. + <_> + 3 12 9 2 2. + <_> + 12 14 9 2 2. + <_> + + <_> + 13 1 4 22 -1. + <_> + 15 1 2 11 2. + <_> + 13 12 2 11 2. + <_> + + <_> + 7 1 4 22 -1. + <_> + 7 1 2 11 2. + <_> + 9 12 2 11 2. + <_> + + <_> + 4 7 20 4 -1. + <_> + 14 7 10 2 2. + <_> + 4 9 10 2 2. + <_> + + <_> + 9 10 6 7 -1. + <_> + 12 10 3 7 2. + <_> + + <_> + 7 7 10 4 -1. + <_> + 7 7 5 4 2. + <_> + + <_> + 0 3 4 15 -1. + <_> + 0 8 4 5 3. + <_> + + <_> + 15 0 8 12 -1. + <_> + 19 0 4 6 2. + <_> + 15 6 4 6 2. + <_> + + <_> + 1 0 8 12 -1. + <_> + 1 0 4 6 2. + <_> + 5 6 4 6 2. + <_> + + <_> + 14 5 6 16 -1. + <_> + 16 5 2 16 3. + <_> + + <_> + 4 5 6 16 -1. + <_> + 6 5 2 16 3. + <_> + + <_> + 15 0 6 16 -1. + <_> + 17 0 2 16 3. + <_> + + <_> + 3 0 6 16 -1. + <_> + 5 0 2 16 3. + <_> + + <_> + 0 2 24 3 -1. + <_> + 0 3 24 1 3. + <_> + + <_> + 7 1 10 4 -1. + <_> + 7 3 10 2 2. + <_> + + <_> + 1 0 23 8 -1. + <_> + 1 4 23 4 2. + <_> + + <_> + 1 17 19 3 -1. + <_> + 1 18 19 1 3. + <_> + + <_> + 6 18 18 2 -1. + <_> + 6 19 18 1 2. + <_> + + <_> + 1 17 9 6 -1. + <_> + 1 19 9 2 3. + <_> + + <_> + 15 15 6 9 -1. + <_> + 15 18 6 3 3. + <_> + + <_> + 3 15 6 9 -1. + <_> + 3 18 6 3 3. + <_> + + <_> + 4 14 20 6 -1. + <_> + 4 17 20 3 2. + <_> + + <_> + 0 10 6 14 -1. + <_> + 0 10 3 7 2. + <_> + 3 17 3 7 2. + <_> + + <_> + 6 18 18 3 -1. + <_> + 6 19 18 1 3. + <_> + + <_> + 4 12 9 7 -1. + <_> + 7 12 3 7 3. + <_> + + <_> + 6 10 18 5 -1. + <_> + 12 10 6 5 3. + <_> + + <_> + 0 10 18 5 -1. + <_> + 6 10 6 5 3. + <_> + + <_> + 3 2 18 9 -1. + <_> + 9 2 6 9 3. + <_> + + <_> + 4 6 10 10 -1. + <_> + 4 6 5 5 2. + <_> + 9 11 5 5 2. + <_> + + <_> + 20 14 4 9 -1. + <_> + 20 14 2 9 2. + <_> + + <_> + 0 14 4 9 -1. + <_> + 2 14 2 9 2. + <_> + + <_> + 11 1 4 20 -1. + <_> + 13 1 2 10 2. + <_> + 11 11 2 10 2. + <_> + + <_> + 6 21 12 3 -1. + <_> + 12 21 6 3 2. + <_> + + <_> + 11 1 4 20 -1. + <_> + 13 1 2 10 2. + <_> + 11 11 2 10 2. + <_> + + <_> + 1 16 10 8 -1. + <_> + 1 16 5 4 2. + <_> + 6 20 5 4 2. + <_> + + <_> + 11 1 4 20 -1. + <_> + 13 1 2 10 2. + <_> + 11 11 2 10 2. + <_> + + <_> + 1 0 3 19 -1. + <_> + 2 0 1 19 3. + <_> + + <_> + 11 1 4 20 -1. + <_> + 13 1 2 10 2. + <_> + 11 11 2 10 2. + <_> + + <_> + 0 1 6 9 -1. + <_> + 2 1 2 9 3. + <_> + + <_> + 3 7 19 4 -1. + <_> + 3 9 19 2 2. + <_> + + <_> + 7 14 9 6 -1. + <_> + 7 16 9 2 3. + <_> + + <_> + 17 1 7 6 -1. + <_> + 17 4 7 3 2. + <_> + + <_> + 5 0 14 8 -1. + <_> + 5 4 14 4 2. + <_> + + <_> + 16 1 8 6 -1. + <_> + 16 4 8 3 2. + <_> + + <_> + 0 1 8 6 -1. + <_> + 0 4 8 3 2. + <_> + + <_> + 6 0 18 4 -1. + <_> + 15 0 9 2 2. + <_> + 6 2 9 2 2. + <_> + + <_> + 0 14 9 6 -1. + <_> + 0 16 9 2 3. + <_> + + <_> + 3 7 18 8 -1. + <_> + 9 7 6 8 3. + <_> + + <_> + 2 11 6 9 -1. + <_> + 4 11 2 9 3. + <_> + + <_> + 10 5 6 9 -1. + <_> + 12 5 2 9 3. + <_> + + <_> + 10 6 4 18 -1. + <_> + 10 6 2 9 2. + <_> + 12 15 2 9 2. + <_> + + <_> + 11 1 4 20 -1. + <_> + 13 1 2 10 2. + <_> + 11 11 2 10 2. + <_> + + <_> + 9 1 4 20 -1. + <_> + 9 1 2 10 2. + <_> + 11 11 2 10 2. + <_> + + <_> + 5 9 18 6 -1. + <_> + 14 9 9 3 2. + <_> + 5 12 9 3 2. + <_> + + <_> + 6 4 6 9 -1. + <_> + 8 4 2 9 3. + <_> + + <_> + 10 16 8 6 -1. + <_> + 10 16 4 6 2. + <_> + + <_> + 0 0 18 8 -1. + <_> + 0 0 9 4 2. + <_> + 9 4 9 4 2. + <_> + + <_> + 6 5 14 12 -1. + <_> + 13 5 7 6 2. + <_> + 6 11 7 6 2. + <_> + + <_> + 4 3 15 7 -1. + <_> + 9 3 5 7 3. + <_> + + <_> + 14 12 10 6 -1. + <_> + 14 14 10 2 3. + <_> + + <_> + 0 11 4 10 -1. + <_> + 0 16 4 5 2. + <_> + + <_> + 1 10 22 3 -1. + <_> + 1 11 22 1 3. + <_> + + <_> + 8 9 6 10 -1. + <_> + 10 9 2 10 3. + <_> + + <_> + 13 2 6 12 -1. + <_> + 16 2 3 6 2. + <_> + 13 8 3 6 2. + <_> + + <_> + 10 6 4 18 -1. + <_> + 10 6 2 9 2. + <_> + 12 15 2 9 2. + <_> + + <_> + 7 8 10 16 -1. + <_> + 12 8 5 8 2. + <_> + 7 16 5 8 2. + <_> + + <_> + 8 1 8 12 -1. + <_> + 8 1 4 6 2. + <_> + 12 7 4 6 2. + <_> + + <_> + 7 1 12 14 -1. + <_> + 13 1 6 7 2. + <_> + 7 8 6 7 2. + <_> + + <_> + 2 14 12 6 -1. + <_> + 2 16 12 2 3. + <_> + + <_> + 11 16 6 6 -1. + <_> + 11 19 6 3 2. + <_> + + <_> + 7 16 6 6 -1. + <_> + 7 19 6 3 2. + <_> + + <_> + 13 4 4 10 -1. + <_> + 13 4 2 10 2. + <_> + + <_> + 0 19 19 3 -1. + <_> + 0 20 19 1 3. + <_> + + <_> + 12 8 6 8 -1. + <_> + 12 12 6 4 2. + <_> + + <_> + 8 1 8 22 -1. + <_> + 8 12 8 11 2. + <_> + + <_> + 12 8 6 8 -1. + <_> + 12 12 6 4 2. + <_> + + <_> + 6 8 6 8 -1. + <_> + 6 12 6 4 2. + <_> + + <_> + 14 5 6 9 -1. + <_> + 14 8 6 3 3. + <_> + + <_> + 0 6 24 4 -1. + <_> + 0 8 24 2 2. + <_> + + <_> + 14 12 10 6 -1. + <_> + 14 14 10 2 3. + <_> + + <_> + 0 12 10 6 -1. + <_> + 0 14 10 2 3. + <_> + + <_> + 4 6 19 3 -1. + <_> + 4 7 19 1 3. + <_> + + <_> + 1 6 19 3 -1. + <_> + 1 7 19 1 3. + <_> + + <_> + 4 0 16 9 -1. + <_> + 4 3 16 3 3. + <_> + + <_> + 0 1 24 5 -1. + <_> + 8 1 8 5 3. + <_> + + <_> + 3 6 6 15 -1. + <_> + 3 11 6 5 3. + <_> + + <_> + 9 6 6 9 -1. + <_> + 11 6 2 9 3. + <_> + + <_> + 0 17 18 3 -1. + <_> + 0 18 18 1 3. + <_> + + <_> + 6 22 18 2 -1. + <_> + 6 23 18 1 2. + <_> + + <_> + 2 12 6 9 -1. + <_> + 2 15 6 3 3. + <_> + + <_> + 18 12 6 9 -1. + <_> + 18 15 6 3 3. + <_> + + <_> + 0 12 6 9 -1. + <_> + 0 15 6 3 3. + <_> + + <_> + 11 14 4 10 -1. + <_> + 11 19 4 5 2. + <_> + + <_> + 9 6 6 16 -1. + <_> + 9 14 6 8 2. + <_> + + <_> + 7 7 10 10 -1. + <_> + 7 12 10 5 2. + <_> + + <_> + 1 3 6 13 -1. + <_> + 3 3 2 13 3. + <_> + + <_> + 18 1 6 13 -1. + <_> + 18 1 3 13 2. + <_> + + <_> + 5 1 6 9 -1. + <_> + 7 1 2 9 3. + <_> + + <_> + 18 2 6 11 -1. + <_> + 18 2 3 11 2. + <_> + + <_> + 0 2 6 11 -1. + <_> + 3 2 3 11 2. + <_> + + <_> + 9 12 15 6 -1. + <_> + 9 14 15 2 3. + <_> + + <_> + 2 2 20 3 -1. + <_> + 2 3 20 1 3. + <_> + + <_> + 10 6 4 9 -1. + <_> + 10 6 2 9 2. + <_> + + <_> + 5 6 12 14 -1. + <_> + 5 6 6 7 2. + <_> + 11 13 6 7 2. + <_> + + <_> + 9 0 6 9 -1. + <_> + 11 0 2 9 3. + <_> + + <_> + 7 0 9 6 -1. + <_> + 10 0 3 6 3. + <_> + + <_> + 10 6 6 9 -1. + <_> + 12 6 2 9 3. + <_> + + <_> + 4 1 12 20 -1. + <_> + 4 1 6 10 2. + <_> + 10 11 6 10 2. + <_> + + <_> + 6 7 18 3 -1. + <_> + 6 7 9 3 2. + <_> + + <_> + 0 7 18 3 -1. + <_> + 9 7 9 3 2. + <_> + + <_> + 3 20 18 3 -1. + <_> + 9 20 6 3 3. + <_> + + <_> + 9 6 6 9 -1. + <_> + 11 6 2 9 3. + <_> + + <_> + 6 2 12 15 -1. + <_> + 10 2 4 15 3. + <_> + + <_> + 2 3 18 3 -1. + <_> + 2 4 18 1 3. + <_> + + <_> + 19 4 4 18 -1. + <_> + 21 4 2 9 2. + <_> + 19 13 2 9 2. + <_> + + <_> + 0 1 19 3 -1. + <_> + 0 2 19 1 3. + <_> + + <_> + 5 0 15 4 -1. + <_> + 5 2 15 2 2. + <_> + + <_> + 5 2 14 5 -1. + <_> + 12 2 7 5 2. + <_> + + <_> + 1 2 22 14 -1. + <_> + 1 2 11 14 2. + <_> + + <_> + 8 15 6 9 -1. + <_> + 10 15 2 9 3. + <_> + + <_> + 6 17 18 3 -1. + <_> + 6 18 18 1 3. + <_> + + <_> + 9 6 3 18 -1. + <_> + 9 12 3 6 3. + <_> + + <_> + 2 0 20 3 -1. + <_> + 2 1 20 1 3. + <_> + + <_> + 5 4 5 12 -1. + <_> + 5 8 5 4 3. + <_> + + <_> + 8 6 12 5 -1. + <_> + 12 6 4 5 3. + <_> + + <_> + 9 12 6 12 -1. + <_> + 9 12 3 6 2. + <_> + 12 18 3 6 2. + <_> + + <_> + 14 14 8 10 -1. + <_> + 18 14 4 5 2. + <_> + 14 19 4 5 2. + <_> + + <_> + 2 14 8 10 -1. + <_> + 2 14 4 5 2. + <_> + 6 19 4 5 2. + <_> + + <_> + 10 18 12 6 -1. + <_> + 16 18 6 3 2. + <_> + 10 21 6 3 2. + <_> + + <_> + 1 3 6 9 -1. + <_> + 1 6 6 3 3. + <_> + + <_> + 11 3 3 20 -1. + <_> + 12 3 1 20 3. + <_> + + <_> + 4 6 14 6 -1. + <_> + 4 6 7 3 2. + <_> + 11 9 7 3 2. + <_> + + <_> + 6 5 12 13 -1. + <_> + 10 5 4 13 3. + <_> + + <_> + 5 4 4 15 -1. + <_> + 5 9 4 5 3. + <_> + + <_> + 9 16 15 4 -1. + <_> + 14 16 5 4 3. + <_> + + <_> + 7 8 6 14 -1. + <_> + 7 8 3 7 2. + <_> + 10 15 3 7 2. + <_> + + <_> + 7 6 10 6 -1. + <_> + 7 8 10 2 3. + <_> + + <_> + 2 5 18 3 -1. + <_> + 2 6 18 1 3. + <_> + + <_> + 5 1 15 8 -1. + <_> + 5 5 15 4 2. + <_> + + <_> + 7 1 8 18 -1. + <_> + 7 10 8 9 2. + <_> + + <_> + 0 10 24 3 -1. + <_> + 0 11 24 1 3. + <_> + + <_> + 0 2 6 13 -1. + <_> + 2 2 2 13 3. + <_> + + <_> + 16 0 8 10 -1. + <_> + 20 0 4 5 2. + <_> + 16 5 4 5 2. + <_> + + <_> + 5 1 10 9 -1. + <_> + 5 4 10 3 3. + <_> + + <_> + 5 6 18 3 -1. + <_> + 5 7 18 1 3. + <_> + + <_> + 0 1 24 3 -1. + <_> + 0 2 24 1 3. + <_> + + <_> + 11 4 6 11 -1. + <_> + 13 4 2 11 3. + <_> + + <_> + 0 0 8 10 -1. + <_> + 0 0 4 5 2. + <_> + 4 5 4 5 2. + <_> + + <_> + 4 16 18 3 -1. + <_> + 4 17 18 1 3. + <_> + + <_> + 2 16 18 3 -1. + <_> + 2 17 18 1 3. + <_> + + <_> + 3 0 18 10 -1. + <_> + 12 0 9 5 2. + <_> + 3 5 9 5 2. + <_> + + <_> + 2 3 20 21 -1. + <_> + 12 3 10 21 2. + <_> + + <_> + 6 7 14 3 -1. + <_> + 6 7 7 3 2. + <_> + + <_> + 0 9 12 6 -1. + <_> + 0 9 6 3 2. + <_> + 6 12 6 3 2. + <_> + + <_> + 3 14 21 4 -1. + <_> + 10 14 7 4 3. + <_> + + <_> + 0 14 21 4 -1. + <_> + 7 14 7 4 3. + <_> + + <_> + 5 21 18 3 -1. + <_> + 11 21 6 3 3. + <_> + + <_> + 1 21 18 3 -1. + <_> + 7 21 6 3 3. + <_> + + <_> + 19 4 4 18 -1. + <_> + 21 4 2 9 2. + <_> + 19 13 2 9 2. + <_> + + <_> + 3 7 18 3 -1. + <_> + 3 8 18 1 3. + <_> + + <_> + 19 4 4 18 -1. + <_> + 21 4 2 9 2. + <_> + 19 13 2 9 2. + <_> + + <_> + 7 15 10 6 -1. + <_> + 7 17 10 2 3. + <_> + + <_> + 9 13 11 9 -1. + <_> + 9 16 11 3 3. + <_> + + <_> + 0 6 4 10 -1. + <_> + 0 11 4 5 2. + <_> + + <_> + 15 16 9 6 -1. + <_> + 15 18 9 2 3. + <_> + + <_> + 1 5 4 18 -1. + <_> + 1 5 2 9 2. + <_> + 3 14 2 9 2. + <_> + + <_> + 9 8 8 10 -1. + <_> + 13 8 4 5 2. + <_> + 9 13 4 5 2. + <_> + + <_> + 7 8 8 10 -1. + <_> + 7 8 4 5 2. + <_> + 11 13 4 5 2. + <_> + + <_> + 9 8 12 5 -1. + <_> + 13 8 4 5 3. + <_> + + <_> + 7 8 9 7 -1. + <_> + 10 8 3 7 3. + <_> + + <_> + 9 8 12 5 -1. + <_> + 13 8 4 5 3. + <_> + + <_> + 7 6 9 7 -1. + <_> + 10 6 3 7 3. + <_> + + <_> + 9 8 12 5 -1. + <_> + 13 8 4 5 3. + <_> + + <_> + 10 5 4 18 -1. + <_> + 10 11 4 6 3. + <_> + + <_> + 5 5 14 12 -1. + <_> + 5 11 14 6 2. + <_> + + <_> + 0 1 11 4 -1. + <_> + 0 3 11 2 2. + <_> + + <_> + 9 10 6 10 -1. + <_> + 11 10 2 10 3. + <_> + + <_> + 2 17 11 6 -1. + <_> + 2 19 11 2 3. + <_> + + <_> + 15 16 9 6 -1. + <_> + 15 18 9 2 3. + <_> + + <_> + 1 10 18 2 -1. + <_> + 1 11 18 1 2. + <_> + + <_> + 6 4 12 13 -1. + <_> + 10 4 4 13 3. + <_> + + <_> + 0 18 18 3 -1. + <_> + 0 19 18 1 3. + <_> + + <_> + 6 18 18 3 -1. + <_> + 6 19 18 1 3. + <_> + + <_> + 0 16 9 6 -1. + <_> + 0 18 9 2 3. + <_> + + <_> + 13 15 9 6 -1. + <_> + 13 17 9 2 3. + <_> + + <_> + 2 15 9 6 -1. + <_> + 2 17 9 2 3. + <_> + + <_> + 13 1 6 16 -1. + <_> + 13 1 3 16 2. + <_> + + <_> + 5 1 6 16 -1. + <_> + 8 1 3 16 2. + <_> + + <_> + 11 5 6 10 -1. + <_> + 13 5 2 10 3. + <_> + + <_> + 7 5 6 10 -1. + <_> + 9 5 2 10 3. + <_> + + <_> + 10 0 6 24 -1. + <_> + 12 0 2 24 3. + <_> + + <_> + 3 4 4 20 -1. + <_> + 3 4 2 10 2. + <_> + 5 14 2 10 2. + <_> + + <_> + 14 0 6 9 -1. + <_> + 16 0 2 9 3. + <_> + + <_> + 4 0 6 9 -1. + <_> + 6 0 2 9 3. + <_> + + <_> + 4 5 18 5 -1. + <_> + 10 5 6 5 3. + <_> + + <_> + 5 6 6 9 -1. + <_> + 7 6 2 9 3. + <_> + + <_> + 7 2 15 8 -1. + <_> + 12 2 5 8 3. + <_> + + <_> + 2 2 15 8 -1. + <_> + 7 2 5 8 3. + <_> + + <_> + 10 0 4 9 -1. + <_> + 10 0 2 9 2. + <_> + + <_> + 3 4 6 12 -1. + <_> + 3 4 3 6 2. + <_> + 6 10 3 6 2. + <_> + + <_> + 16 0 8 18 -1. + <_> + 16 0 4 18 2. + <_> + + <_> + 0 0 8 18 -1. + <_> + 4 0 4 18 2. + <_> + + <_> + 0 7 24 6 -1. + <_> + 0 9 24 2 3. + <_> + + <_> + 4 7 14 3 -1. + <_> + 11 7 7 3 2. + <_> + + <_> + 10 8 8 15 -1. + <_> + 10 8 4 15 2. + <_> + + <_> + 7 0 10 14 -1. + <_> + 12 0 5 14 2. + <_> + + <_> + 13 10 8 10 -1. + <_> + 17 10 4 5 2. + <_> + 13 15 4 5 2. + <_> + + <_> + 3 0 4 9 -1. + <_> + 5 0 2 9 2. + <_> + + <_> + 16 1 6 8 -1. + <_> + 16 1 3 8 2. + <_> + + <_> + 2 1 6 8 -1. + <_> + 5 1 3 8 2. + <_> + + <_> + 3 6 18 12 -1. + <_> + 3 10 18 4 3. + <_> + + <_> + 4 12 16 4 -1. + <_> + 4 14 16 2 2. + <_> + + <_> + 4 9 16 15 -1. + <_> + 4 14 16 5 3. + <_> + + <_> + 3 10 8 10 -1. + <_> + 3 10 4 5 2. + <_> + 7 15 4 5 2. + <_> + + <_> + 8 18 16 6 -1. + <_> + 16 18 8 3 2. + <_> + 8 21 8 3 2. + <_> + + <_> + 2 16 12 5 -1. + <_> + 6 16 4 5 3. + <_> + + <_> + 14 14 9 4 -1. + <_> + 14 16 9 2 2. + <_> + + <_> + 7 14 9 6 -1. + <_> + 7 16 9 2 3. + <_> + + <_> + 4 10 16 12 -1. + <_> + 4 14 16 4 3. + <_> + + <_> + 0 13 19 6 -1. + <_> + 0 15 19 2 3. + <_> + + <_> + 10 13 9 6 -1. + <_> + 10 15 9 2 3. + <_> + + <_> + 5 0 3 23 -1. + <_> + 6 0 1 23 3. + <_> + + <_> + 0 8 24 6 -1. + <_> + 0 10 24 2 3. + <_> + + <_> + 0 5 5 12 -1. + <_> + 0 9 5 4 3. + <_> + + <_> + 3 0 19 18 -1. + <_> + 3 9 19 9 2. + <_> + + <_> + 9 11 6 12 -1. + <_> + 9 11 3 6 2. + <_> + 12 17 3 6 2. + <_> + + <_> + 0 5 24 8 -1. + <_> + 12 5 12 4 2. + <_> + 0 9 12 4 2. + <_> + + <_> + 6 18 9 4 -1. + <_> + 6 20 9 2 2. + <_> + + <_> + 8 8 10 6 -1. + <_> + 8 10 10 2 3. + <_> + + <_> + 2 7 20 3 -1. + <_> + 2 8 20 1 3. + <_> + + <_> + 12 0 7 20 -1. + <_> + 12 10 7 10 2. + <_> + + <_> + 5 0 7 20 -1. + <_> + 5 10 7 10 2. + <_> + + <_> + 14 2 2 18 -1. + <_> + 14 11 2 9 2. + <_> + + <_> + 5 8 10 12 -1. + <_> + 10 8 5 12 2. + <_> + + <_> + 6 9 12 8 -1. + <_> + 12 9 6 4 2. + <_> + 6 13 6 4 2. + <_> + + <_> + 7 7 3 14 -1. + <_> + 7 14 3 7 2. + <_> + + <_> + 11 2 12 16 -1. + <_> + 17 2 6 8 2. + <_> + 11 10 6 8 2. + <_> + + <_> + 7 0 6 9 -1. + <_> + 9 0 2 9 3. + <_> + + <_> + 13 14 9 4 -1. + <_> + 13 16 9 2 2. + <_> + + <_> + 0 12 22 4 -1. + <_> + 0 12 11 2 2. + <_> + 11 14 11 2 2. + <_> + + <_> + 1 12 22 6 -1. + <_> + 12 12 11 3 2. + <_> + 1 15 11 3 2. + <_> + + <_> + 6 6 9 6 -1. + <_> + 9 6 3 6 3. + <_> + + <_> + 10 0 4 9 -1. + <_> + 10 0 2 9 2. + <_> + + <_> + 3 8 18 7 -1. + <_> + 9 8 6 7 3. + <_> + + <_> + 0 6 24 6 -1. + <_> + 0 8 24 2 3. + <_> + + <_> + 0 11 24 10 -1. + <_> + 8 11 8 10 3. + <_> + + <_> + 3 3 18 21 -1. + <_> + 9 3 6 21 3. + <_> + + <_> + 7 12 4 10 -1. + <_> + 9 12 2 10 2. + <_> + + <_> + 10 16 10 8 -1. + <_> + 15 16 5 4 2. + <_> + 10 20 5 4 2. + <_> + + <_> + 8 6 6 9 -1. + <_> + 10 6 2 9 3. + <_> + + <_> + 12 10 6 12 -1. + <_> + 15 10 3 6 2. + <_> + 12 16 3 6 2. + <_> + + <_> + 6 10 6 12 -1. + <_> + 6 10 3 6 2. + <_> + 9 16 3 6 2. + <_> + + <_> + 16 12 6 12 -1. + <_> + 19 12 3 6 2. + <_> + 16 18 3 6 2. + <_> + + <_> + 2 12 6 12 -1. + <_> + 2 12 3 6 2. + <_> + 5 18 3 6 2. + <_> + + <_> + 10 15 6 9 -1. + <_> + 12 15 2 9 3. + <_> + + <_> + 8 15 6 9 -1. + <_> + 10 15 2 9 3. + <_> + + <_> + 14 20 10 4 -1. + <_> + 14 20 5 4 2. + <_> + + <_> + 0 20 10 4 -1. + <_> + 5 20 5 4 2. + <_> + + <_> + 11 17 9 6 -1. + <_> + 11 19 9 2 3. + <_> + + <_> + 3 2 14 4 -1. + <_> + 3 4 14 2 2. + <_> + + <_> + 10 1 10 4 -1. + <_> + 10 3 10 2 2. + <_> + + <_> + 0 15 10 4 -1. + <_> + 5 15 5 4 2. + <_> + + <_> + 19 2 3 19 -1. + <_> + 20 2 1 19 3. + <_> + + <_> + 4 12 9 8 -1. + <_> + 7 12 3 8 3. + <_> + + <_> + 4 7 5 12 -1. + <_> + 4 11 5 4 3. + <_> + + <_> + 0 1 24 3 -1. + <_> + 8 1 8 3 3. + <_> + + <_> + 6 8 12 4 -1. + <_> + 6 10 12 2 2. + <_> + + <_> + 19 3 4 10 -1. + <_> + 19 3 2 10 2. + <_> + + <_> + 0 6 9 6 -1. + <_> + 3 6 3 6 3. + <_> + + <_> + 18 0 6 22 -1. + <_> + 20 0 2 22 3. + <_> + + <_> + 0 0 6 22 -1. + <_> + 2 0 2 22 3. + <_> + + <_> + 5 15 19 3 -1. + <_> + 5 16 19 1 3. + <_> + + <_> + 10 7 4 15 -1. + <_> + 10 12 4 5 3. + <_> + + <_> + 9 6 6 9 -1. + <_> + 11 6 2 9 3. + <_> + + <_> + 0 21 18 3 -1. + <_> + 0 22 18 1 3. + <_> + + <_> + 7 3 10 15 -1. + <_> + 7 8 10 5 3. + <_> + + <_> + 1 7 18 3 -1. + <_> + 1 8 18 1 3. + <_> + + <_> + 8 2 9 6 -1. + <_> + 11 2 3 6 3. + <_> + + <_> + 0 10 24 14 -1. + <_> + 0 17 24 7 2. + <_> + + <_> + 13 9 8 10 -1. + <_> + 17 9 4 5 2. + <_> + 13 14 4 5 2. + <_> + + <_> + 10 5 4 9 -1. + <_> + 12 5 2 9 2. + <_> + + <_> + 13 9 8 10 -1. + <_> + 17 9 4 5 2. + <_> + 13 14 4 5 2. + <_> + + <_> + 7 11 10 10 -1. + <_> + 7 11 5 5 2. + <_> + 12 16 5 5 2. + <_> + + <_> + 4 13 18 4 -1. + <_> + 13 13 9 2 2. + <_> + 4 15 9 2 2. + <_> + + <_> + 0 0 19 2 -1. + <_> + 0 1 19 1 2. + <_> + + <_> + 0 18 24 6 -1. + <_> + 8 18 8 6 3. + <_> + + <_> + 6 4 8 16 -1. + <_> + 6 12 8 8 2. + <_> + + <_> + 7 8 10 4 -1. + <_> + 7 10 10 2 2. + <_> + + <_> + 0 3 6 9 -1. + <_> + 0 6 6 3 3. + <_> + + <_> + 13 15 7 9 -1. + <_> + 13 18 7 3 3. + <_> + + <_> + 3 18 12 6 -1. + <_> + 3 18 6 3 2. + <_> + 9 21 6 3 2. + <_> + + <_> + 12 14 6 9 -1. + <_> + 12 17 6 3 3. + <_> + + <_> + 2 15 15 8 -1. + <_> + 2 19 15 4 2. + <_> + + <_> + 9 6 6 16 -1. + <_> + 9 14 6 8 2. + <_> + + <_> + 6 6 7 12 -1. + <_> + 6 10 7 4 3. + <_> + + <_> + 14 6 6 9 -1. + <_> + 14 9 6 3 3. + <_> + + <_> + 5 14 6 9 -1. + <_> + 5 17 6 3 3. + <_> + + <_> + 10 8 6 9 -1. + <_> + 12 8 2 9 3. + <_> + + <_> + 6 6 4 18 -1. + <_> + 6 6 2 9 2. + <_> + 8 15 2 9 2. + <_> + + <_> + 14 9 6 12 -1. + <_> + 17 9 3 6 2. + <_> + 14 15 3 6 2. + <_> + + <_> + 4 9 6 12 -1. + <_> + 4 9 3 6 2. + <_> + 7 15 3 6 2. + <_> + + <_> + 14 15 9 6 -1. + <_> + 14 17 9 2 3. + <_> + + <_> + 0 20 18 4 -1. + <_> + 0 20 9 2 2. + <_> + 9 22 9 2 2. + <_> + + <_> + 13 18 9 6 -1. + <_> + 13 20 9 2 3. + <_> + + <_> + 2 18 9 6 -1. + <_> + 2 20 9 2 3. + <_> + + <_> + 6 16 18 3 -1. + <_> + 6 17 18 1 3. + <_> + + <_> + 0 16 18 3 -1. + <_> + 0 17 18 1 3. + <_> + + <_> + 19 2 4 22 -1. + <_> + 21 2 2 11 2. + <_> + 19 13 2 11 2. + <_> + + <_> + 1 2 4 22 -1. + <_> + 1 2 2 11 2. + <_> + 3 13 2 11 2. + <_> + + <_> + 15 0 2 24 -1. + <_> + 15 0 1 24 2. + <_> + + <_> + 3 20 16 4 -1. + <_> + 11 20 8 4 2. + <_> + + <_> + 11 6 4 18 -1. + <_> + 13 6 2 9 2. + <_> + 11 15 2 9 2. + <_> + + <_> + 7 9 10 14 -1. + <_> + 7 9 5 7 2. + <_> + 12 16 5 7 2. + <_> + + <_> + 14 6 6 9 -1. + <_> + 14 9 6 3 3. + <_> + + <_> + 3 6 7 9 -1. + <_> + 3 9 7 3 3. + <_> + + <_> + 20 4 4 20 -1. + <_> + 22 4 2 10 2. + <_> + 20 14 2 10 2. + <_> + + <_> + 7 6 6 9 -1. + <_> + 7 9 6 3 3. + <_> + + <_> + 7 0 10 14 -1. + <_> + 12 0 5 7 2. + <_> + 7 7 5 7 2. + <_> + + <_> + 2 1 18 6 -1. + <_> + 11 1 9 6 2. + <_> + + <_> + 15 0 2 24 -1. + <_> + 15 0 1 24 2. + <_> + + <_> + 7 0 2 24 -1. + <_> + 8 0 1 24 2. + <_> + + <_> + 13 12 6 7 -1. + <_> + 13 12 3 7 2. + <_> + + <_> + 5 12 6 7 -1. + <_> + 8 12 3 7 2. + <_> + + <_> + 3 5 18 19 -1. + <_> + 9 5 6 19 3. + <_> + + <_> + 5 6 9 6 -1. + <_> + 8 6 3 6 3. + <_> + + <_> + 9 5 9 6 -1. + <_> + 12 5 3 6 3. + <_> + + <_> + 3 16 10 8 -1. + <_> + 3 16 5 4 2. + <_> + 8 20 5 4 2. + <_> + + <_> + 19 8 5 15 -1. + <_> + 19 13 5 5 3. + <_> + + <_> + 0 8 5 15 -1. + <_> + 0 13 5 5 3. + <_> + + <_> + 20 4 4 20 -1. + <_> + 22 4 2 10 2. + <_> + 20 14 2 10 2. + <_> + + <_> + 0 4 4 20 -1. + <_> + 0 4 2 10 2. + <_> + 2 14 2 10 2. + <_> + + <_> + 7 7 10 4 -1. + <_> + 7 7 5 4 2. + <_> + + <_> + 4 19 14 4 -1. + <_> + 11 19 7 4 2. + <_> + + <_> + 10 11 12 3 -1. + <_> + 10 11 6 3 2. + <_> + + <_> + 0 1 24 3 -1. + <_> + 0 2 24 1 3. + <_> + + <_> + 7 2 14 20 -1. + <_> + 14 2 7 10 2. + <_> + 7 12 7 10 2. + <_> + + <_> + 0 13 6 9 -1. + <_> + 2 13 2 9 3. + <_> + + <_> + 13 0 4 19 -1. + <_> + 13 0 2 19 2. + <_> + + <_> + 1 11 14 3 -1. + <_> + 8 11 7 3 2. + <_> + + <_> + 7 1 16 20 -1. + <_> + 15 1 8 10 2. + <_> + 7 11 8 10 2. + <_> + + <_> + 0 10 21 9 -1. + <_> + 7 10 7 9 3. + <_> + + <_> + 6 19 15 5 -1. + <_> + 11 19 5 5 3. + <_> + + <_> + 8 10 6 6 -1. + <_> + 11 10 3 6 2. + <_> + + <_> + 7 1 16 20 -1. + <_> + 15 1 8 10 2. + <_> + 7 11 8 10 2. + <_> + + <_> + 1 1 16 20 -1. + <_> + 1 1 8 10 2. + <_> + 9 11 8 10 2. + <_> + + <_> + 16 4 3 12 -1. + <_> + 16 10 3 6 2. + <_> + + <_> + 5 4 3 12 -1. + <_> + 5 10 3 6 2. + <_> + + <_> + 7 6 10 8 -1. + <_> + 12 6 5 4 2. + <_> + 7 10 5 4 2. + <_> + + <_> + 4 9 6 6 -1. + <_> + 4 12 6 3 2. + <_> + + <_> + 6 5 12 4 -1. + <_> + 6 7 12 2 2. + <_> + + <_> + 9 2 5 15 -1. + <_> + 9 7 5 5 3. + <_> + + <_> + 15 0 9 6 -1. + <_> + 15 2 9 2 3. + <_> + + <_> + 6 0 11 10 -1. + <_> + 6 5 11 5 2. + <_> + + <_> + 12 7 4 12 -1. + <_> + 12 13 4 6 2. + <_> + + <_> + 7 2 9 4 -1. + <_> + 7 4 9 2 2. + <_> + + <_> + 6 0 13 6 -1. + <_> + 6 2 13 2 3. + <_> + + <_> + 10 6 4 18 -1. + <_> + 10 6 2 9 2. + <_> + 12 15 2 9 2. + <_> + + <_> + 10 8 6 9 -1. + <_> + 12 8 2 9 3. + <_> + + <_> + 3 18 10 6 -1. + <_> + 3 20 10 2 3. + <_> + + <_> + 4 14 20 3 -1. + <_> + 4 15 20 1 3. + <_> + + <_> + 2 15 9 6 -1. + <_> + 2 17 9 2 3. + <_> + + <_> + 13 0 4 19 -1. + <_> + 13 0 2 19 2. + <_> + + <_> + 7 0 4 19 -1. + <_> + 9 0 2 19 2. + <_> + + <_> + 1 4 22 2 -1. + <_> + 1 5 22 1 2. + <_> + + <_> + 0 0 9 6 -1. + <_> + 0 2 9 2 3. + <_> + + <_> + 0 0 24 18 -1. + <_> + 0 9 24 9 2. + <_> + + <_> + 3 2 16 8 -1. + <_> + 3 6 16 4 2. + <_> + + <_> + 3 6 18 6 -1. + <_> + 3 8 18 2 3. + <_> + + <_> + 3 1 6 10 -1. + <_> + 5 1 2 10 3. + <_> + + <_> + 13 0 9 6 -1. + <_> + 16 0 3 6 3. + <_> + + <_> + 2 0 9 6 -1. + <_> + 5 0 3 6 3. + <_> + + <_> + 10 2 4 15 -1. + <_> + 10 7 4 5 3. + <_> + + <_> + 6 0 7 10 -1. + <_> + 6 5 7 5 2. + <_> + + <_> + 2 2 20 4 -1. + <_> + 12 2 10 2 2. + <_> + 2 4 10 2 2. + <_> + + <_> + 2 11 19 3 -1. + <_> + 2 12 19 1 3. + <_> + + <_> + 10 8 6 9 -1. + <_> + 12 8 2 9 3. + <_> + + <_> + 8 8 6 9 -1. + <_> + 10 8 2 9 3. + <_> + + <_> + 13 8 4 9 -1. + <_> + 13 8 2 9 2. + <_> + + <_> + 3 11 9 9 -1. + <_> + 6 11 3 9 3. + <_> + + <_> + 3 9 18 5 -1. + <_> + 9 9 6 5 3. + <_> + + <_> + 2 4 2 20 -1. + <_> + 2 14 2 10 2. + <_> + + <_> + 14 17 8 6 -1. + <_> + 14 20 8 3 2. + <_> + + <_> + 3 21 18 2 -1. + <_> + 3 22 18 1 2. + <_> + + <_> + 5 4 15 6 -1. + <_> + 10 4 5 6 3. + <_> + + <_> + 2 15 12 6 -1. + <_> + 2 17 12 2 3. + <_> + + <_> + 17 8 6 9 -1. + <_> + 17 11 6 3 3. + <_> + + <_> + 2 12 20 4 -1. + <_> + 2 12 10 2 2. + <_> + 12 14 10 2 2. + <_> + + <_> + 0 17 24 6 -1. + <_> + 0 19 24 2 3. + <_> + + <_> + 7 16 9 4 -1. + <_> + 7 18 9 2 2. + <_> + + <_> + 15 1 4 22 -1. + <_> + 17 1 2 11 2. + <_> + 15 12 2 11 2. + <_> + + <_> + 5 1 4 22 -1. + <_> + 5 1 2 11 2. + <_> + 7 12 2 11 2. + <_> + + <_> + 11 13 8 9 -1. + <_> + 11 16 8 3 3. + <_> + + <_> + 6 1 6 9 -1. + <_> + 8 1 2 9 3. + <_> + + <_> + 11 4 3 18 -1. + <_> + 11 10 3 6 3. + <_> + + <_> + 5 8 12 6 -1. + <_> + 5 8 6 3 2. + <_> + 11 11 6 3 2. + <_> + + <_> + 15 7 5 8 -1. + <_> + 15 11 5 4 2. + <_> + + <_> + 4 7 5 8 -1. + <_> + 4 11 5 4 2. + <_> + + <_> + 12 6 6 12 -1. + <_> + 15 6 3 6 2. + <_> + 12 12 3 6 2. + <_> + + <_> + 6 6 6 12 -1. + <_> + 6 6 3 6 2. + <_> + 9 12 3 6 2. + <_> + + <_> + 5 9 14 8 -1. + <_> + 12 9 7 4 2. + <_> + 5 13 7 4 2. + <_> + + <_> + 9 1 3 14 -1. + <_> + 9 8 3 7 2. + <_> + + <_> + 12 6 6 12 -1. + <_> + 12 10 6 4 3. + <_> + + <_> + 4 5 4 18 -1. + <_> + 4 5 2 9 2. + <_> + 6 14 2 9 2. + <_> + + <_> + 4 6 16 18 -1. + <_> + 4 12 16 6 3. + <_> + + <_> + 5 4 7 20 -1. + <_> + 5 14 7 10 2. + <_> + + <_> + 14 8 8 12 -1. + <_> + 14 14 8 6 2. + <_> + + <_> + 9 10 6 14 -1. + <_> + 9 10 3 7 2. + <_> + 12 17 3 7 2. + <_> + + <_> + 9 5 9 6 -1. + <_> + 12 5 3 6 3. + <_> + + <_> + 9 4 3 18 -1. + <_> + 10 4 1 18 3. + <_> + + <_> + 1 4 22 14 -1. + <_> + 12 4 11 7 2. + <_> + 1 11 11 7 2. + <_> + + <_> + 2 7 18 2 -1. + <_> + 2 8 18 1 2. + <_> + + <_> + 12 6 6 12 -1. + <_> + 12 10 6 4 3. + <_> + + <_> + 6 5 9 7 -1. + <_> + 9 5 3 7 3. + <_> + + <_> + 12 7 4 12 -1. + <_> + 12 13 4 6 2. + <_> + + <_> + 8 7 4 12 -1. + <_> + 8 13 4 6 2. + <_> + + <_> + 7 2 10 22 -1. + <_> + 7 13 10 11 2. + <_> + + <_> + 0 1 3 20 -1. + <_> + 1 1 1 20 3. + <_> + + <_> + 4 13 18 4 -1. + <_> + 13 13 9 2 2. + <_> + 4 15 9 2 2. + <_> + + <_> + 2 13 18 4 -1. + <_> + 2 13 9 2 2. + <_> + 11 15 9 2 2. + <_> + + <_> + 15 15 9 6 -1. + <_> + 15 17 9 2 3. + <_> + + <_> + 0 15 9 6 -1. + <_> + 0 17 9 2 3. + <_> + + <_> + 6 0 18 24 -1. + <_> + 15 0 9 12 2. + <_> + 6 12 9 12 2. + <_> + + <_> + 6 6 6 12 -1. + <_> + 6 10 6 4 3. + <_> + + <_> + 8 7 10 4 -1. + <_> + 8 9 10 2 2. + <_> + + <_> + 1 9 18 6 -1. + <_> + 1 9 9 3 2. + <_> + 10 12 9 3 2. + <_> + + <_> + 6 6 18 3 -1. + <_> + 6 7 18 1 3. + <_> + + <_> + 7 7 9 8 -1. + <_> + 10 7 3 8 3. + <_> + + <_> + 10 12 6 12 -1. + <_> + 12 12 2 12 3. + <_> + + <_> + 3 14 18 3 -1. + <_> + 3 15 18 1 3. + <_> + + <_> + 15 17 9 7 -1. + <_> + 18 17 3 7 3. + <_> + + <_> + 1 12 10 6 -1. + <_> + 1 14 10 2 3. + <_> + + <_> + 15 17 9 7 -1. + <_> + 18 17 3 7 3. + <_> + + <_> + 10 3 3 19 -1. + <_> + 11 3 1 19 3. + <_> + + <_> + 15 17 9 7 -1. + <_> + 18 17 3 7 3. + <_> + + <_> + 6 1 11 9 -1. + <_> + 6 4 11 3 3. + <_> + + <_> + 15 17 9 7 -1. + <_> + 18 17 3 7 3. + <_> + + <_> + 6 5 11 6 -1. + <_> + 6 8 11 3 2. + <_> + + <_> + 16 7 8 5 -1. + <_> + 16 7 4 5 2. + <_> + + <_> + 2 4 20 19 -1. + <_> + 12 4 10 19 2. + <_> + + <_> + 2 1 21 6 -1. + <_> + 9 1 7 6 3. + <_> + + <_> + 6 5 12 14 -1. + <_> + 6 5 6 7 2. + <_> + 12 12 6 7 2. + <_> + + <_> + 9 0 6 9 -1. + <_> + 11 0 2 9 3. + <_> + + <_> + 2 11 8 5 -1. + <_> + 6 11 4 5 2. + <_> + + <_> + 16 7 8 5 -1. + <_> + 16 7 4 5 2. + <_> + + <_> + 0 7 8 5 -1. + <_> + 4 7 4 5 2. + <_> + + <_> + 15 17 9 7 -1. + <_> + 18 17 3 7 3. + <_> + + <_> + 8 6 8 10 -1. + <_> + 8 6 4 5 2. + <_> + 12 11 4 5 2. + <_> + + <_> + 15 15 9 9 -1. + <_> + 18 15 3 9 3. + <_> + + <_> + 0 15 9 9 -1. + <_> + 3 15 3 9 3. + <_> + + <_> + 12 10 9 7 -1. + <_> + 15 10 3 7 3. + <_> + + <_> + 3 10 9 7 -1. + <_> + 6 10 3 7 3. + <_> + + <_> + 13 15 10 8 -1. + <_> + 18 15 5 4 2. + <_> + 13 19 5 4 2. + <_> + + <_> + 0 1 6 12 -1. + <_> + 0 1 3 6 2. + <_> + 3 7 3 6 2. + <_> + + <_> + 10 0 6 12 -1. + <_> + 13 0 3 6 2. + <_> + 10 6 3 6 2. + <_> + + <_> + 7 0 10 12 -1. + <_> + 7 0 5 6 2. + <_> + 12 6 5 6 2. + <_> + + <_> + 4 1 16 8 -1. + <_> + 4 1 8 8 2. + <_> + + <_> + 0 21 19 3 -1. + <_> + 0 22 19 1 3. + <_> + + <_> + 6 9 18 4 -1. + <_> + 15 9 9 2 2. + <_> + 6 11 9 2 2. + <_> + + <_> + 3 4 9 6 -1. + <_> + 3 6 9 2 3. + <_> + + <_> + 9 1 6 15 -1. + <_> + 9 6 6 5 3. + <_> + + <_> + 5 9 6 6 -1. + <_> + 8 9 3 6 2. + <_> + + <_> + 5 1 14 9 -1. + <_> + 5 4 14 3 3. + <_> + + <_> + 3 0 8 20 -1. + <_> + 3 0 4 10 2. + <_> + 7 10 4 10 2. + <_> + + <_> + 5 0 7 9 -1. + <_> + 5 3 7 3 3. + <_> + + <_> + 6 6 12 5 -1. + <_> + 10 6 4 5 3. + <_> + + <_> + 0 1 8 14 -1. + <_> + 4 1 4 14 2. + <_> + + <_> + 2 12 22 4 -1. + <_> + 2 14 22 2 2. + <_> + + <_> + 8 17 6 6 -1. + <_> + 8 20 6 3 2. + <_> + + <_> + 18 1 6 7 -1. + <_> + 18 1 3 7 2. + <_> + + <_> + 0 0 6 6 -1. + <_> + 3 0 3 6 2. + <_> + + <_> + 4 6 17 18 -1. + <_> + 4 12 17 6 3. + <_> + + <_> + 6 0 12 6 -1. + <_> + 6 0 6 3 2. + <_> + 12 3 6 3 2. + <_> + + <_> + 4 7 18 4 -1. + <_> + 13 7 9 2 2. + <_> + 4 9 9 2 2. + <_> + + <_> + 4 12 10 6 -1. + <_> + 4 14 10 2 3. + <_> + + <_> + 7 9 10 12 -1. + <_> + 12 9 5 6 2. + <_> + 7 15 5 6 2. + <_> + + <_> + 0 1 24 3 -1. + <_> + 8 1 8 3 3. + <_> + + <_> + 13 11 6 6 -1. + <_> + 13 11 3 6 2. + <_> + + <_> + 5 11 6 6 -1. + <_> + 8 11 3 6 2. + <_> + + <_> + 3 10 19 3 -1. + <_> + 3 11 19 1 3. + <_> + + <_> + 0 2 6 9 -1. + <_> + 0 5 6 3 3. + <_> + + <_> + 14 16 10 6 -1. + <_> + 14 18 10 2 3. + <_> + + <_> + 0 16 10 6 -1. + <_> + 0 18 10 2 3. + <_> + + <_> + 14 13 9 6 -1. + <_> + 14 15 9 2 3. + <_> + + <_> + 0 16 18 3 -1. + <_> + 0 17 18 1 3. + <_> + + <_> + 6 16 18 3 -1. + <_> + 6 17 18 1 3. + <_> + + <_> + 0 18 9 6 -1. + <_> + 0 20 9 2 3. + <_> + + <_> + 14 13 9 6 -1. + <_> + 14 15 9 2 3. + <_> + + <_> + 6 2 6 9 -1. + <_> + 8 2 2 9 3. + <_> + + <_> + 15 8 4 12 -1. + <_> + 15 8 2 12 2. + <_> + + <_> + 8 13 8 8 -1. + <_> + 8 17 8 4 2. + <_> + + <_> + 4 20 18 3 -1. + <_> + 10 20 6 3 3. + <_> + + <_> + 5 8 4 12 -1. + <_> + 7 8 2 12 2. + <_> + + <_> + 7 7 12 3 -1. + <_> + 7 7 6 3 2. + <_> + + <_> + 10 6 4 9 -1. + <_> + 12 6 2 9 2. + <_> + + <_> + 5 20 18 3 -1. + <_> + 11 20 6 3 3. + <_> + + <_> + 1 20 18 3 -1. + <_> + 7 20 6 3 3. + <_> + + <_> + 18 1 6 20 -1. + <_> + 21 1 3 10 2. + <_> + 18 11 3 10 2. + <_> + + <_> + 0 1 6 20 -1. + <_> + 0 1 3 10 2. + <_> + 3 11 3 10 2. + <_> + + <_> + 13 3 4 18 -1. + <_> + 15 3 2 9 2. + <_> + 13 12 2 9 2. + <_> + + <_> + 0 2 6 12 -1. + <_> + 0 6 6 4 3. + <_> + + <_> + 12 9 12 6 -1. + <_> + 18 9 6 3 2. + <_> + 12 12 6 3 2. + <_> + + <_> + 7 3 4 18 -1. + <_> + 7 3 2 9 2. + <_> + 9 12 2 9 2. + <_> + + <_> + 14 0 6 9 -1. + <_> + 16 0 2 9 3. + <_> + + <_> + 0 9 12 6 -1. + <_> + 0 9 6 3 2. + <_> + 6 12 6 3 2. + <_> + + <_> + 14 4 8 20 -1. + <_> + 18 4 4 10 2. + <_> + 14 14 4 10 2. + <_> + + <_> + 2 4 8 20 -1. + <_> + 2 4 4 10 2. + <_> + 6 14 4 10 2. + <_> + + <_> + 14 13 9 6 -1. + <_> + 14 15 9 2 3. + <_> + + <_> + 1 13 9 6 -1. + <_> + 1 15 9 2 3. + <_> + + <_> + 3 15 18 3 -1. + <_> + 9 15 6 3 3. + <_> + + <_> + 5 13 9 6 -1. + <_> + 5 15 9 2 3. + <_> + + <_> + 5 0 18 3 -1. + <_> + 5 1 18 1 3. + <_> + + <_> + 8 2 6 7 -1. + <_> + 11 2 3 7 2. + <_> + + <_> + 9 1 9 6 -1. + <_> + 12 1 3 6 3. + <_> + + <_> + 6 1 9 6 -1. + <_> + 9 1 3 6 3. + <_> + + <_> + 5 6 14 6 -1. + <_> + 12 6 7 3 2. + <_> + 5 9 7 3 2. + <_> + + <_> + 8 2 6 13 -1. + <_> + 10 2 2 13 3. + <_> + + <_> + 6 11 12 6 -1. + <_> + 12 11 6 3 2. + <_> + 6 14 6 3 2. + <_> + + <_> + 3 1 18 15 -1. + <_> + 9 1 6 15 3. + <_> + + <_> + 13 0 6 7 -1. + <_> + 13 0 3 7 2. + <_> + + <_> + 3 3 16 6 -1. + <_> + 3 6 16 3 2. + <_> + + <_> + 12 1 3 12 -1. + <_> + 12 7 3 6 2. + <_> + + <_> + 7 7 6 9 -1. + <_> + 9 7 2 9 3. + <_> + + <_> + 13 0 4 24 -1. + <_> + 13 0 2 24 2. + <_> + + <_> + 7 0 4 24 -1. + <_> + 9 0 2 24 2. + <_> + + <_> + 11 9 5 12 -1. + <_> + 11 13 5 4 3. + <_> + + <_> + 7 15 9 6 -1. + <_> + 7 17 9 2 3. + <_> + + <_> + 5 7 18 6 -1. + <_> + 5 9 18 2 3. + <_> + + <_> + 8 9 5 12 -1. + <_> + 8 13 5 4 3. + <_> + + <_> + 4 17 17 6 -1. + <_> + 4 19 17 2 3. + <_> + + <_> + 0 3 18 14 -1. + <_> + 0 3 9 7 2. + <_> + 9 10 9 7 2. + <_> + + <_> + 0 1 24 2 -1. + <_> + 0 2 24 1 2. + <_> + + <_> + 0 15 18 3 -1. + <_> + 0 16 18 1 3. + <_> + + <_> + 9 0 6 9 -1. + <_> + 11 0 2 9 3. + <_> + + <_> + 3 3 14 12 -1. + <_> + 3 9 14 6 2. + <_> + + <_> + 12 1 3 12 -1. + <_> + 12 7 3 6 2. + <_> + + <_> + 8 0 6 9 -1. + <_> + 10 0 2 9 3. + <_> + + <_> + 10 6 6 10 -1. + <_> + 12 6 2 10 3. + <_> + + <_> + 5 0 6 9 -1. + <_> + 7 0 2 9 3. + <_> + + <_> + 2 0 21 7 -1. + <_> + 9 0 7 7 3. + <_> + + <_> + 6 11 12 5 -1. + <_> + 10 11 4 5 3. + <_> + + <_> + 8 7 9 8 -1. + <_> + 11 7 3 8 3. + <_> + + <_> + 9 6 6 18 -1. + <_> + 9 6 3 9 2. + <_> + 12 15 3 9 2. + <_> + + <_> + 15 14 8 10 -1. + <_> + 19 14 4 5 2. + <_> + 15 19 4 5 2. + <_> + + <_> + 1 14 8 10 -1. + <_> + 1 14 4 5 2. + <_> + 5 19 4 5 2. + <_> + + <_> + 11 0 8 10 -1. + <_> + 15 0 4 5 2. + <_> + 11 5 4 5 2. + <_> + + <_> + 5 0 8 10 -1. + <_> + 5 0 4 5 2. + <_> + 9 5 4 5 2. + <_> + + <_> + 6 1 12 5 -1. + <_> + 6 1 6 5 2. + <_> + + <_> + 1 12 18 2 -1. + <_> + 10 12 9 2 2. + <_> + + <_> + 2 8 20 6 -1. + <_> + 12 8 10 3 2. + <_> + 2 11 10 3 2. + <_> + + <_> + 7 6 9 7 -1. + <_> + 10 6 3 7 3. + <_> + + <_> + 10 5 8 16 -1. + <_> + 14 5 4 8 2. + <_> + 10 13 4 8 2. + <_> + + <_> + 3 9 16 8 -1. + <_> + 3 9 8 4 2. + <_> + 11 13 8 4 2. + <_> + + <_> + 7 8 10 4 -1. + <_> + 7 8 5 4 2. + <_> + + <_> + 7 12 10 8 -1. + <_> + 7 12 5 4 2. + <_> + 12 16 5 4 2. + <_> + + <_> + 9 19 15 4 -1. + <_> + 14 19 5 4 3. + <_> + + <_> + 1 0 18 9 -1. + <_> + 7 0 6 9 3. + <_> + + <_> + 13 4 10 8 -1. + <_> + 18 4 5 4 2. + <_> + 13 8 5 4 2. + <_> + + <_> + 3 16 18 4 -1. + <_> + 9 16 6 4 3. + <_> + + <_> + 8 7 10 12 -1. + <_> + 13 7 5 6 2. + <_> + 8 13 5 6 2. + <_> + + <_> + 6 7 10 12 -1. + <_> + 6 7 5 6 2. + <_> + 11 13 5 6 2. + <_> + + <_> + 4 6 18 7 -1. + <_> + 10 6 6 7 3. + <_> + + <_> + 0 17 18 3 -1. + <_> + 0 18 18 1 3. + <_> + + <_> + 3 17 18 3 -1. + <_> + 3 18 18 1 3. + <_> + + <_> + 2 4 6 10 -1. + <_> + 4 4 2 10 3. + <_> + + <_> + 16 0 8 24 -1. + <_> + 16 0 4 24 2. + <_> + + <_> + 4 0 8 15 -1. + <_> + 8 0 4 15 2. + <_> + + <_> + 16 0 8 24 -1. + <_> + 16 0 4 24 2. + <_> + + <_> + 1 4 18 9 -1. + <_> + 7 4 6 9 3. + <_> + + <_> + 15 12 9 6 -1. + <_> + 15 14 9 2 3. + <_> + + <_> + 3 9 18 6 -1. + <_> + 3 9 9 3 2. + <_> + 12 12 9 3 2. + <_> + + <_> + 18 5 6 9 -1. + <_> + 18 8 6 3 3. + <_> + + <_> + 0 5 6 9 -1. + <_> + 0 8 6 3 3. + <_> + + <_> + 4 7 18 4 -1. + <_> + 13 7 9 2 2. + <_> + 4 9 9 2 2. + <_> + + <_> + 2 1 12 20 -1. + <_> + 2 1 6 10 2. + <_> + 8 11 6 10 2. + <_> + + <_> + 17 0 6 23 -1. + <_> + 17 0 3 23 2. + <_> + + <_> + 1 6 2 18 -1. + <_> + 1 15 2 9 2. + <_> + + <_> + 8 8 10 6 -1. + <_> + 8 10 10 2 3. + <_> + + <_> + 0 6 20 6 -1. + <_> + 0 6 10 3 2. + <_> + 10 9 10 3 2. + <_> + + <_> + 11 12 12 5 -1. + <_> + 15 12 4 5 3. + <_> + + <_> + 0 4 3 19 -1. + <_> + 1 4 1 19 3. + <_> + + <_> + 19 1 3 18 -1. + <_> + 20 1 1 18 3. + <_> + + <_> + 2 1 3 18 -1. + <_> + 3 1 1 18 3. + <_> + + <_> + 3 10 18 3 -1. + <_> + 9 10 6 3 3. + <_> + + <_> + 4 4 10 9 -1. + <_> + 9 4 5 9 2. + <_> + + <_> + 7 13 14 7 -1. + <_> + 7 13 7 7 2. + <_> + + <_> + 3 13 14 7 -1. + <_> + 10 13 7 7 2. + <_> + + <_> + 8 15 9 6 -1. + <_> + 11 15 3 6 3. + <_> + + <_> + 4 14 8 10 -1. + <_> + 4 14 4 5 2. + <_> + 8 19 4 5 2. + <_> + + <_> + 10 14 4 10 -1. + <_> + 10 19 4 5 2. + <_> + + <_> + 3 8 5 16 -1. + <_> + 3 16 5 8 2. + <_> + + <_> + 15 10 9 6 -1. + <_> + 15 12 9 2 3. + <_> + + <_> + 0 10 9 6 -1. + <_> + 0 12 9 2 3. + <_> + + <_> + 6 7 12 9 -1. + <_> + 6 10 12 3 3. + <_> + + <_> + 9 10 5 8 -1. + <_> + 9 14 5 4 2. + <_> + + <_> + 12 1 3 12 -1. + <_> + 12 7 3 6 2. + <_> + + <_> + 8 15 6 9 -1. + <_> + 10 15 2 9 3. + <_> + + <_> + 16 6 7 6 -1. + <_> + 16 9 7 3 2. + <_> + + <_> + 8 1 4 22 -1. + <_> + 10 1 2 22 2. + <_> + + <_> + 6 6 14 3 -1. + <_> + 6 6 7 3 2. + <_> + + <_> + 0 18 19 3 -1. + <_> + 0 19 19 1 3. + <_> + + <_> + 17 0 6 24 -1. + <_> + 17 0 3 24 2. + <_> + + <_> + 0 13 15 6 -1. + <_> + 5 13 5 6 3. + <_> + + <_> + 9 6 10 14 -1. + <_> + 14 6 5 7 2. + <_> + 9 13 5 7 2. + <_> + + <_> + 1 6 8 10 -1. + <_> + 1 6 4 5 2. + <_> + 5 11 4 5 2. + <_> + + <_> + 7 6 12 5 -1. + <_> + 7 6 6 5 2. + <_> + + <_> + 7 7 9 6 -1. + <_> + 10 7 3 6 3. + <_> + + <_> + 7 8 14 14 -1. + <_> + 14 8 7 7 2. + <_> + 7 15 7 7 2. + <_> + + <_> + 3 8 14 14 -1. + <_> + 3 8 7 7 2. + <_> + 10 15 7 7 2. + <_> + + <_> + 9 8 13 4 -1. + <_> + 9 10 13 2 2. + <_> + + <_> + 3 2 6 12 -1. + <_> + 3 2 3 6 2. + <_> + 6 8 3 6 2. + <_> + + <_> + 6 10 17 6 -1. + <_> + 6 13 17 3 2. + <_> + + <_> + 1 10 17 6 -1. + <_> + 1 13 17 3 2. + <_> + + <_> + 16 7 8 9 -1. + <_> + 16 10 8 3 3. + <_> + + <_> + 0 7 8 9 -1. + <_> + 0 10 8 3 3. + <_> + + <_> + 0 9 24 10 -1. + <_> + 12 9 12 5 2. + <_> + 0 14 12 5 2. + <_> + + <_> + 3 2 15 8 -1. + <_> + 8 2 5 8 3. + <_> + + <_> + 4 2 18 8 -1. + <_> + 10 2 6 8 3. + <_> + + <_> + 0 1 18 4 -1. + <_> + 0 1 9 2 2. + <_> + 9 3 9 2 2. + <_> + + <_> + 20 2 3 18 -1. + <_> + 21 2 1 18 3. + <_> + + <_> + 1 3 3 19 -1. + <_> + 2 3 1 19 3. + <_> + + <_> + 18 8 6 16 -1. + <_> + 20 8 2 16 3. + <_> + + <_> + 0 8 6 16 -1. + <_> + 2 8 2 16 3. + <_> + + <_> + 8 18 11 6 -1. + <_> + 8 20 11 2 3. + <_> + + <_> + 4 6 12 5 -1. + <_> + 8 6 4 5 3. + <_> + + <_> + 7 6 12 5 -1. + <_> + 11 6 4 5 3. + <_> + + <_> + 6 3 9 6 -1. + <_> + 9 3 3 6 3. + <_> + + <_> + 7 6 12 5 -1. + <_> + 7 6 6 5 2. + <_> + + <_> + 9 8 6 7 -1. + <_> + 12 8 3 7 2. + <_> + + <_> + 8 2 9 6 -1. + <_> + 11 2 3 6 3. + <_> + + <_> + 8 14 6 9 -1. + <_> + 8 17 6 3 3. + <_> + + <_> + 8 2 9 6 -1. + <_> + 11 2 3 6 3. + <_> + + <_> + 4 3 16 20 -1. + <_> + 4 3 8 10 2. + <_> + 12 13 8 10 2. + <_> + + <_> + 7 6 10 12 -1. + <_> + 12 6 5 6 2. + <_> + 7 12 5 6 2. + <_> + + <_> + 0 2 7 12 -1. + <_> + 0 6 7 4 3. + <_> + + <_> + 12 17 11 6 -1. + <_> + 12 19 11 2 3. + <_> + + <_> + 4 7 12 8 -1. + <_> + 4 7 6 4 2. + <_> + 10 11 6 4 2. + <_> + + <_> + 8 11 8 10 -1. + <_> + 12 11 4 5 2. + <_> + 8 16 4 5 2. + <_> + + <_> + 9 1 4 9 -1. + <_> + 11 1 2 9 2. + <_> + + <_> + 14 0 3 22 -1. + <_> + 15 0 1 22 3. + <_> + + <_> + 7 0 3 22 -1. + <_> + 8 0 1 22 3. + <_> + + <_> + 4 7 18 4 -1. + <_> + 13 7 9 2 2. + <_> + 4 9 9 2 2. + <_> + + <_> + 10 2 4 15 -1. + <_> + 10 7 4 5 3. + <_> + + <_> + 12 1 3 12 -1. + <_> + 12 7 3 6 2. + <_> + + <_> + 0 0 18 13 -1. + <_> + 9 0 9 13 2. + <_> + + <_> + 16 0 3 24 -1. + <_> + 17 0 1 24 3. + <_> + + <_> + 5 0 3 24 -1. + <_> + 6 0 1 24 3. + <_> + + <_> + 10 15 5 8 -1. + <_> + 10 19 5 4 2. + <_> + + <_> + 2 18 18 2 -1. + <_> + 2 19 18 1 2. + <_> + + <_> + 2 8 20 3 -1. + <_> + 2 9 20 1 3. + <_> + + <_> + 7 6 9 6 -1. + <_> + 7 8 9 2 3. + <_> + + <_> + 3 2 19 10 -1. + <_> + 3 7 19 5 2. + <_> + + <_> + 2 7 19 3 -1. + <_> + 2 8 19 1 3. + <_> + + <_> + 15 6 9 4 -1. + <_> + 15 8 9 2 2. + <_> + + <_> + 2 2 18 8 -1. + <_> + 8 2 6 8 3. + <_> + + <_> + 10 9 14 4 -1. + <_> + 10 9 7 4 2. + <_> + + <_> + 4 4 6 16 -1. + <_> + 7 4 3 16 2. + <_> + + <_> + 15 8 9 16 -1. + <_> + 18 8 3 16 3. + <_> + + <_> + 0 8 9 16 -1. + <_> + 3 8 3 16 3. + <_> + + <_> + 18 0 6 14 -1. + <_> + 20 0 2 14 3. + <_> + + <_> + 0 0 6 14 -1. + <_> + 2 0 2 14 3. + <_> + + <_> + 15 0 6 22 -1. + <_> + 17 0 2 22 3. + <_> + + <_> + 3 0 6 22 -1. + <_> + 5 0 2 22 3. + <_> + + <_> + 12 2 12 20 -1. + <_> + 16 2 4 20 3. + <_> + + <_> + 0 2 12 20 -1. + <_> + 4 2 4 20 3. + <_> + + <_> + 11 6 4 9 -1. + <_> + 11 6 2 9 2. + <_> + + <_> + 9 0 6 16 -1. + <_> + 12 0 3 16 2. + <_> + + <_> + 12 1 3 12 -1. + <_> + 12 7 3 6 2. + <_> + + <_> + 3 4 18 6 -1. + <_> + 3 4 9 3 2. + <_> + 12 7 9 3 2. + <_> + + <_> + 5 5 16 8 -1. + <_> + 13 5 8 4 2. + <_> + 5 9 8 4 2. + <_> + + <_> + 0 13 10 6 -1. + <_> + 0 15 10 2 3. + <_> + + <_> + 8 14 9 6 -1. + <_> + 8 16 9 2 3. + <_> + + <_> + 6 2 9 6 -1. + <_> + 9 2 3 6 3. + <_> + + <_> + 14 1 10 8 -1. + <_> + 19 1 5 4 2. + <_> + 14 5 5 4 2. + <_> + + <_> + 9 1 3 12 -1. + <_> + 9 7 3 6 2. + <_> + + <_> + 6 4 12 9 -1. + <_> + 6 7 12 3 3. + <_> + + <_> + 6 5 12 6 -1. + <_> + 10 5 4 6 3. + <_> + + <_> + 1 1 8 5 -1. + <_> + 5 1 4 5 2. + <_> + + <_> + 12 12 6 8 -1. + <_> + 12 16 6 4 2. + <_> + + <_> + 3 12 12 6 -1. + <_> + 3 14 12 2 3. + <_> + + <_> + 9 18 12 6 -1. + <_> + 15 18 6 3 2. + <_> + 9 21 6 3 2. + <_> + + <_> + 4 13 6 6 -1. + <_> + 4 16 6 3 2. + <_> + + <_> + 11 3 7 18 -1. + <_> + 11 12 7 9 2. + <_> + + <_> + 3 9 18 3 -1. + <_> + 9 9 6 3 3. + <_> + + <_> + 5 3 19 2 -1. + <_> + 5 4 19 1 2. + <_> + + <_> + 4 2 12 6 -1. + <_> + 4 2 6 3 2. + <_> + 10 5 6 3 2. + <_> + + <_> + 9 6 6 9 -1. + <_> + 11 6 2 9 3. + <_> + + <_> + 8 6 6 9 -1. + <_> + 10 6 2 9 3. + <_> + + <_> + 16 9 5 15 -1. + <_> + 16 14 5 5 3. + <_> + + <_> + 3 9 5 15 -1. + <_> + 3 14 5 5 3. + <_> + + <_> + 6 6 14 6 -1. + <_> + 13 6 7 3 2. + <_> + 6 9 7 3 2. + <_> + + <_> + 8 6 3 14 -1. + <_> + 8 13 3 7 2. + <_> + + <_> + 0 16 24 5 -1. + <_> + 8 16 8 5 3. + <_> + + <_> + 0 20 20 3 -1. + <_> + 10 20 10 3 2. + <_> + + <_> + 5 10 18 2 -1. + <_> + 5 11 18 1 2. + <_> + + <_> + 0 6 6 10 -1. + <_> + 2 6 2 10 3. + <_> + + <_> + 2 1 20 3 -1. + <_> + 2 2 20 1 3. + <_> + + <_> + 9 13 6 11 -1. + <_> + 11 13 2 11 3. + <_> + + <_> + 9 15 6 8 -1. + <_> + 9 19 6 4 2. + <_> + + <_> + 9 12 6 9 -1. + <_> + 9 15 6 3 3. + <_> + + <_> + 5 11 18 2 -1. + <_> + 5 12 18 1 2. + <_> + + <_> + 2 6 15 6 -1. + <_> + 2 8 15 2 3. + <_> + + <_> + 6 0 18 3 -1. + <_> + 6 1 18 1 3. + <_> + + <_> + 5 0 3 18 -1. + <_> + 6 0 1 18 3. + <_> + + <_> + 18 3 6 10 -1. + <_> + 20 3 2 10 3. + <_> + + <_> + 0 3 6 10 -1. + <_> + 2 3 2 10 3. + <_> + + <_> + 10 5 8 9 -1. + <_> + 10 5 4 9 2. + <_> + + <_> + 6 5 8 9 -1. + <_> + 10 5 4 9 2. + <_> + + <_> + 3 2 20 3 -1. + <_> + 3 3 20 1 3. + <_> + + <_> + 5 2 13 4 -1. + <_> + 5 4 13 2 2. + <_> + + <_> + 17 0 7 14 -1. + <_> + 17 7 7 7 2. + <_> + + <_> + 0 0 7 14 -1. + <_> + 0 7 7 7 2. + <_> + + <_> + 9 11 10 6 -1. + <_> + 9 11 5 6 2. + <_> + + <_> + 5 11 10 6 -1. + <_> + 10 11 5 6 2. + <_> + + <_> + 11 6 3 18 -1. + <_> + 11 12 3 6 3. + <_> + + <_> + 0 16 18 3 -1. + <_> + 0 17 18 1 3. + <_> + + <_> + 6 16 18 3 -1. + <_> + 6 17 18 1 3. + <_> + + <_> + 4 6 9 10 -1. + <_> + 4 11 9 5 2. + <_> + + <_> + 9 7 15 4 -1. + <_> + 9 9 15 2 2. + <_> + + <_> + 5 6 12 6 -1. + <_> + 5 6 6 3 2. + <_> + 11 9 6 3 2. + <_> + + <_> + 6 1 12 9 -1. + <_> + 6 4 12 3 3. + <_> + + <_> + 7 9 6 12 -1. + <_> + 7 9 3 6 2. + <_> + 10 15 3 6 2. + <_> + + <_> + 11 5 13 6 -1. + <_> + 11 7 13 2 3. + <_> + + <_> + 1 11 22 13 -1. + <_> + 12 11 11 13 2. + <_> + + <_> + 18 8 6 6 -1. + <_> + 18 11 6 3 2. + <_> + + <_> + 0 8 6 6 -1. + <_> + 0 11 6 3 2. + <_> + + <_> + 0 6 24 3 -1. + <_> + 0 7 24 1 3. + <_> + + <_> + 0 5 10 6 -1. + <_> + 0 7 10 2 3. + <_> + + <_> + 6 7 18 3 -1. + <_> + 6 8 18 1 3. + <_> + + <_> + 0 0 10 6 -1. + <_> + 0 2 10 2 3. + <_> + + <_> + 19 0 3 19 -1. + <_> + 20 0 1 19 3. + <_> + + <_> + 4 6 12 16 -1. + <_> + 4 6 6 8 2. + <_> + 10 14 6 8 2. + <_> + + <_> + 19 6 4 18 -1. + <_> + 21 6 2 9 2. + <_> + 19 15 2 9 2. + <_> + + <_> + 1 6 4 18 -1. + <_> + 1 6 2 9 2. + <_> + 3 15 2 9 2. + <_> + + <_> + 3 21 18 3 -1. + <_> + 3 22 18 1 3. + <_> + + <_> + 0 19 9 4 -1. + <_> + 0 21 9 2 2. + <_> + + <_> + 12 18 12 6 -1. + <_> + 18 18 6 3 2. + <_> + 12 21 6 3 2. + <_> + + <_> + 7 18 9 4 -1. + <_> + 7 20 9 2 2. + <_> + + <_> + 12 16 10 8 -1. + <_> + 17 16 5 4 2. + <_> + 12 20 5 4 2. + <_> + + <_> + 2 16 10 8 -1. + <_> + 2 16 5 4 2. + <_> + 7 20 5 4 2. + <_> + + <_> + 14 0 10 12 -1. + <_> + 19 0 5 6 2. + <_> + 14 6 5 6 2. + <_> + + <_> + 0 0 10 12 -1. + <_> + 0 0 5 6 2. + <_> + 5 6 5 6 2. + <_> + + <_> + 15 14 9 6 -1. + <_> + 15 16 9 2 3. + <_> + + <_> + 0 14 9 6 -1. + <_> + 0 16 9 2 3. + <_> + + <_> + 14 14 10 6 -1. + <_> + 14 16 10 2 3. + <_> + + <_> + 0 14 10 6 -1. + <_> + 0 16 10 2 3. + <_> + + <_> + 5 18 18 2 -1. + <_> + 5 19 18 1 2. + <_> + + <_> + 0 18 18 3 -1. + <_> + 0 19 18 1 3. + <_> + + <_> + 3 5 18 12 -1. + <_> + 12 5 9 6 2. + <_> + 3 11 9 6 2. + <_> + + <_> + 5 3 7 9 -1. + <_> + 5 6 7 3 3. + <_> + + <_> + 4 0 19 15 -1. + <_> + 4 5 19 5 3. + <_> + + <_> + 3 0 16 4 -1. + <_> + 3 2 16 2 2. + <_> + + <_> + 4 12 16 12 -1. + <_> + 4 12 8 12 2. + <_> + + <_> + 4 3 12 15 -1. + <_> + 10 3 6 15 2. + <_> + + <_> + 16 4 2 19 -1. + <_> + 16 4 1 19 2. + <_> + + <_> + 6 4 2 19 -1. + <_> + 7 4 1 19 2. + <_> + + <_> + 13 14 8 10 -1. + <_> + 17 14 4 5 2. + <_> + 13 19 4 5 2. + <_> + + <_> + 3 14 8 10 -1. + <_> + 3 14 4 5 2. + <_> + 7 19 4 5 2. + <_> + + <_> + 12 6 3 18 -1. + <_> + 12 12 3 6 3. + <_> + + <_> + 5 11 12 6 -1. + <_> + 5 11 6 3 2. + <_> + 11 14 6 3 2. + <_> + + <_> + 10 5 8 10 -1. + <_> + 14 5 4 5 2. + <_> + 10 10 4 5 2. + <_> + + <_> + 6 4 12 10 -1. + <_> + 6 4 6 5 2. + <_> + 12 9 6 5 2. + <_> + + <_> + 6 8 18 10 -1. + <_> + 15 8 9 5 2. + <_> + 6 13 9 5 2. + <_> + + <_> + 0 8 18 10 -1. + <_> + 0 8 9 5 2. + <_> + 9 13 9 5 2. + <_> + + <_> + 12 6 3 18 -1. + <_> + 12 12 3 6 3. + <_> + + <_> + 0 14 18 3 -1. + <_> + 0 15 18 1 3. + <_> + + <_> + 12 6 3 18 -1. + <_> + 12 12 3 6 3. + <_> + + <_> + 9 6 3 18 -1. + <_> + 9 12 3 6 3. + <_> + + <_> + 6 14 18 3 -1. + <_> + 6 15 18 1 3. + <_> + + <_> + 0 5 18 3 -1. + <_> + 0 6 18 1 3. + <_> + + <_> + 2 5 22 3 -1. + <_> + 2 6 22 1 3. + <_> + + <_> + 0 0 21 10 -1. + <_> + 7 0 7 10 3. + <_> + + <_> + 6 3 18 17 -1. + <_> + 12 3 6 17 3. + <_> + + <_> + 0 3 18 17 -1. + <_> + 6 3 6 17 3. + <_> + + <_> + 0 12 24 11 -1. + <_> + 8 12 8 11 3. + <_> + + <_> + 4 10 16 6 -1. + <_> + 4 13 16 3 2. + <_> + + <_> + 12 8 6 8 -1. + <_> + 12 12 6 4 2. + <_> + + <_> + 6 14 8 7 -1. + <_> + 10 14 4 7 2. + <_> + + <_> + 15 10 6 14 -1. + <_> + 18 10 3 7 2. + <_> + 15 17 3 7 2. + <_> + + <_> + 3 10 6 14 -1. + <_> + 3 10 3 7 2. + <_> + 6 17 3 7 2. + <_> + + <_> + 6 12 18 2 -1. + <_> + 6 13 18 1 2. + <_> + + <_> + 5 8 10 6 -1. + <_> + 5 10 10 2 3. + <_> + + <_> + 12 11 9 4 -1. + <_> + 12 13 9 2 2. + <_> + + <_> + 0 11 9 6 -1. + <_> + 0 13 9 2 3. + <_> + + <_> + 11 2 3 18 -1. + <_> + 12 2 1 18 3. + <_> + + <_> + 10 2 3 18 -1. + <_> + 11 2 1 18 3. + <_> + + <_> + 9 12 6 10 -1. + <_> + 11 12 2 10 3. + <_> + + <_> + 1 10 6 9 -1. + <_> + 1 13 6 3 3. + <_> + + <_> + 6 9 16 6 -1. + <_> + 14 9 8 3 2. + <_> + 6 12 8 3 2. + <_> + + <_> + 1 8 9 6 -1. + <_> + 1 10 9 2 3. + <_> + + <_> + 7 7 16 6 -1. + <_> + 7 9 16 2 3. + <_> + + <_> + 0 0 18 3 -1. + <_> + 0 1 18 1 3. + <_> + + <_> + 10 0 6 9 -1. + <_> + 12 0 2 9 3. + <_> + + <_> + 9 5 6 6 -1. + <_> + 12 5 3 6 2. + <_> + + <_> + 10 6 4 18 -1. + <_> + 12 6 2 9 2. + <_> + 10 15 2 9 2. + <_> + + <_> + 8 0 6 9 -1. + <_> + 10 0 2 9 3. + <_> + + <_> + 9 1 6 9 -1. + <_> + 9 4 6 3 3. + <_> + + <_> + 1 0 18 9 -1. + <_> + 1 3 18 3 3. + <_> + + <_> + 0 3 24 3 -1. + <_> + 0 4 24 1 3. + <_> + + <_> + 6 14 9 4 -1. + <_> + 6 16 9 2 2. + <_> + + <_> + 8 9 8 10 -1. + <_> + 12 9 4 5 2. + <_> + 8 14 4 5 2. + <_> + + <_> + 5 2 13 9 -1. + <_> + 5 5 13 3 3. + <_> + + <_> + 4 4 16 9 -1. + <_> + 4 7 16 3 3. + <_> + + <_> + 4 4 14 9 -1. + <_> + 4 7 14 3 3. + <_> + + <_> + 8 5 9 6 -1. + <_> + 8 7 9 2 3. + <_> + + <_> + 1 7 16 6 -1. + <_> + 1 9 16 2 3. + <_> + + <_> + 10 5 13 9 -1. + <_> + 10 8 13 3 3. + <_> + + <_> + 1 5 13 9 -1. + <_> + 1 8 13 3 3. + <_> + + <_> + 0 4 24 6 -1. + <_> + 12 4 12 3 2. + <_> + 0 7 12 3 2. + <_> + + <_> + 1 14 10 9 -1. + <_> + 1 17 10 3 3. + <_> + + <_> + 5 17 18 3 -1. + <_> + 5 18 18 1 3. + <_> + + <_> + 0 16 18 3 -1. + <_> + 0 17 18 1 3. + <_> + + <_> + 9 17 9 6 -1. + <_> + 9 19 9 2 3. + <_> + + <_> + 1 20 22 4 -1. + <_> + 1 20 11 2 2. + <_> + 12 22 11 2 2. + <_> + + <_> + 8 14 8 6 -1. + <_> + 8 17 8 3 2. + <_> + + <_> + 8 6 8 15 -1. + <_> + 8 11 8 5 3. + <_> + + <_> + 5 4 18 3 -1. + <_> + 5 5 18 1 3. + <_> + + <_> + 9 3 5 10 -1. + <_> + 9 8 5 5 2. + <_> + + <_> + 6 8 12 3 -1. + <_> + 6 8 6 3 2. + <_> + + <_> + 2 6 18 6 -1. + <_> + 2 6 9 3 2. + <_> + 11 9 9 3 2. + <_> + + <_> + 10 6 4 18 -1. + <_> + 12 6 2 9 2. + <_> + 10 15 2 9 2. + <_> + + <_> + 7 5 6 6 -1. + <_> + 10 5 3 6 2. + <_> + + <_> + 14 5 2 18 -1. + <_> + 14 14 2 9 2. + <_> + + <_> + 8 5 2 18 -1. + <_> + 8 14 2 9 2. + <_> + + <_> + 9 2 10 6 -1. + <_> + 9 2 5 6 2. + <_> + + <_> + 3 1 18 12 -1. + <_> + 12 1 9 12 2. + <_> + + <_> + 5 2 17 22 -1. + <_> + 5 13 17 11 2. + <_> + + <_> + 4 0 12 6 -1. + <_> + 4 2 12 2 3. + <_> + + <_> + 6 9 16 6 -1. + <_> + 14 9 8 3 2. + <_> + 6 12 8 3 2. + <_> + + <_> + 9 0 5 18 -1. + <_> + 9 9 5 9 2. + <_> + + <_> + 12 0 6 9 -1. + <_> + 14 0 2 9 3. + <_> + + <_> + 6 0 6 9 -1. + <_> + 8 0 2 9 3. + <_> + + <_> + 9 1 6 12 -1. + <_> + 11 1 2 12 3. + <_> + + <_> + 5 9 13 4 -1. + <_> + 5 11 13 2 2. + <_> + + <_> + 5 8 19 3 -1. + <_> + 5 9 19 1 3. + <_> + + <_> + 9 9 6 8 -1. + <_> + 9 13 6 4 2. + <_> + + <_> + 11 9 4 15 -1. + <_> + 11 14 4 5 3. + <_> + + <_> + 2 0 6 14 -1. + <_> + 2 0 3 7 2. + <_> + 5 7 3 7 2. + <_> + + <_> + 15 1 6 14 -1. + <_> + 18 1 3 7 2. + <_> + 15 8 3 7 2. + <_> + + <_> + 3 1 6 14 -1. + <_> + 3 1 3 7 2. + <_> + 6 8 3 7 2. + <_> + + <_> + 3 20 18 4 -1. + <_> + 12 20 9 2 2. + <_> + 3 22 9 2 2. + <_> + + <_> + 5 0 4 20 -1. + <_> + 5 0 2 10 2. + <_> + 7 10 2 10 2. + <_> + + <_> + 16 8 8 12 -1. + <_> + 20 8 4 6 2. + <_> + 16 14 4 6 2. + <_> + + <_> + 0 8 8 12 -1. + <_> + 0 8 4 6 2. + <_> + 4 14 4 6 2. + <_> + + <_> + 13 13 10 8 -1. + <_> + 18 13 5 4 2. + <_> + 13 17 5 4 2. + <_> + + <_> + 1 13 10 8 -1. + <_> + 1 13 5 4 2. + <_> + 6 17 5 4 2. + <_> + + <_> + 15 8 4 15 -1. + <_> + 15 13 4 5 3. + <_> + + <_> + 5 8 4 15 -1. + <_> + 5 13 4 5 3. + <_> + + <_> + 6 11 16 12 -1. + <_> + 6 15 16 4 3. + <_> + + <_> + 2 11 16 12 -1. + <_> + 2 15 16 4 3. + <_> + + <_> + 14 12 7 9 -1. + <_> + 14 15 7 3 3. + <_> + + <_> + 10 1 3 21 -1. + <_> + 10 8 3 7 3. + <_> + + <_> + 13 11 9 4 -1. + <_> + 13 13 9 2 2. + <_> + + <_> + 3 10 17 9 -1. + <_> + 3 13 17 3 3. + <_> + + <_> + 13 8 8 15 -1. + <_> + 13 13 8 5 3. + <_> + + <_> + 3 8 8 15 -1. + <_> + 3 13 8 5 3. + <_> + + <_> + 11 14 10 8 -1. + <_> + 16 14 5 4 2. + <_> + 11 18 5 4 2. + <_> + + <_> + 0 18 22 6 -1. + <_> + 0 18 11 3 2. + <_> + 11 21 11 3 2. + <_> + + <_> + 0 16 24 4 -1. + <_> + 0 16 12 4 2. + <_> + + <_> + 6 20 12 3 -1. + <_> + 12 20 6 3 2. + <_> + + <_> + 18 12 6 12 -1. + <_> + 21 12 3 6 2. + <_> + 18 18 3 6 2. + <_> + + <_> + 0 12 6 12 -1. + <_> + 0 12 3 6 2. + <_> + 3 18 3 6 2. + <_> + + <_> + 15 17 9 6 -1. + <_> + 15 19 9 2 3. + <_> + + <_> + 1 6 22 10 -1. + <_> + 1 6 11 5 2. + <_> + 12 11 11 5 2. + <_> + + <_> + 15 17 9 6 -1. + <_> + 15 19 9 2 3. + <_> + + <_> + 0 18 18 2 -1. + <_> + 0 19 18 1 2. + <_> + + <_> + 3 15 19 3 -1. + <_> + 3 16 19 1 3. + <_> + + <_> + 0 13 18 3 -1. + <_> + 0 14 18 1 3. + <_> + + <_> + 15 17 9 6 -1. + <_> + 15 19 9 2 3. + <_> + + <_> + 0 17 9 6 -1. + <_> + 0 19 9 2 3. + <_> + + <_> + 12 17 9 6 -1. + <_> + 12 19 9 2 3. + <_> + + <_> + 3 17 9 6 -1. + <_> + 3 19 9 2 3. + <_> + + <_> + 16 2 3 20 -1. + <_> + 17 2 1 20 3. + <_> + + <_> + 0 13 24 8 -1. + <_> + 0 17 24 4 2. + <_> + + <_> + 9 1 6 22 -1. + <_> + 12 1 3 11 2. + <_> + 9 12 3 11 2. + diff --git a/exercises/static/exercises/rescue_people/my_solution.py b/exercises/static/exercises/rescue_people/my_solution.py new file mode 100644 index 000000000..2014730c2 --- /dev/null +++ b/exercises/static/exercises/rescue_people/my_solution.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python + +import rospy +import numpy as np +import cv2 +from drone_wrapper import DroneWrapper +from std_msgs.msg import Bool, Float64 +from sensor_msgs.msg import Image +from geometry_msgs.msg import Twist, Pose + +code_live_flag = False +AREA = [[7.0, -4.0], [7.0, 3.0], [-1.0, 7.0], [-7.0, 0.5], [-0.5, -7.0]] +CASCPATH = "haarcascade_frontalface_default.xml" + +def gui_play_stop_cb(msg): + global code_live_flag, code_live_timer + if msg.data == True: + if not code_live_flag: + code_live_flag = True + code_live_timer = rospy.Timer(rospy.Duration(nsecs=50000000), execute) + else: + if code_live_flag: + code_live_flag = False + code_live_timer.shutdown() + +def set_image_filtered(img): + gui_filtered_img_pub.publish(drone.bridge.cv2_to_imgmsg(img)) + +def set_image_threshed(img): + gui_threshed_img_pub.publish(drone.bridge.cv2_to_imgmsg(img)) + +def execute(event): + global drone + img_frontal = drone.get_frontal_image() + img_ventral = drone.get_ventral_image() + # Both the above images are cv2 images + ################# Insert your code here ################################# + + set_image_filtered(img_frontal) + set_image_threshed(img_ventral) + + ######################################################################### + +if __name__ == "__main__": + drone = DroneWrapper() + rospy.Subscriber('gui/play_stop', Bool, gui_play_stop_cb) + gui_filtered_img_pub = rospy.Publisher('interface/filtered_img', Image, queue_size = 1) + gui_threshed_img_pub = rospy.Publisher('interface/threshed_img', Image, queue_size = 1) + code_live_flag = False + code_live_timer = rospy.Timer(rospy.Duration(nsecs=50000000), execute) + code_live_timer.shutdown() + while not rospy.is_shutdown(): + rospy.spin() diff --git a/exercises/static/exercises/rescue_people/rescue_people.launch b/exercises/static/exercises/rescue_people/rescue_people.launch new file mode 100644 index 000000000..2a43e7841 --- /dev/null +++ b/exercises/static/exercises/rescue_people/rescue_people.launch @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/exercises/static/exercises/rescue_people/rescue_people.world b/exercises/static/exercises/rescue_people/rescue_people.world new file mode 100644 index 000000000..75f924b62 --- /dev/null +++ b/exercises/static/exercises/rescue_people/rescue_people.world @@ -0,0 +1,81 @@ + + + + + + model://face1 + 35 -35 0 0 0 -1.57 + + + model://face2 + 42 -40 0 0 0 0.57 + + + model://face3 + 32 -39 0 0 0 2.57 + + + model://face4 + 40 -34 0 0 0 0 + + + model://face5 + 25 -35 0 0 0 1.11 + + + model://face6 + 37 -31 0 0 0 -1.0 + + + + + model://boat_beacon + 0 0 0 0 0 0.785 + + + + + model://ocean + 0 0 -0.02 0 0 -1.57 + + + + 0 + 0.01 0.01 0.01 1.0 + + + 12 + + + + + + + model://sun + + + + + 0 0 -9.8066 + + + quick + 10 + 1.3 + 0 + + + 0 + 0.2 + 100 + 0.001 + + + 0.004 + 1 + 250 + 6.0e-6 2.3e-5 -4.2e-5 + + + + diff --git a/exercises/static/exercises/rescue_people/web-template/RADI-launch b/exercises/static/exercises/rescue_people/web-template/RADI-launch new file mode 100755 index 000000000..afde9c081 --- /dev/null +++ b/exercises/static/exercises/rescue_people/web-template/RADI-launch @@ -0,0 +1,2 @@ +#!/bin/sh +docker run -it --rm -p 8000:8000 -p 2303:2303 -p 1905:1905 -p 8765:8765 -p 6080:6080 -p 1108:1108 jderobot/robotics-academy:3.1.2 ./start-3.1.sh diff --git a/exercises/static/exercises/rescue_people/web-template/README.md b/exercises/static/exercises/rescue_people/web-template/README.md new file mode 100644 index 000000000..739914387 --- /dev/null +++ b/exercises/static/exercises/rescue_people/web-template/README.md @@ -0,0 +1 @@ +[Exercise Documentation Website](https://jderobot.github.io/RoboticsAcademy/exercises/Drones/rescue_people) diff --git a/exercises/static/exercises/rescue_people/web-template/code/academy.py b/exercises/static/exercises/rescue_people/web-template/code/academy.py new file mode 100644 index 000000000..7a59ce7f5 --- /dev/null +++ b/exercises/static/exercises/rescue_people/web-template/code/academy.py @@ -0,0 +1,8 @@ +# Enter sequential code! +from GUI import GUI +from HAL import HAL + +while True: + # Enter iterative code! + img = HAL.get_ventral_image() + GUI.showImage(img) \ No newline at end of file diff --git a/exercises/static/exercises/rescue_people/web-template/console.py b/exercises/static/exercises/rescue_people/web-template/console.py new file mode 100644 index 000000000..7b72a0913 --- /dev/null +++ b/exercises/static/exercises/rescue_people/web-template/console.py @@ -0,0 +1,20 @@ +# Functions to start and close console +import os +import sys + + +def start_console(): + # Get all the file descriptors and choose the latest one + fds = os.listdir("/dev/pts/") + fds.sort() + console_fd = fds[-2] + + sys.stderr = open('/dev/pts/' + console_fd, 'w') + sys.stdout = open('/dev/pts/' + console_fd, 'w') + sys.stdin = open('/dev/pts/' + console_fd, 'w') + + +def close_console(): + sys.stderr.close() + sys.stdout.close() + sys.stdin.close() diff --git a/exercises/static/exercises/rescue_people/web-template/exercise.py b/exercises/static/exercises/rescue_people/web-template/exercise.py new file mode 100644 index 000000000..6e97c15d8 --- /dev/null +++ b/exercises/static/exercises/rescue_people/web-template/exercise.py @@ -0,0 +1,361 @@ +#!/usr/bin/env python + +from __future__ import print_function + +from websocket_server import WebsocketServer +import time +import threading +import subprocess +import sys +from datetime import datetime +import re +import json +import importlib + +import rospy +from std_srvs.srv import Empty + +from gui import GUI, ThreadGUI +from hal import HAL +from console import start_console, close_console + + +class Template: + # Initialize class variables + # self.ideal_cycle to run an execution for at least 1 second + # self.process for the current running process + def __init__(self): + self.measure_thread = None + self.thread = None + self.reload = False + self.stop_brain = True + self.user_code = "" + + # Time variables + self.ideal_cycle = 80 + self.measured_cycle = 80 + self.iteration_counter = 0 + self.real_time_factor = 0 + self.frequency_message = {'brain': '', 'gui': '', 'rtf': ''} + + self.server = None + self.client = None + self.host = sys.argv[1] + + # Initialize the GUI, HAL and Console behind the scenes + self.hal = HAL() + self.gui = GUI(self.host) + + # Function to parse the code + # A few assumptions: + # 1. The user always passes sequential and iterative codes + # 2. Only a single infinite loop + def parse_code(self, source_code): + sequential_code, iterative_code = self.seperate_seq_iter(source_code) + return iterative_code, sequential_code + + # Function to separate the iterative and sequential code + def seperate_seq_iter(self, source_code): + if source_code == "": + return "", "" + + # Search for an instance of while True + infinite_loop = re.search(r'[^ ]while\s*\(\s*True\s*\)\s*:|[^ ]while\s*True\s*:|[^ ]while\s*1\s*:|[^ ]while\s*\(\s*1\s*\)\s*:', source_code) + + # Separate the content inside while True and the other + # (Separating the sequential and iterative part!) + try: + start_index = infinite_loop.start() + iterative_code = source_code[start_index:] + sequential_code = source_code[:start_index] + + # Remove while True: syntax from the code + # And remove the the 4 spaces indentation before each command + iterative_code = re.sub(r'[^ ]while\s*\(\s*True\s*\)\s*:|[^ ]while\s*True\s*:|[^ ]while\s*1\s*:|[^ ]while\s*\(\s*1\s*\)\s*:', '', iterative_code) + # Add newlines to match line on bug report + extra_lines = sequential_code.count('\n') + while (extra_lines >= 0): + iterative_code = '\n' + iterative_code + extra_lines -= 1 + iterative_code = re.sub(r'^[ ]{4}', '', iterative_code, flags=re.M) + + except: + sequential_code = source_code + iterative_code = "" + + return sequential_code, iterative_code + + # The process function + def process_code(self, source_code): + # Redirect the information to console + start_console() + + iterative_code, sequential_code = self.parse_code(source_code) + + # print(sequential_code) + # print(iterative_code) + + # The Python exec function + # Run the sequential part + gui_module, hal_module = self.generate_modules() + reference_environment = {"GUI": gui_module, "HAL": hal_module} + while (self.stop_brain == True): + if (self.reload == True): + return + time.sleep(0.1) + exec(sequential_code, reference_environment) + + # Run the iterative part inside template + # and keep the check for flag + while self.reload == False: + while (self.stop_brain == True): + if (self.reload == True): + return + time.sleep(0.1) + + start_time = datetime.now() + + # Execute the iterative portion + exec(iterative_code, reference_environment) + + # Template specifics to run! + finish_time = datetime.now() + dt = finish_time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + + # Keep updating the iteration counter + if (iterative_code == ""): + self.iteration_counter = 0 + else: + self.iteration_counter = self.iteration_counter + 1 + + # The code should be run for atleast the target time step + # If it's less put to sleep + if (ms < self.ideal_cycle): + time.sleep((self.ideal_cycle - ms) / 1000.0) + + close_console() + print("Current Thread Joined!") + + # Function to generate the modules for use in ACE Editor + def generate_modules(self): + # Define HAL module + hal_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) + hal_module.HAL = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) + # hal_module.drone = imp.new_module("drone") + # motors# hal_module.HAL.motors = imp.new_module("motors") + + # Add HAL functions + hal_module.HAL.get_frontal_image = self.hal.get_frontal_image + hal_module.HAL.get_ventral_image = self.hal.get_ventral_image + hal_module.HAL.get_position = self.hal.get_position + hal_module.HAL.get_velocity = self.hal.get_velocity + hal_module.HAL.get_yaw_rate = self.hal.get_yaw_rate + hal_module.HAL.get_orientation = self.hal.get_orientation + hal_module.HAL.get_roll = self.hal.get_roll + hal_module.HAL.get_pitch = self.hal.get_pitch + hal_module.HAL.get_yaw = self.hal.get_yaw + hal_module.HAL.get_landed_state = self.hal.get_landed_state + hal_module.HAL.set_cmd_pos = self.hal.set_cmd_pos + hal_module.HAL.set_cmd_vel = self.hal.set_cmd_vel + hal_module.HAL.set_cmd_mix = self.hal.set_cmd_mix + hal_module.HAL.takeoff = self.hal.takeoff + hal_module.HAL.land = self.hal.land + + # Define GUI module + gui_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) + gui_module.GUI = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) + + # Add GUI functions + gui_module.GUI.showImage = self.gui.showImage + gui_module.GUI.showLeftImage = self.gui.showLeftImage + + # Adding modules to system + # Protip: The names should be different from + # other modules, otherwise some errors + sys.modules["HAL"] = hal_module + sys.modules["GUI"] = gui_module + + return gui_module, hal_module + + # Function to measure the frequency of iterations + def measure_frequency(self): + previous_time = datetime.now() + # An infinite loop + while True: + # Sleep for 2 seconds + time.sleep(2) + + # Measure the current time and subtract from the previous time to get real time interval + current_time = datetime.now() + dt = current_time - previous_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + previous_time = current_time + + # Get the time period + try: + # Division by zero + self.measured_cycle = ms / self.iteration_counter + except: + self.measured_cycle = 0 + + # Reset the counter + self.iteration_counter = 0 + + # Send to client + self.send_frequency_message() + + # Function to generate and send frequency messages + def send_frequency_message(self): + # This function generates and sends frequency measures of the brain and gui + brain_frequency = 0; gui_frequency = 0 + try: + brain_frequency = round(1000 / self.measured_cycle, 1) + except ZeroDivisionError: + brain_frequency = 0 + + try: + gui_frequency = round(1000 / self.thread_gui.measured_cycle, 1) + except ZeroDivisionError: + gui_frequency = 0 + + self.frequency_message["brain"] = brain_frequency + self.frequency_message["gui"] = gui_frequency + self.frequency_message["rtf"] = self.real_time_factor + + message = "#freq" + json.dumps(self.frequency_message) + self.server.send_message(self.client, message) + + def send_ping_message(self): + self.server.send_message(self.client, "#ping") + + # Function to notify the front end that the code was received and sent to execution + def send_code_message(self): + self.server.send_message(self.client, "#exec") + + # Function to track the real time factor from Gazebo statistics + # https://stackoverflow.com/a/17698359 + # (For reference, Python3 solution specified in the same answer) + def track_stats(self): + args = ["gz", "stats", "-p"] + # Prints gz statistics. "-p": Output comma-separated values containing- + # real-time factor (percent), simtime (sec), realtime (sec), paused (T or F) + stats_process = subprocess.Popen(args, stdout=subprocess.PIPE, bufsize=1, universal_newlines=True) + # bufsize=1 enables line-bufferred mode (the input buffer is flushed + # automatically on newlines if you would write to process.stdin ) + with stats_process.stdout: + for line in iter(stats_process.stdout.readline, ''): + stats_list = [x.strip() for x in line.split(',')] + self.real_time_factor = stats_list[0] + + # Function to maintain thread execution + def execute_thread(self, source_code): + # Keep checking until the thread is alive + # The thread will die when the coming iteration reads the flag + if self.thread is not None: + while self.thread.is_alive(): + time.sleep(0.2) + + # Turn the flag down, the iteration has successfully stopped! + self.reload = False + # New thread execution + self.thread = threading.Thread(target=self.process_code, args=[source_code]) + self.thread.start() + self.send_code_message() + print("New Thread Started!") + + # Function to read and set frequency from incoming message + def read_frequency_message(self, message): + frequency_message = json.loads(message) + + # Set brain frequency + frequency = float(frequency_message["brain"]) + self.ideal_cycle = 1000.0 / frequency + + # Set gui frequency + frequency = float(frequency_message["gui"]) + self.thread_gui.ideal_cycle = 1000.0 / frequency + + return + + # The websocket function + # Gets called when there is an incoming message from the client + def handle(self, client, server, message): + if message[:5] == "#freq": + frequency_message = message[5:] + self.read_frequency_message(frequency_message) + time.sleep(1) + return + + elif(message[:5] == "#ping"): + time.sleep(1) + self.send_ping_message() + return + elif (message[:5] == "#code"): + try: + # Once received turn the reload flag up and send it to execute_thread function + self.user_code = message[6:] + # print(repr(code)) + self.reload = True + self.execute_thread(self.user_code) + except: + pass + + elif (message[:5] == "#rest"): + try: + self.reload = True + self.stop_brain = True + self.execute_thread(self.user_code) + except: + pass + + elif (message[:5] == "#stop"): + self.stop_brain = True + + elif (message[:5] == "#play"): + self.stop_brain = False + + # Function that gets called when the server is connected + def connected(self, client, server): + self.client = client + # Start the GUI update thread + self.thread_gui = ThreadGUI(self.gui) + self.thread_gui.start() + + # Start the real time factor tracker thread + self.stats_thread = threading.Thread(target=self.track_stats) + self.stats_thread.start() + + # Start measure frequency + self.measure_thread = threading.Thread(target=self.measure_frequency) + self.measure_thread.start() + + print(client, 'connected') + + # Function that gets called when the connected closes + def handle_close(self, client, server): + print(client, 'closed') + + def run_server(self): + self.server = WebsocketServer(port=1905, host=self.host) + self.server.set_fn_new_client(self.connected) + self.server.set_fn_client_left(self.handle_close) + self.server.set_fn_message_received(self.handle) + + logged = False + while not logged: + try: + f = open("/ws_code.log", "w") + f.write("websocket_code=ready") + f.close() + logged = True + except: + time.sleep(0.1) + + self.server.run_forever() + + +# Execute! +if __name__ == "__main__": + server = Template() + server.run_server() \ No newline at end of file diff --git a/exercises/static/exercises/rescue_people/web-template/gui.py b/exercises/static/exercises/rescue_people/web-template/gui.py new file mode 100644 index 000000000..51d327f1a --- /dev/null +++ b/exercises/static/exercises/rescue_people/web-template/gui.py @@ -0,0 +1,248 @@ +import json +import cv2 +import base64 +import threading +import time +from datetime import datetime +from websocket_server import WebsocketServer + + +# Graphical User Interface Class +class GUI: + # Initialization function + # The actual initialization + def __init__(self, host): + t = threading.Thread(target=self.run_server) + + self.payload = {'image': ''} + self.left_payload = {'image': ''} + self.server = None + self.client = None + + self.host = host + + # Image variables + self.image_to_be_shown = None + self.image_to_be_shown_updated = False + self.image_show_lock = threading.Lock() + + self.left_image_to_be_shown = None + self.left_image_to_be_shown_updated = False + self.left_image_show_lock = threading.Lock() + + self.acknowledge = False + self.acknowledge_lock = threading.Lock() + + # Take the console object to set the same websocket and client + t.start() + + # Explicit initialization function + # Class method, so user can call it without instantiation + @classmethod + def initGUI(cls, host): + # self.payload = {'image': '', 'shape': []} + new_instance = cls(host) + return new_instance + + # Function to prepare image payload + # Encodes the image as a JSON string and sends through the WS + def payloadImage(self): + self.image_show_lock.acquire() + image_to_be_shown_updated = self.image_to_be_shown_updated + image_to_be_shown = self.image_to_be_shown + self.image_show_lock.release() + + image = image_to_be_shown + payload = {'image': '', 'shape': ''} + + if not image_to_be_shown_updated: + return payload + + shape = image.shape + frame = cv2.imencode('.JPEG', image)[1] + encoded_image = base64.b64encode(frame) + + payload['image'] = encoded_image.decode('utf-8') + payload['shape'] = shape + + self.image_show_lock.acquire() + self.image_to_be_shown_updated = False + self.image_show_lock.release() + + return payload + + # Function to prepare image payload + # Encodes the image as a JSON string and sends through the WS + def payloadLeftImage(self): + self.left_image_show_lock.acquire() + left_image_to_be_shown_updated = self.left_image_to_be_shown_updated + left_image_to_be_shown = self.left_image_to_be_shown + self.left_image_show_lock.release() + + image = left_image_to_be_shown + payload = {'image': '', 'shape': ''} + + if not left_image_to_be_shown_updated: + return payload + + shape = image.shape + frame = cv2.imencode('.JPEG', image)[1] + encoded_image = base64.b64encode(frame) + + payload['image'] = encoded_image.decode('utf-8') + payload['shape'] = shape + + self.left_image_show_lock.acquire() + self.left_image_to_be_shown_updated = False + self.left_image_show_lock.release() + + return payload + + # Function for student to call + def showImage(self, image): + self.image_show_lock.acquire() + self.image_to_be_shown = image + self.image_to_be_shown_updated = True + self.image_show_lock.release() + + # Function for student to call + def showLeftImage(self, image): + self.left_image_show_lock.acquire() + self.left_image_to_be_shown = image + self.left_image_to_be_shown_updated = True + self.left_image_show_lock.release() + + # Function to get the client + # Called when a new client is received + def get_client(self, client, server): + self.client = client + + # Function to get value of Acknowledge + def get_acknowledge(self): + self.acknowledge_lock.acquire() + acknowledge = self.acknowledge + self.acknowledge_lock.release() + + return acknowledge + + # Function to get value of Acknowledge + def set_acknowledge(self, value): + self.acknowledge_lock.acquire() + self.acknowledge = value + self.acknowledge_lock.release() + + # Update the gui + def update_gui(self): + # Payload Image Message + payload = self.payloadImage() + self.payload["image"] = json.dumps(payload) + + message = "#gui" + json.dumps(self.payload) + self.server.send_message(self.client, message) + + # Payload Left Image Message + left_payload = self.payloadLeftImage() + self.left_payload["image"] = json.dumps(left_payload) + + message = "#gul" + json.dumps(self.left_payload) + self.server.send_message(self.client, message) + + # Function to read the message from websocket + # Gets called when there is an incoming message from the client + def get_message(self, client, server, message): + # Acknowledge Message for GUI Thread + if message[:4] == "#ack": + self.set_acknowledge(True) + + # Activate the server + def run_server(self): + self.server = WebsocketServer(port=2303, host=self.host) + self.server.set_fn_new_client(self.get_client) + self.server.set_fn_message_received(self.get_message) + + logged = False + while not logged: + try: + f = open("/ws_gui.log", "w") + f.write("websocket_gui=ready") + f.close() + logged = True + except: + time.sleep(0.1) + + self.server.run_forever() + + # Function to reset + def reset_gui(self): + pass + + +# This class decouples the user thread +# and the GUI update thread +class ThreadGUI: + def __init__(self, gui): + self.gui = gui + + # Time variables + self.ideal_cycle = 80 + self.measured_cycle = 80 + self.iteration_counter = 0 + + # Function to start the execution of threads + def start(self): + self.measure_thread = threading.Thread(target=self.measure_thread) + self.thread = threading.Thread(target=self.run) + + self.measure_thread.start() + self.thread.start() + + print("GUI Thread Started!") + + # The measuring thread to measure frequency + def measure_thread(self): + while self.gui.client is None: + pass + + previous_time = datetime.now() + while True: + # Sleep for 2 seconds + time.sleep(2) + + # Measure the current time and subtract from previous time to get real time interval + current_time = datetime.now() + dt = current_time - previous_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + previous_time = current_time + + # Get the time period + try: + # Division by zero + self.measured_cycle = ms / self.iteration_counter + except: + self.measured_cycle = 0 + + # Reset the counter + self.iteration_counter = 0 + + # The main thread of execution + def run(self): + while self.gui.client is None: + pass + + while True: + start_time = datetime.now() + self.gui.update_gui() + acknowledge_message = self.gui.get_acknowledge() + + while not acknowledge_message: + acknowledge_message = self.gui.get_acknowledge() + + self.gui.set_acknowledge(False) + + finish_time = datetime.now() + self.iteration_counter = self.iteration_counter + 1 + + dt = finish_time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + if ms < self.ideal_cycle: + time.sleep((self.ideal_cycle-ms) / 1000.0) diff --git a/exercises/static/exercises/rescue_people/web-template/hal.py b/exercises/static/exercises/rescue_people/web-template/hal.py new file mode 100644 index 000000000..25635a119 --- /dev/null +++ b/exercises/static/exercises/rescue_people/web-template/hal.py @@ -0,0 +1,82 @@ +import numpy as np +import rospy +import cv2 + +from drone_wrapper import DroneWrapper + + +# Hardware Abstraction Layer +class HAL: + IMG_WIDTH = 320 + IMG_HEIGHT = 240 + + def __init__(self): + rospy.init_node("HAL") + + self.image = None + self.drone = DroneWrapper(name="rqt") + + # Explicit initialization functions + # Class method, so user can call it without instantiation + @classmethod + def initRobot(cls): + new_instance = cls() + return new_instance + + # Get Image from ROS Driver Camera + def get_frontal_image(self): + image = self.drone.get_frontal_image() + image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) + return image_rgb + + def get_ventral_image(self): + image = self.drone.get_ventral_image() + image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) + return image_rgb + + def get_position(self): + pos = self.drone.get_position() + return pos + + def get_velocity(self): + vel = self.drone.get_velocity() + return vel + + def get_yaw_rate(self): + yaw_rate = self.drone.get_yaw_rate() + return yaw_rate + + def get_orientation(self): + orientation = self.drone.get_orientation() + return orientation + + def get_roll(self): + roll = self.drone.get_roll() + return roll + + def get_pitch(self): + pitch = self.drone.get_pitch() + return pitch + + def get_yaw(self): + yaw = self.drone.get_yaw() + return yaw + + def get_landed_state(self): + state = self.drone.get_landed_state() + return state + + def set_cmd_pos(self, x, y, z, az): + self.drone.set_cmd_pos(x, y, z, az) + + def set_cmd_vel(self, vx, vy, vz, az): + self.drone.set_cmd_vel(vx, vy, vz, az) + + def set_cmd_mix(self, vx, vy, z, az): + self.drone.set_cmd_mix(vx, vy, z, az) + + def takeoff(self, h=3): + self.drone.takeoff(h) + + def land(self): + self.drone.land() diff --git a/exercises/static/exercises/rescue_people/web-template/interfaces/__init__.py b/exercises/static/exercises/rescue_people/web-template/interfaces/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/exercises/static/exercises/rescue_people/web-template/interfaces/camera.py b/exercises/static/exercises/rescue_people/web-template/interfaces/camera.py new file mode 100644 index 000000000..5a021a13e --- /dev/null +++ b/exercises/static/exercises/rescue_people/web-template/interfaces/camera.py @@ -0,0 +1,89 @@ +import rospy +from sensor_msgs.msg import Image as ImageROS +import threading +from math import pi as PI +import cv2 +from cv_bridge import CvBridge, CvBridgeError + + +MAXRANGE = 8 # max length received from imageD +MINRANGE = 0 + + +def imageMsg2Image(img, bridge): + + image = Image() + + image.width = img.width + image.height = img.height + image.format = "BGR8" + image.timeStamp = img.header.stamp.secs + (img.header.stamp.nsecs * 1e-9) + cv_image = 0 + if img.encoding[-2:] == "C1": + gray_img_buff = bridge.imgmsg_to_cv2(img, img.encoding) + cv_image = depthToRGB8(gray_img_buff, img.encoding) + else: + cv_image = bridge.imgmsg_to_cv2(img, "bgr8") + image.data = cv_image + return image + + +import numpy as np + + +class Image: + + def __init__(self): + + self.height = 3 # Image height [pixels] + self.width = 3 # Image width [pixels] + self.timeStamp = 0 # Time stamp [s] */ + self.format = "" # Image format string (RGB8, BGR,...) + self.data = np.zeros((self.height, self.width, 3), np.uint8) # The image data itself + self.data.shape = self.height, self.width, 3 + + def __str__(self): + s = "Image: {\n height: " + str(self.height) + "\n width: " + str(self.width) + s = s + "\n format: " + self.format + "\n timeStamp: " + str(self.timeStamp) + s = s + "\n data: " + str(self.data) + "\n}" + return s + + +class ListenerCamera: + + def __init__(self, topic): + + self.topic = topic + self.data = Image() + self.sub = None + self.lock = threading.Lock() + + self.bridge = CvBridge() + self.start() + + def __callback(self, img): + + image = imageMsg2Image(img, self.bridge) + + self.lock.acquire() + self.data = image + self.lock.release() + + def stop(self): + + self.sub.unregister() + + def start(self): + self.sub = rospy.Subscriber(self.topic, ImageROS, self.__callback) + + def getImage(self): + + self.lock.acquire() + image = self.data + self.lock.release() + + return image + + def hasproxy(self): + + return hasattr(self, "sub") and self.sub diff --git a/exercises/static/exercises/rescue_people/web-template/interfaces/motors.py b/exercises/static/exercises/rescue_people/web-template/interfaces/motors.py new file mode 100644 index 000000000..70dca8a46 --- /dev/null +++ b/exercises/static/exercises/rescue_people/web-template/interfaces/motors.py @@ -0,0 +1,123 @@ +import rospy +from geometry_msgs.msg import Twist +import threading +from math import pi as PI +from .threadPublisher import ThreadPublisher + + + +def cmdvel2Twist(vel): + + tw = Twist() + tw.linear.x = vel.vx + tw.linear.y = vel.vy + tw.linear.z = vel.vz + tw.angular.x = vel.ax + tw.angular.y = vel.ay + tw.angular.z = vel.az + + return tw + + +class CMDVel (): + + def __init__(self): + + self.vx = 0 # vel in x[m/s] (use this for V in wheeled robots) + self.vy = 0 # vel in y[m/s] + self.vz = 0 # vel in z[m/s] + self.ax = 0 # angular vel in X axis [rad/s] + self.ay = 0 # angular vel in X axis [rad/s] + self.az = 0 # angular vel in Z axis [rad/s] (use this for W in wheeled robots) + self.timeStamp = 0 # Time stamp [s] + + + def __str__(self): + s = "CMDVel: {\n vx: " + str(self.vx) + "\n vy: " + str(self.vy) + s = s + "\n vz: " + str(self.vz) + "\n ax: " + str(self.ax) + s = s + "\n ay: " + str(self.ay) + "\n az: " + str(self.az) + s = s + "\n timeStamp: " + str(self.timeStamp) + "\n}" + return s + +class PublisherMotors: + + def __init__(self, topic, maxV, maxW): + + self.maxW = maxW + self.maxV = maxV + + self.topic = topic + self.data = CMDVel() + self.pub = rospy.Publisher(self.topic, Twist, queue_size=1) + + self.lock = threading.Lock() + + self.kill_event = threading.Event() + self.thread = ThreadPublisher(self, self.kill_event) + + self.thread.daemon = True + self.start() + + def publish (self): + + self.lock.acquire() + tw = cmdvel2Twist(self.data) + self.lock.release() + self.pub.publish(tw) + + def stop(self): + + self.kill_event.set() + self.pub.unregister() + + def start (self): + + self.kill_event.clear() + self.thread.start() + + + + def getMaxW(self): + return self.maxW + + def getMaxV(self): + return self.maxV + + + def sendVelocities(self, vel): + + self.lock.acquire() + self.data = vel + self.lock.release() + + def sendV(self, v): + + self.sendVX(v) + + def sendL(self, l): + + self.sendVY(l) + + def sendW(self, w): + + self.sendAZ(w) + + def sendVX(self, vx): + + self.lock.acquire() + self.data.vx = vx + self.lock.release() + + def sendVY(self, vy): + + self.lock.acquire() + self.data.vy = vy + self.lock.release() + + def sendAZ(self, az): + + self.lock.acquire() + self.data.az = az + self.lock.release() + + diff --git a/exercises/static/exercises/rescue_people/web-template/interfaces/pose3d.py b/exercises/static/exercises/rescue_people/web-template/interfaces/pose3d.py new file mode 100644 index 000000000..fd0bfc37a --- /dev/null +++ b/exercises/static/exercises/rescue_people/web-template/interfaces/pose3d.py @@ -0,0 +1,176 @@ +import rospy +import threading +from math import asin, atan2, pi +from nav_msgs.msg import Odometry + +def quat2Yaw(qw, qx, qy, qz): + ''' + Translates from Quaternion to Yaw. + + @param qw,qx,qy,qz: Quaternion values + + @type qw,qx,qy,qz: float + + @return Yaw value translated from Quaternion + + ''' + rotateZa0=2.0*(qx*qy + qw*qz) + rotateZa1=qw*qw + qx*qx - qy*qy - qz*qz + rotateZ=0.0 + if(rotateZa0 != 0.0 and rotateZa1 != 0.0): + rotateZ=atan2(rotateZa0,rotateZa1) + return rotateZ + +def quat2Pitch(qw, qx, qy, qz): + ''' + Translates from Quaternion to Pitch. + + @param qw,qx,qy,qz: Quaternion values + + @type qw,qx,qy,qz: float + + @return Pitch value translated from Quaternion + + ''' + + rotateYa0=-2.0*(qx*qz - qw*qy) + rotateY=0.0 + if(rotateYa0 >= 1.0): + rotateY = pi/2.0 + elif(rotateYa0 <= -1.0): + rotateY = -pi/2.0 + else: + rotateY = asin(rotateYa0) + + return rotateY + +def quat2Roll (qw, qx, qy, qz): + ''' + Translates from Quaternion to Roll. + + @param qw,qx,qy,qz: Quaternion values + + @type qw,qx,qy,qz: float + + @return Roll value translated from Quaternion + + ''' + rotateXa0=2.0*(qy*qz + qw*qx) + rotateXa1=qw*qw - qx*qx - qy*qy + qz*qz + rotateX=0.0 + + if(rotateXa0 != 0.0 and rotateXa1 != 0.0): + rotateX=atan2(rotateXa0, rotateXa1) + return rotateX + + +def odometry2Pose3D(odom): + ''' + Translates from ROS Odometry to JderobotTypes Pose3d. + + @param odom: ROS Odometry to translate + + @type odom: Odometry + + @return a Pose3d translated from odom + + ''' + pose = Pose3d() + ori = odom.pose.pose.orientation + + pose.x = odom.pose.pose.position.x + pose.y = odom.pose.pose.position.y + pose.z = odom.pose.pose.position.z + #pose.h = odom.pose.pose.position.h + pose.yaw = quat2Yaw(ori.w, ori.x, ori.y, ori.z) + pose.pitch = quat2Pitch(ori.w, ori.x, ori.y, ori.z) + pose.roll = quat2Roll(ori.w, ori.x, ori.y, ori.z) + pose.q = [ori.w, ori.x, ori.y, ori.z] + pose.timeStamp = odom.header.stamp.secs + (odom.header.stamp.nsecs *1e-9) + + return pose + +class Pose3d (): + + def __init__(self): + + self.x = 0 # X coord [meters] + self.y = 0 # Y coord [meters] + self.z = 0 # Z coord [meters] + self.h = 1 # H param + self.yaw = 0 #Yaw angle[rads] + self.pitch = 0 # Pitch angle[rads] + self.roll = 0 # Roll angle[rads] + self.q = [0,0,0,0] # Quaternion + self.timeStamp = 0 # Time stamp [s] + + + def __str__(self): + s = "Pose3D: {\n x: " + str(self.x) + "\n Y: " + str(self.y) + s = s + "\n Z: " + str(self.z) + "\n H: " + str(self.h) + s = s + "\n Yaw: " + str(self.yaw) + "\n Pitch: " + str(self.pitch) + "\n Roll: " + str(self.roll) + s = s + "\n quaternion: " + str(self.q) + "\n timeStamp: " + str(self.timeStamp) + "\n}" + return s + + +class ListenerPose3d: + ''' + ROS Pose3D Subscriber. Pose3D Client to Receive pose3d from ROS nodes. + ''' + def __init__(self, topic): + ''' + ListenerPose3d Constructor. + + @param topic: ROS topic to subscribe + + @type topic: String + + ''' + self.topic = topic + self.data = Pose3d() + self.sub = None + self.lock = threading.Lock() + self.start() + + def __callback (self, odom): + ''' + Callback function to receive and save Pose3d. + + @param odom: ROS Odometry received + + @type odom: Odometry + + ''' + pose = odometry2Pose3D(odom) + + self.lock.acquire() + self.data = pose + self.lock.release() + + def stop(self): + ''' + Stops (Unregisters) the client. + + ''' + self.sub.unregister() + + def start (self): + ''' + Starts (Subscribes) the client. + + ''' + self.sub = rospy.Subscriber(self.topic, Odometry, self.__callback) + + def getPose3d(self): + ''' + Returns last Pose3d. + + @return last JdeRobotTypes Pose3d saved + + ''' + self.lock.acquire() + pose = self.data + self.lock.release() + + return pose + diff --git a/exercises/static/exercises/rescue_people/web-template/interfaces/threadPublisher.py b/exercises/static/exercises/rescue_people/web-template/interfaces/threadPublisher.py new file mode 100644 index 000000000..69aa0ad48 --- /dev/null +++ b/exercises/static/exercises/rescue_people/web-template/interfaces/threadPublisher.py @@ -0,0 +1,46 @@ +# +# Copyright (C) 1997-2016 JDE Developers Team +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. +# Authors : +# Alberto Martin Florido +# Aitor Martinez Fernandez +# +import threading +import time +from datetime import datetime + +time_cycle = 80 + + +class ThreadPublisher(threading.Thread): + + def __init__(self, pub, kill_event): + self.pub = pub + self.kill_event = kill_event + threading.Thread.__init__(self, args=kill_event) + + def run(self): + while (not self.kill_event.is_set()): + start_time = datetime.now() + + self.pub.publish() + + finish_Time = datetime.now() + + dt = finish_Time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + #print (ms) + if (ms < time_cycle): + time.sleep((time_cycle - ms) / 1000.0) \ No newline at end of file diff --git a/exercises/static/exercises/rescue_people/web-template/interfaces/threadStoppable.py b/exercises/static/exercises/rescue_people/web-template/interfaces/threadStoppable.py new file mode 100644 index 000000000..b631d180f --- /dev/null +++ b/exercises/static/exercises/rescue_people/web-template/interfaces/threadStoppable.py @@ -0,0 +1,36 @@ +import threading +import time +from datetime import datetime + +time_cycle = 80 + + +class StoppableThread(threading.Thread): + """Thread class with a stop() method. The thread itself has to check + regularly for the stopped() condition.""" + + def __init__(self, target, kill_event=threading.Event(), *args, **kwargs): + super(StoppableThread, self).__init__(*args, **kwargs) + self._target = target + self._target_args = kwargs["args"] + self._kill_event = kill_event + + def run(self): + while not self.stopped(): + start_time = datetime.now() + + self._target(*self._target_args) + + finish_time = datetime.now() + + dt = finish_time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + # print (ms) + if ms < time_cycle: + time.sleep((time_cycle - ms) / 1000.0) + + def stop(self): + self._kill_event.set() + + def stopped(self): + return self._kill_event.is_set() diff --git a/exercises/static/exercises/rescue_people/web-template/launch/gazebo.launch b/exercises/static/exercises/rescue_people/web-template/launch/gazebo.launch new file mode 100644 index 000000000..b959ebdc7 --- /dev/null +++ b/exercises/static/exercises/rescue_people/web-template/launch/gazebo.launch @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/exercises/static/exercises/rescue_people/web-template/launch/launch.py b/exercises/static/exercises/rescue_people/web-template/launch/launch.py new file mode 100644 index 000000000..dade2943c --- /dev/null +++ b/exercises/static/exercises/rescue_people/web-template/launch/launch.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python3 + +import stat +import rospy +from os import lstat +from subprocess import Popen, PIPE + + +DRI_PATH = "/dev/dri/card0" +EXERCISE = "rescue_people" +TIMEOUT = 30 +MAX_ATTEMPT = 2 + + +# Check if acceleration can be enabled +def check_device(device_path): + try: + return stat.S_ISCHR(lstat(device_path)[stat.ST_MODE]) + except: + return False + + +# Spawn new process +def spawn_process(args, insert_vglrun=False): + if insert_vglrun: + args.insert(0, "vglrun") + process = Popen(args, stdout=PIPE, bufsize=1, universal_newlines=True) + return process + + +class Test(): + def gazebo(self): + rospy.logwarn("[GAZEBO] Launching") + try: + rospy.wait_for_service("/gazebo/get_model_properties", TIMEOUT) + return True + except rospy.ROSException: + return False + + def px4(self): + rospy.logwarn("[PX4-SITL] Launching") + start_time = rospy.get_time() + args = ["./PX4-Autopilot/build/px4_sitl_default/bin/px4-commander","--instance", "0", "check"] + while rospy.get_time() - start_time < TIMEOUT: + process = spawn_process(args, insert_vglrun=False) + with process.stdout: + for line in iter(process.stdout.readline, ''): + if ("Prearm check: OK" in line): + return True + rospy.sleep(2) + return False + + def mavros(self, ns=""): + rospy.logwarn("[MAVROS] Launching") + try: + rospy.wait_for_service(ns + "/mavros/cmd/arming", TIMEOUT) + return True + except rospy.ROSException: + return False + + +class Launch(): + def __init__(self): + self.test = Test() + self.acceleration_enabled = check_device(DRI_PATH) + + # Start roscore + args = ["/opt/ros/noetic/bin/roscore"] + spawn_process(args, insert_vglrun=False) + + rospy.init_node("launch", anonymous=True) + + def start(self): + ######## LAUNCH GAZEBO ######## + args = ["/opt/ros/noetic/bin/roslaunch", + "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/gazebo.launch", + "--wait", + "--log" + ] + + attempt = 1 + while True: + spawn_process(args, insert_vglrun=self.acceleration_enabled) + if self.test.gazebo() == True: + break + if attempt == MAX_ATTEMPT: + rospy.logerr("[GAZEBO] Launch Failed") + return + attempt = attempt + 1 + + + ######## LAUNCH PX4 ######## + args = ["/opt/ros/noetic/bin/roslaunch", + "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/px4.launch", + "--log" + ] + + attempt = 1 + while True: + spawn_process(args, insert_vglrun=self.acceleration_enabled) + if self.test.px4() == True: + break + if attempt == MAX_ATTEMPT: + rospy.logerr("[PX4] Launch Failed") + return + attempt = attempt + 1 + + + ######## LAUNCH MAVROS ######## + args = ["/opt/ros/noetic/bin/roslaunch", + "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/mavros.launch", + "--log" + ] + + attempt = 1 + while True: + spawn_process(args, insert_vglrun=self.acceleration_enabled) + if self.test.mavros() == True: + break + if attempt == MAX_ATTEMPT: + rospy.logerr("[MAVROS] Launch Failed") + return + attempt = attempt + 1 + + +if __name__ == "__main__": + launch = Launch() + launch.start() + + with open("/drones_launch.log", "w") as f: + f.write("success") \ No newline at end of file diff --git a/exercises/static/exercises/rescue_people/web-template/launch/mavros.launch b/exercises/static/exercises/rescue_people/web-template/launch/mavros.launch new file mode 100644 index 000000000..b899c0ec1 --- /dev/null +++ b/exercises/static/exercises/rescue_people/web-template/launch/mavros.launch @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/exercises/static/exercises/rescue_people/web-template/launch/px4.launch b/exercises/static/exercises/rescue_people/web-template/launch/px4.launch new file mode 100644 index 000000000..bb3f656b5 --- /dev/null +++ b/exercises/static/exercises/rescue_people/web-template/launch/px4.launch @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/exercises/static/exercises/rescue_people/web-template/rescue_people.world b/exercises/static/exercises/rescue_people/web-template/rescue_people.world new file mode 100644 index 000000000..957ba13c1 --- /dev/null +++ b/exercises/static/exercises/rescue_people/web-template/rescue_people.world @@ -0,0 +1,83 @@ + + + + + + + + model://face1 + 35 -35 0 0 0 -1.57 + + + model://face2 + 42 -40 0 0 0 0.57 + + + model://face3 + 32 -39 0 0 0 2.57 + + + model://face4 + 40 -34 0 0 0 0 + + + model://face5 + 25 -35 0 0 0 1.11 + + + model://face6 + 37 -31 0 0 0 -1.0 + + + + + model://boat_beacon + 0 0 0 0 0 0.785 + + + + + model://ocean + 0 0 -0.02 0 0 -1.57 + + + + 0 + 0.01 0.01 0.01 1.0 + + + 12 + + + + + + + model://sun + + + + + 0 0 -9.8066 + + + quick + 10 + 1.3 + 0 + + + 0 + 0.2 + 100 + 0.001 + + + 0.004 + 1 + 250 + 6.0e-6 2.3e-5 -4.2e-5 + + + + \ No newline at end of file diff --git a/exercises/static/exercises/visual_lander/README.md b/exercises/static/exercises/visual_lander/README.md new file mode 100644 index 000000000..a386ca16a --- /dev/null +++ b/exercises/static/exercises/visual_lander/README.md @@ -0,0 +1 @@ +[Exercise Documentation Website](https://jderobot.github.io/RoboticsAcademy/exercises/Drones/visual_lander) diff --git a/exercises/static/exercises/visual_lander/my_solution.py b/exercises/static/exercises/visual_lander/my_solution.py new file mode 100644 index 000000000..05d74d9d1 --- /dev/null +++ b/exercises/static/exercises/visual_lander/my_solution.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python + +import rospy +import numpy as np +import cv2 +from drone_wrapper import DroneWrapper +from std_msgs.msg import Bool, Float64 +from sensor_msgs.msg import Image +from geometry_msgs.msg import Twist, Pose + +code_live_flag = False + +def gui_play_stop_cb(msg): + global code_live_flag, code_live_timer + if msg.data == True: + if not code_live_flag: + code_live_flag = True + code_live_timer = rospy.Timer(rospy.Duration(nsecs=50000000), execute) + else: + if code_live_flag: + code_live_flag = False + code_live_timer.shutdown() + +def set_image_filtered(img): + gui_filtered_img_pub.publish(drone.bridge.cv2_to_imgmsg(img)) + +def set_image_threshed(img): + gui_threshed_img_pub.publish(drone.bridge.cv2_to_imgmsg(img)) + +def execute(event): + global drone + img_frontal = drone.get_frontal_image() + img_ventral = drone.get_ventral_image() + # Both the above images are cv2 images + ################# Insert your code here ################################# + + set_image_filtered(img_frontal) + set_image_threshed(img_ventral) + + ######################################################################### + +if __name__ == "__main__": + drone = DroneWrapper() + rospy.Subscriber('gui/play_stop', Bool, gui_play_stop_cb) + gui_filtered_img_pub = rospy.Publisher('interface/filtered_img', Image, queue_size = 1) + gui_threshed_img_pub = rospy.Publisher('interface/threshed_img', Image, queue_size = 1) + code_live_flag = False + code_live_timer = rospy.Timer(rospy.Duration(nsecs=50000000), execute) + code_live_timer.shutdown() + while not rospy.is_shutdown(): + rospy.spin() diff --git a/exercises/static/exercises/visual_lander/visual_lander.launch b/exercises/static/exercises/visual_lander/visual_lander.launch new file mode 100644 index 000000000..29f032865 --- /dev/null +++ b/exercises/static/exercises/visual_lander/visual_lander.launch @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/exercises/static/exercises/visual_lander/visual_lander.world b/exercises/static/exercises/visual_lander/visual_lander.world new file mode 100644 index 000000000..890308ef6 --- /dev/null +++ b/exercises/static/exercises/visual_lander/visual_lander.world @@ -0,0 +1,52 @@ + + + + + + false + + + + + model://sun + + + + model://grass_plane + + + + model://logoJdeRobot + -5.4041 0.171664 0 0 -0 0 + + + + model://car_color_beacon + 5 0 0.12 0 0 0 + + + + + 0 0 -9.8066 + + + quick + 10 + 1.3 + 0 + + + 0 + 0.2 + 100 + 0.001 + + + 0.004 + 1 + 250 + 6.0e-6 2.3e-5 -4.2e-5 + + + + diff --git a/exercises/static/exercises/visual_lander/web-template/RADI-launch b/exercises/static/exercises/visual_lander/web-template/RADI-launch new file mode 100755 index 000000000..afde9c081 --- /dev/null +++ b/exercises/static/exercises/visual_lander/web-template/RADI-launch @@ -0,0 +1,2 @@ +#!/bin/sh +docker run -it --rm -p 8000:8000 -p 2303:2303 -p 1905:1905 -p 8765:8765 -p 6080:6080 -p 1108:1108 jderobot/robotics-academy:3.1.2 ./start-3.1.sh diff --git a/exercises/static/exercises/visual_lander/web-template/README.md b/exercises/static/exercises/visual_lander/web-template/README.md new file mode 100644 index 000000000..a386ca16a --- /dev/null +++ b/exercises/static/exercises/visual_lander/web-template/README.md @@ -0,0 +1 @@ +[Exercise Documentation Website](https://jderobot.github.io/RoboticsAcademy/exercises/Drones/visual_lander) diff --git a/exercises/static/exercises/visual_lander/web-template/car.py b/exercises/static/exercises/visual_lander/web-template/car.py new file mode 100644 index 000000000..11e20a8c8 --- /dev/null +++ b/exercises/static/exercises/visual_lander/web-template/car.py @@ -0,0 +1,72 @@ +import rospy +import sys +from threading import Event + +from gazebo_msgs.msg import ModelState +from gazebo_msgs.srv import SetModelState +from interfaces.threadStoppable import StoppableThread + +class Car(): + def __init__(self): + self.set_state = rospy.ServiceProxy('/gazebo/set_model_state', SetModelState) + self.play_event = Event() + self.curr_posx = 5.0 #initial position + self.base_speed = 0.0001 + self.level0 = self.base_speed * 1 + self.level1 = self.base_speed * 10 + self.level2 = self.base_speed * 20 + self.level3 = self.base_speed * 30 + + #Explicit initialization functions + #Class method, so user can call it without instantiation + @classmethod + def initRobot(cls): + new_instance = cls() + return new_instance + + def set_pos_car(self, x=0): + req = ModelState() + req.model_name = "car_color_beacon" + req.pose.position.x = x + self.set_state(req) + + def __start__(self, path_level): + while self.play_event.is_set(): + rospy.sleep(0.1) + if path_level == 0: + self.set_pos_car(self.curr_posx) + self.curr_posx = self.curr_posx + self.level0 + elif path_level == 1: + self.set_pos_car(self.curr_posx) + self.curr_posx = self.curr_posx + self.level1 + elif path_level == 2: + self.set_pos_car(self.curr_posx) + self.curr_posx = self.curr_posx + self.level2 + elif path_level == 3: + self.set_pos_car(self.curr_posx) + self.curr_posx = self.curr_posx + self.level3 + else: + sys.exit() #exit thread + + def start_car(self, path_level): + self.play_event.set() + self.thread = StoppableThread(target=self.__start__, args=[path_level,]) + self.thread.start() + + def stop_car(self): + try: + self.play_event.clear() + rospy.sleep(0.5) + self.thread.join() + except: + pass + + def reset_car(self): + try: + self.play_event.clear() + rospy.sleep(0.5) + self.thread.join() + except: + pass + self.curr_posx = 5.0 + self.set_pos_car(self.curr_posx) #set initial position \ No newline at end of file diff --git a/exercises/static/exercises/visual_lander/web-template/code/academy.py b/exercises/static/exercises/visual_lander/web-template/code/academy.py new file mode 100644 index 000000000..7a59ce7f5 --- /dev/null +++ b/exercises/static/exercises/visual_lander/web-template/code/academy.py @@ -0,0 +1,8 @@ +# Enter sequential code! +from GUI import GUI +from HAL import HAL + +while True: + # Enter iterative code! + img = HAL.get_ventral_image() + GUI.showImage(img) \ No newline at end of file diff --git a/exercises/static/exercises/visual_lander/web-template/console.py b/exercises/static/exercises/visual_lander/web-template/console.py new file mode 100644 index 000000000..7b72a0913 --- /dev/null +++ b/exercises/static/exercises/visual_lander/web-template/console.py @@ -0,0 +1,20 @@ +# Functions to start and close console +import os +import sys + + +def start_console(): + # Get all the file descriptors and choose the latest one + fds = os.listdir("/dev/pts/") + fds.sort() + console_fd = fds[-2] + + sys.stderr = open('/dev/pts/' + console_fd, 'w') + sys.stdout = open('/dev/pts/' + console_fd, 'w') + sys.stdin = open('/dev/pts/' + console_fd, 'w') + + +def close_console(): + sys.stderr.close() + sys.stdout.close() + sys.stdin.close() diff --git a/exercises/static/exercises/visual_lander/web-template/exercise.py b/exercises/static/exercises/visual_lander/web-template/exercise.py new file mode 100644 index 000000000..3874651bc --- /dev/null +++ b/exercises/static/exercises/visual_lander/web-template/exercise.py @@ -0,0 +1,363 @@ +#!/usr/bin/env python + +from __future__ import print_function + +from websocket_server import WebsocketServer +import time +import threading +import subprocess +import sys +from datetime import datetime +import re +import json +import importlib + +import rospy +from std_srvs.srv import Empty + +from gui import GUI, ThreadGUI +from hal import HAL +from car import Car +from console import start_console, close_console + + +class Template: + # Initialize class variables + # self.ideal_cycle to run an execution for at least 1 second + # self.process for the current running process + def __init__(self): + self.measure_thread = None + self.thread = None + self.reload = False + self.stop_brain = True + self.user_code = "" + + # Time variables + self.ideal_cycle = 80 + self.measured_cycle = 80 + self.iteration_counter = 0 + self.real_time_factor = 0 + self.frequency_message = {'brain': '', 'gui': '', 'rtf': ''} + + self.server = None + self.client = None + self.host = sys.argv[1] + + # Initialize the GUI, HAL and Console behind the scenes + self.hal = HAL() + self.car = Car() + self.gui = GUI(self.host, self.car) + + # Function to parse the code + # A few assumptions: + # 1. The user always passes sequential and iterative codes + # 2. Only a single infinite loop + def parse_code(self, source_code): + sequential_code, iterative_code = self.seperate_seq_iter(source_code) + return iterative_code, sequential_code + + # Function to separate the iterative and sequential code + def seperate_seq_iter(self, source_code): + if source_code == "": + return "", "" + + # Search for an instance of while True + infinite_loop = re.search(r'[^ ]while\s*\(\s*True\s*\)\s*:|[^ ]while\s*True\s*:|[^ ]while\s*1\s*:|[^ ]while\s*\(\s*1\s*\)\s*:', source_code) + + # Separate the content inside while True and the other + # (Separating the sequential and iterative part!) + try: + start_index = infinite_loop.start() + iterative_code = source_code[start_index:] + sequential_code = source_code[:start_index] + + # Remove while True: syntax from the code + # And remove the the 4 spaces indentation before each command + iterative_code = re.sub(r'[^ ]while\s*\(\s*True\s*\)\s*:|[^ ]while\s*True\s*:|[^ ]while\s*1\s*:|[^ ]while\s*\(\s*1\s*\)\s*:', '', iterative_code) + # Add newlines to match line on bug report + extra_lines = sequential_code.count('\n') + while (extra_lines >= 0): + iterative_code = '\n' + iterative_code + extra_lines -= 1 + iterative_code = re.sub(r'^[ ]{4}', '', iterative_code, flags=re.M) + + except: + sequential_code = source_code + iterative_code = "" + + return sequential_code, iterative_code + + # The process function + def process_code(self, source_code): + # Redirect the information to console + start_console() + + iterative_code, sequential_code = self.parse_code(source_code) + + # print(sequential_code) + # print(iterative_code) + + # The Python exec function + # Run the sequential part + gui_module, hal_module = self.generate_modules() + reference_environment = {"GUI": gui_module, "HAL": hal_module} + while (self.stop_brain == True): + if (self.reload == True): + return + time.sleep(0.1) + exec(sequential_code, reference_environment) + + # Run the iterative part inside template + # and keep the check for flag + while self.reload == False: + while (self.stop_brain == True): + if (self.reload == True): + return + time.sleep(0.1) + + start_time = datetime.now() + + # Execute the iterative portion + exec(iterative_code, reference_environment) + + # Template specifics to run! + finish_time = datetime.now() + dt = finish_time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + + # Keep updating the iteration counter + if (iterative_code == ""): + self.iteration_counter = 0 + else: + self.iteration_counter = self.iteration_counter + 1 + + # The code should be run for atleast the target time step + # If it's less put to sleep + if (ms < self.ideal_cycle): + time.sleep((self.ideal_cycle - ms) / 1000.0) + + close_console() + print("Current Thread Joined!") + + # Function to generate the modules for use in ACE Editor + def generate_modules(self): + # Define HAL module + hal_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) + hal_module.HAL = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) + # hal_module.drone = imp.new_module("drone") + # motors# hal_module.HAL.motors = imp.new_module("motors") + + # Add HAL functions + hal_module.HAL.get_frontal_image = self.hal.get_frontal_image + hal_module.HAL.get_ventral_image = self.hal.get_ventral_image + hal_module.HAL.get_position = self.hal.get_position + hal_module.HAL.get_velocity = self.hal.get_velocity + hal_module.HAL.get_yaw_rate = self.hal.get_yaw_rate + hal_module.HAL.get_orientation = self.hal.get_orientation + hal_module.HAL.get_roll = self.hal.get_roll + hal_module.HAL.get_pitch = self.hal.get_pitch + hal_module.HAL.get_yaw = self.hal.get_yaw + hal_module.HAL.get_landed_state = self.hal.get_landed_state + hal_module.HAL.set_cmd_pos = self.hal.set_cmd_pos + hal_module.HAL.set_cmd_vel = self.hal.set_cmd_vel + hal_module.HAL.set_cmd_mix = self.hal.set_cmd_mix + hal_module.HAL.takeoff = self.hal.takeoff + hal_module.HAL.land = self.hal.land + + # Define GUI module + gui_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) + gui_module.GUI = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) + + # Add GUI functions + gui_module.GUI.showImage = self.gui.showImage + gui_module.GUI.showLeftImage = self.gui.showLeftImage + + # Adding modules to system + # Protip: The names should be different from + # other modules, otherwise some errors + sys.modules["HAL"] = hal_module + sys.modules["GUI"] = gui_module + + return gui_module, hal_module + + # Function to measure the frequency of iterations + def measure_frequency(self): + previous_time = datetime.now() + # An infinite loop + while True: + # Sleep for 2 seconds + time.sleep(2) + + # Measure the current time and subtract from the previous time to get real time interval + current_time = datetime.now() + dt = current_time - previous_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + previous_time = current_time + + # Get the time period + try: + # Division by zero + self.measured_cycle = ms / self.iteration_counter + except: + self.measured_cycle = 0 + + # Reset the counter + self.iteration_counter = 0 + + # Send to client + self.send_frequency_message() + + # Function to generate and send frequency messages + def send_frequency_message(self): + # This function generates and sends frequency measures of the brain and gui + brain_frequency = 0; gui_frequency = 0 + try: + brain_frequency = round(1000 / self.measured_cycle, 1) + except ZeroDivisionError: + brain_frequency = 0 + + try: + gui_frequency = round(1000 / self.thread_gui.measured_cycle, 1) + except ZeroDivisionError: + gui_frequency = 0 + + self.frequency_message["brain"] = brain_frequency + self.frequency_message["gui"] = gui_frequency + self.frequency_message["rtf"] = self.real_time_factor + + message = "#freq" + json.dumps(self.frequency_message) + self.server.send_message(self.client, message) + + def send_ping_message(self): + self.server.send_message(self.client, "#ping") + + # Function to notify the front end that the code was received and sent to execution + def send_code_message(self): + self.server.send_message(self.client, "#exec") + + # Function to track the real time factor from Gazebo statistics + # https://stackoverflow.com/a/17698359 + # (For reference, Python3 solution specified in the same answer) + def track_stats(self): + args = ["gz", "stats", "-p"] + # Prints gz statistics. "-p": Output comma-separated values containing- + # real-time factor (percent), simtime (sec), realtime (sec), paused (T or F) + stats_process = subprocess.Popen(args, stdout=subprocess.PIPE, bufsize=1, universal_newlines=True) + # bufsize=1 enables line-bufferred mode (the input buffer is flushed + # automatically on newlines if you would write to process.stdin ) + with stats_process.stdout: + for line in iter(stats_process.stdout.readline, ''): + stats_list = [x.strip() for x in line.split(',')] + self.real_time_factor = stats_list[0] + + # Function to maintain thread execution + def execute_thread(self, source_code): + # Keep checking until the thread is alive + # The thread will die when the coming iteration reads the flag + if self.thread is not None: + while self.thread.is_alive(): + time.sleep(0.2) + + # Turn the flag down, the iteration has successfully stopped! + self.reload = False + # New thread execution + self.thread = threading.Thread(target=self.process_code, args=[source_code]) + self.thread.start() + self.send_code_message() + print("New Thread Started!") + + # Function to read and set frequency from incoming message + def read_frequency_message(self, message): + frequency_message = json.loads(message) + + # Set brain frequency + frequency = float(frequency_message["brain"]) + self.ideal_cycle = 1000.0 / frequency + + # Set gui frequency + frequency = float(frequency_message["gui"]) + self.thread_gui.ideal_cycle = 1000.0 / frequency + + return + + # The websocket function + # Gets called when there is an incoming message from the client + def handle(self, client, server, message): + if message[:5] == "#freq": + frequency_message = message[5:] + self.read_frequency_message(frequency_message) + time.sleep(1) + return + + elif(message[:5] == "#ping"): + time.sleep(1) + self.send_ping_message() + return + elif (message[:5] == "#code"): + try: + # Once received turn the reload flag up and send it to execute_thread function + self.user_code = message[6:] + # print(repr(code)) + self.reload = True + self.execute_thread(self.user_code) + except: + pass + + elif (message[:5] == "#rest"): + try: + self.reload = True + self.stop_brain = True + self.execute_thread(self.user_code) + except: + pass + + elif (message[:5] == "#stop"): + self.stop_brain = True + + elif (message[:5] == "#play"): + self.stop_brain = False + + # Function that gets called when the server is connected + def connected(self, client, server): + self.client = client + # Start the GUI update thread + self.thread_gui = ThreadGUI(self.gui) + self.thread_gui.start() + + # Start the real time factor tracker thread + self.stats_thread = threading.Thread(target=self.track_stats) + self.stats_thread.start() + + # Start measure frequency + self.measure_thread = threading.Thread(target=self.measure_frequency) + self.measure_thread.start() + + print(client, 'connected') + + # Function that gets called when the connected closes + def handle_close(self, client, server): + print(client, 'closed') + + def run_server(self): + self.server = WebsocketServer(port=1905, host=self.host) + self.server.set_fn_new_client(self.connected) + self.server.set_fn_client_left(self.handle_close) + self.server.set_fn_message_received(self.handle) + + logged = False + while not logged: + try: + f = open("/ws_code.log", "w") + f.write("websocket_code=ready") + f.close() + logged = True + except: + time.sleep(0.1) + + self.server.run_forever() + + +# Execute! +if __name__ == "__main__": + server = Template() + server.run_server() \ No newline at end of file diff --git a/exercises/static/exercises/visual_lander/web-template/gui.py b/exercises/static/exercises/visual_lander/web-template/gui.py new file mode 100644 index 000000000..2973e6cb9 --- /dev/null +++ b/exercises/static/exercises/visual_lander/web-template/gui.py @@ -0,0 +1,254 @@ +import json +import cv2 +import base64 +import threading +import time +from datetime import datetime +from websocket_server import WebsocketServer + + +# Graphical User Interface Class +class GUI: + # Initialization function + # The actual initialization + def __init__(self, host, car): + t = threading.Thread(target=self.run_server) + + self.payload = {'image': ''} + self.left_payload = {'image': ''} + self.server = None + self.client = None + + self.host = host + + # Image variables + self.image_to_be_shown = None + self.image_to_be_shown_updated = False + self.image_show_lock = threading.Lock() + + self.left_image_to_be_shown = None + self.left_image_to_be_shown_updated = False + self.left_image_show_lock = threading.Lock() + + self.acknowledge = False + self.acknowledge_lock = threading.Lock() + + # Take the console object to set the same websocket and client + self.car = car + t.start() + + # Explicit initialization function + # Class method, so user can call it without instantiation + @classmethod + def initGUI(cls, host): + # self.payload = {'image': '', 'shape': []} + new_instance = cls(host) + return new_instance + + # Function to prepare image payload + # Encodes the image as a JSON string and sends through the WS + def payloadImage(self): + self.image_show_lock.acquire() + image_to_be_shown_updated = self.image_to_be_shown_updated + image_to_be_shown = self.image_to_be_shown + self.image_show_lock.release() + + image = image_to_be_shown + payload = {'image': '', 'shape': ''} + + if not image_to_be_shown_updated: + return payload + + shape = image.shape + frame = cv2.imencode('.JPEG', image)[1] + encoded_image = base64.b64encode(frame) + + payload['image'] = encoded_image.decode('utf-8') + payload['shape'] = shape + + self.image_show_lock.acquire() + self.image_to_be_shown_updated = False + self.image_show_lock.release() + + return payload + + # Function to prepare image payload + # Encodes the image as a JSON string and sends through the WS + def payloadLeftImage(self): + self.left_image_show_lock.acquire() + left_image_to_be_shown_updated = self.left_image_to_be_shown_updated + left_image_to_be_shown = self.left_image_to_be_shown + self.left_image_show_lock.release() + + image = left_image_to_be_shown + payload = {'image': '', 'shape': ''} + + if not left_image_to_be_shown_updated: + return payload + + shape = image.shape + frame = cv2.imencode('.JPEG', image)[1] + encoded_image = base64.b64encode(frame) + + payload['image'] = encoded_image.decode('utf-8') + payload['shape'] = shape + + self.left_image_show_lock.acquire() + self.left_image_to_be_shown_updated = False + self.left_image_show_lock.release() + + return payload + + # Function for student to call + def showImage(self, image): + self.image_show_lock.acquire() + self.image_to_be_shown = image + self.image_to_be_shown_updated = True + self.image_show_lock.release() + + # Function for student to call + def showLeftImage(self, image): + self.left_image_show_lock.acquire() + self.left_image_to_be_shown = image + self.left_image_to_be_shown_updated = True + self.left_image_show_lock.release() + + # Function to get the client + # Called when a new client is received + def get_client(self, client, server): + self.client = client + + # Function to get value of Acknowledge + def get_acknowledge(self): + self.acknowledge_lock.acquire() + acknowledge = self.acknowledge + self.acknowledge_lock.release() + + return acknowledge + + # Function to get value of Acknowledge + def set_acknowledge(self, value): + self.acknowledge_lock.acquire() + self.acknowledge = value + self.acknowledge_lock.release() + + # Update the gui + def update_gui(self): + # Payload Image Message + payload = self.payloadImage() + self.payload["image"] = json.dumps(payload) + + message = "#gui" + json.dumps(self.payload) + self.server.send_message(self.client, message) + + # Payload Left Image Message + left_payload = self.payloadLeftImage() + self.left_payload["image"] = json.dumps(left_payload) + + message = "#gul" + json.dumps(self.left_payload) + self.server.send_message(self.client, message) + + # Function to read the message from websocket + # Gets called when there is an incoming message from the client + def get_message(self, client, server, message): + # Acknowledge Message for GUI Thread + if message[:4] == "#ack": + self.set_acknowledge(True) + elif message[:4] == "#car": + self.car.start_car(int(message[4:5])) + elif message[:4] == "#stp": + self.car.stop_car() + elif message[:4] == "#rst": + self.car.reset_car() + # Activate the server + def run_server(self): + self.server = WebsocketServer(port=2303, host=self.host) + self.server.set_fn_new_client(self.get_client) + self.server.set_fn_message_received(self.get_message) + + logged = False + while not logged: + try: + f = open("/ws_gui.log", "w") + f.write("websocket_gui=ready") + f.close() + logged = True + except: + time.sleep(0.1) + + self.server.run_forever() + + # Function to reset + def reset_gui(self): + pass + + +# This class decouples the user thread +# and the GUI update thread +class ThreadGUI: + def __init__(self, gui): + self.gui = gui + + # Time variables + self.ideal_cycle = 80 + self.measured_cycle = 80 + self.iteration_counter = 0 + + # Function to start the execution of threads + def start(self): + self.measure_thread = threading.Thread(target=self.measure_thread) + self.thread = threading.Thread(target=self.run) + + self.measure_thread.start() + self.thread.start() + + print("GUI Thread Started!") + + # The measuring thread to measure frequency + def measure_thread(self): + while self.gui.client is None: + pass + + previous_time = datetime.now() + while True: + # Sleep for 2 seconds + time.sleep(2) + + # Measure the current time and subtract from previous time to get real time interval + current_time = datetime.now() + dt = current_time - previous_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + previous_time = current_time + + # Get the time period + try: + # Division by zero + self.measured_cycle = ms / self.iteration_counter + except: + self.measured_cycle = 0 + + # Reset the counter + self.iteration_counter = 0 + + # The main thread of execution + def run(self): + while self.gui.client is None: + pass + + while True: + start_time = datetime.now() + self.gui.update_gui() + acknowledge_message = self.gui.get_acknowledge() + + while not acknowledge_message: + acknowledge_message = self.gui.get_acknowledge() + + self.gui.set_acknowledge(False) + + finish_time = datetime.now() + self.iteration_counter = self.iteration_counter + 1 + + dt = finish_time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + if ms < self.ideal_cycle: + time.sleep((self.ideal_cycle-ms) / 1000.0) diff --git a/exercises/static/exercises/visual_lander/web-template/hal.py b/exercises/static/exercises/visual_lander/web-template/hal.py new file mode 100644 index 000000000..25635a119 --- /dev/null +++ b/exercises/static/exercises/visual_lander/web-template/hal.py @@ -0,0 +1,82 @@ +import numpy as np +import rospy +import cv2 + +from drone_wrapper import DroneWrapper + + +# Hardware Abstraction Layer +class HAL: + IMG_WIDTH = 320 + IMG_HEIGHT = 240 + + def __init__(self): + rospy.init_node("HAL") + + self.image = None + self.drone = DroneWrapper(name="rqt") + + # Explicit initialization functions + # Class method, so user can call it without instantiation + @classmethod + def initRobot(cls): + new_instance = cls() + return new_instance + + # Get Image from ROS Driver Camera + def get_frontal_image(self): + image = self.drone.get_frontal_image() + image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) + return image_rgb + + def get_ventral_image(self): + image = self.drone.get_ventral_image() + image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) + return image_rgb + + def get_position(self): + pos = self.drone.get_position() + return pos + + def get_velocity(self): + vel = self.drone.get_velocity() + return vel + + def get_yaw_rate(self): + yaw_rate = self.drone.get_yaw_rate() + return yaw_rate + + def get_orientation(self): + orientation = self.drone.get_orientation() + return orientation + + def get_roll(self): + roll = self.drone.get_roll() + return roll + + def get_pitch(self): + pitch = self.drone.get_pitch() + return pitch + + def get_yaw(self): + yaw = self.drone.get_yaw() + return yaw + + def get_landed_state(self): + state = self.drone.get_landed_state() + return state + + def set_cmd_pos(self, x, y, z, az): + self.drone.set_cmd_pos(x, y, z, az) + + def set_cmd_vel(self, vx, vy, vz, az): + self.drone.set_cmd_vel(vx, vy, vz, az) + + def set_cmd_mix(self, vx, vy, z, az): + self.drone.set_cmd_mix(vx, vy, z, az) + + def takeoff(self, h=3): + self.drone.takeoff(h) + + def land(self): + self.drone.land() diff --git a/exercises/static/exercises/visual_lander/web-template/interfaces/__init__.py b/exercises/static/exercises/visual_lander/web-template/interfaces/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/exercises/static/exercises/visual_lander/web-template/interfaces/camera.py b/exercises/static/exercises/visual_lander/web-template/interfaces/camera.py new file mode 100644 index 000000000..5a021a13e --- /dev/null +++ b/exercises/static/exercises/visual_lander/web-template/interfaces/camera.py @@ -0,0 +1,89 @@ +import rospy +from sensor_msgs.msg import Image as ImageROS +import threading +from math import pi as PI +import cv2 +from cv_bridge import CvBridge, CvBridgeError + + +MAXRANGE = 8 # max length received from imageD +MINRANGE = 0 + + +def imageMsg2Image(img, bridge): + + image = Image() + + image.width = img.width + image.height = img.height + image.format = "BGR8" + image.timeStamp = img.header.stamp.secs + (img.header.stamp.nsecs * 1e-9) + cv_image = 0 + if img.encoding[-2:] == "C1": + gray_img_buff = bridge.imgmsg_to_cv2(img, img.encoding) + cv_image = depthToRGB8(gray_img_buff, img.encoding) + else: + cv_image = bridge.imgmsg_to_cv2(img, "bgr8") + image.data = cv_image + return image + + +import numpy as np + + +class Image: + + def __init__(self): + + self.height = 3 # Image height [pixels] + self.width = 3 # Image width [pixels] + self.timeStamp = 0 # Time stamp [s] */ + self.format = "" # Image format string (RGB8, BGR,...) + self.data = np.zeros((self.height, self.width, 3), np.uint8) # The image data itself + self.data.shape = self.height, self.width, 3 + + def __str__(self): + s = "Image: {\n height: " + str(self.height) + "\n width: " + str(self.width) + s = s + "\n format: " + self.format + "\n timeStamp: " + str(self.timeStamp) + s = s + "\n data: " + str(self.data) + "\n}" + return s + + +class ListenerCamera: + + def __init__(self, topic): + + self.topic = topic + self.data = Image() + self.sub = None + self.lock = threading.Lock() + + self.bridge = CvBridge() + self.start() + + def __callback(self, img): + + image = imageMsg2Image(img, self.bridge) + + self.lock.acquire() + self.data = image + self.lock.release() + + def stop(self): + + self.sub.unregister() + + def start(self): + self.sub = rospy.Subscriber(self.topic, ImageROS, self.__callback) + + def getImage(self): + + self.lock.acquire() + image = self.data + self.lock.release() + + return image + + def hasproxy(self): + + return hasattr(self, "sub") and self.sub diff --git a/exercises/static/exercises/visual_lander/web-template/interfaces/motors.py b/exercises/static/exercises/visual_lander/web-template/interfaces/motors.py new file mode 100644 index 000000000..70dca8a46 --- /dev/null +++ b/exercises/static/exercises/visual_lander/web-template/interfaces/motors.py @@ -0,0 +1,123 @@ +import rospy +from geometry_msgs.msg import Twist +import threading +from math import pi as PI +from .threadPublisher import ThreadPublisher + + + +def cmdvel2Twist(vel): + + tw = Twist() + tw.linear.x = vel.vx + tw.linear.y = vel.vy + tw.linear.z = vel.vz + tw.angular.x = vel.ax + tw.angular.y = vel.ay + tw.angular.z = vel.az + + return tw + + +class CMDVel (): + + def __init__(self): + + self.vx = 0 # vel in x[m/s] (use this for V in wheeled robots) + self.vy = 0 # vel in y[m/s] + self.vz = 0 # vel in z[m/s] + self.ax = 0 # angular vel in X axis [rad/s] + self.ay = 0 # angular vel in X axis [rad/s] + self.az = 0 # angular vel in Z axis [rad/s] (use this for W in wheeled robots) + self.timeStamp = 0 # Time stamp [s] + + + def __str__(self): + s = "CMDVel: {\n vx: " + str(self.vx) + "\n vy: " + str(self.vy) + s = s + "\n vz: " + str(self.vz) + "\n ax: " + str(self.ax) + s = s + "\n ay: " + str(self.ay) + "\n az: " + str(self.az) + s = s + "\n timeStamp: " + str(self.timeStamp) + "\n}" + return s + +class PublisherMotors: + + def __init__(self, topic, maxV, maxW): + + self.maxW = maxW + self.maxV = maxV + + self.topic = topic + self.data = CMDVel() + self.pub = rospy.Publisher(self.topic, Twist, queue_size=1) + + self.lock = threading.Lock() + + self.kill_event = threading.Event() + self.thread = ThreadPublisher(self, self.kill_event) + + self.thread.daemon = True + self.start() + + def publish (self): + + self.lock.acquire() + tw = cmdvel2Twist(self.data) + self.lock.release() + self.pub.publish(tw) + + def stop(self): + + self.kill_event.set() + self.pub.unregister() + + def start (self): + + self.kill_event.clear() + self.thread.start() + + + + def getMaxW(self): + return self.maxW + + def getMaxV(self): + return self.maxV + + + def sendVelocities(self, vel): + + self.lock.acquire() + self.data = vel + self.lock.release() + + def sendV(self, v): + + self.sendVX(v) + + def sendL(self, l): + + self.sendVY(l) + + def sendW(self, w): + + self.sendAZ(w) + + def sendVX(self, vx): + + self.lock.acquire() + self.data.vx = vx + self.lock.release() + + def sendVY(self, vy): + + self.lock.acquire() + self.data.vy = vy + self.lock.release() + + def sendAZ(self, az): + + self.lock.acquire() + self.data.az = az + self.lock.release() + + diff --git a/exercises/static/exercises/visual_lander/web-template/interfaces/pose3d.py b/exercises/static/exercises/visual_lander/web-template/interfaces/pose3d.py new file mode 100644 index 000000000..fd0bfc37a --- /dev/null +++ b/exercises/static/exercises/visual_lander/web-template/interfaces/pose3d.py @@ -0,0 +1,176 @@ +import rospy +import threading +from math import asin, atan2, pi +from nav_msgs.msg import Odometry + +def quat2Yaw(qw, qx, qy, qz): + ''' + Translates from Quaternion to Yaw. + + @param qw,qx,qy,qz: Quaternion values + + @type qw,qx,qy,qz: float + + @return Yaw value translated from Quaternion + + ''' + rotateZa0=2.0*(qx*qy + qw*qz) + rotateZa1=qw*qw + qx*qx - qy*qy - qz*qz + rotateZ=0.0 + if(rotateZa0 != 0.0 and rotateZa1 != 0.0): + rotateZ=atan2(rotateZa0,rotateZa1) + return rotateZ + +def quat2Pitch(qw, qx, qy, qz): + ''' + Translates from Quaternion to Pitch. + + @param qw,qx,qy,qz: Quaternion values + + @type qw,qx,qy,qz: float + + @return Pitch value translated from Quaternion + + ''' + + rotateYa0=-2.0*(qx*qz - qw*qy) + rotateY=0.0 + if(rotateYa0 >= 1.0): + rotateY = pi/2.0 + elif(rotateYa0 <= -1.0): + rotateY = -pi/2.0 + else: + rotateY = asin(rotateYa0) + + return rotateY + +def quat2Roll (qw, qx, qy, qz): + ''' + Translates from Quaternion to Roll. + + @param qw,qx,qy,qz: Quaternion values + + @type qw,qx,qy,qz: float + + @return Roll value translated from Quaternion + + ''' + rotateXa0=2.0*(qy*qz + qw*qx) + rotateXa1=qw*qw - qx*qx - qy*qy + qz*qz + rotateX=0.0 + + if(rotateXa0 != 0.0 and rotateXa1 != 0.0): + rotateX=atan2(rotateXa0, rotateXa1) + return rotateX + + +def odometry2Pose3D(odom): + ''' + Translates from ROS Odometry to JderobotTypes Pose3d. + + @param odom: ROS Odometry to translate + + @type odom: Odometry + + @return a Pose3d translated from odom + + ''' + pose = Pose3d() + ori = odom.pose.pose.orientation + + pose.x = odom.pose.pose.position.x + pose.y = odom.pose.pose.position.y + pose.z = odom.pose.pose.position.z + #pose.h = odom.pose.pose.position.h + pose.yaw = quat2Yaw(ori.w, ori.x, ori.y, ori.z) + pose.pitch = quat2Pitch(ori.w, ori.x, ori.y, ori.z) + pose.roll = quat2Roll(ori.w, ori.x, ori.y, ori.z) + pose.q = [ori.w, ori.x, ori.y, ori.z] + pose.timeStamp = odom.header.stamp.secs + (odom.header.stamp.nsecs *1e-9) + + return pose + +class Pose3d (): + + def __init__(self): + + self.x = 0 # X coord [meters] + self.y = 0 # Y coord [meters] + self.z = 0 # Z coord [meters] + self.h = 1 # H param + self.yaw = 0 #Yaw angle[rads] + self.pitch = 0 # Pitch angle[rads] + self.roll = 0 # Roll angle[rads] + self.q = [0,0,0,0] # Quaternion + self.timeStamp = 0 # Time stamp [s] + + + def __str__(self): + s = "Pose3D: {\n x: " + str(self.x) + "\n Y: " + str(self.y) + s = s + "\n Z: " + str(self.z) + "\n H: " + str(self.h) + s = s + "\n Yaw: " + str(self.yaw) + "\n Pitch: " + str(self.pitch) + "\n Roll: " + str(self.roll) + s = s + "\n quaternion: " + str(self.q) + "\n timeStamp: " + str(self.timeStamp) + "\n}" + return s + + +class ListenerPose3d: + ''' + ROS Pose3D Subscriber. Pose3D Client to Receive pose3d from ROS nodes. + ''' + def __init__(self, topic): + ''' + ListenerPose3d Constructor. + + @param topic: ROS topic to subscribe + + @type topic: String + + ''' + self.topic = topic + self.data = Pose3d() + self.sub = None + self.lock = threading.Lock() + self.start() + + def __callback (self, odom): + ''' + Callback function to receive and save Pose3d. + + @param odom: ROS Odometry received + + @type odom: Odometry + + ''' + pose = odometry2Pose3D(odom) + + self.lock.acquire() + self.data = pose + self.lock.release() + + def stop(self): + ''' + Stops (Unregisters) the client. + + ''' + self.sub.unregister() + + def start (self): + ''' + Starts (Subscribes) the client. + + ''' + self.sub = rospy.Subscriber(self.topic, Odometry, self.__callback) + + def getPose3d(self): + ''' + Returns last Pose3d. + + @return last JdeRobotTypes Pose3d saved + + ''' + self.lock.acquire() + pose = self.data + self.lock.release() + + return pose + diff --git a/exercises/static/exercises/visual_lander/web-template/interfaces/threadPublisher.py b/exercises/static/exercises/visual_lander/web-template/interfaces/threadPublisher.py new file mode 100644 index 000000000..69aa0ad48 --- /dev/null +++ b/exercises/static/exercises/visual_lander/web-template/interfaces/threadPublisher.py @@ -0,0 +1,46 @@ +# +# Copyright (C) 1997-2016 JDE Developers Team +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. +# Authors : +# Alberto Martin Florido +# Aitor Martinez Fernandez +# +import threading +import time +from datetime import datetime + +time_cycle = 80 + + +class ThreadPublisher(threading.Thread): + + def __init__(self, pub, kill_event): + self.pub = pub + self.kill_event = kill_event + threading.Thread.__init__(self, args=kill_event) + + def run(self): + while (not self.kill_event.is_set()): + start_time = datetime.now() + + self.pub.publish() + + finish_Time = datetime.now() + + dt = finish_Time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + #print (ms) + if (ms < time_cycle): + time.sleep((time_cycle - ms) / 1000.0) \ No newline at end of file diff --git a/exercises/static/exercises/visual_lander/web-template/interfaces/threadStoppable.py b/exercises/static/exercises/visual_lander/web-template/interfaces/threadStoppable.py new file mode 100644 index 000000000..b631d180f --- /dev/null +++ b/exercises/static/exercises/visual_lander/web-template/interfaces/threadStoppable.py @@ -0,0 +1,36 @@ +import threading +import time +from datetime import datetime + +time_cycle = 80 + + +class StoppableThread(threading.Thread): + """Thread class with a stop() method. The thread itself has to check + regularly for the stopped() condition.""" + + def __init__(self, target, kill_event=threading.Event(), *args, **kwargs): + super(StoppableThread, self).__init__(*args, **kwargs) + self._target = target + self._target_args = kwargs["args"] + self._kill_event = kill_event + + def run(self): + while not self.stopped(): + start_time = datetime.now() + + self._target(*self._target_args) + + finish_time = datetime.now() + + dt = finish_time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + # print (ms) + if ms < time_cycle: + time.sleep((time_cycle - ms) / 1000.0) + + def stop(self): + self._kill_event.set() + + def stopped(self): + return self._kill_event.is_set() diff --git a/exercises/static/exercises/visual_lander/web-template/launch/gazebo.launch b/exercises/static/exercises/visual_lander/web-template/launch/gazebo.launch new file mode 100644 index 000000000..7768d03f8 --- /dev/null +++ b/exercises/static/exercises/visual_lander/web-template/launch/gazebo.launch @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/exercises/static/exercises/visual_lander/web-template/launch/launch.py b/exercises/static/exercises/visual_lander/web-template/launch/launch.py new file mode 100644 index 000000000..d22afda8e --- /dev/null +++ b/exercises/static/exercises/visual_lander/web-template/launch/launch.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python3 + +import stat +import rospy +from os import lstat +from subprocess import Popen, PIPE + + +DRI_PATH = "/dev/dri/card0" +EXERCISE = "visual_lander" +TIMEOUT = 30 +MAX_ATTEMPT = 2 + + +# Check if acceleration can be enabled +def check_device(device_path): + try: + return stat.S_ISCHR(lstat(device_path)[stat.ST_MODE]) + except: + return False + + +# Spawn new process +def spawn_process(args, insert_vglrun=False): + if insert_vglrun: + args.insert(0, "vglrun") + process = Popen(args, stdout=PIPE, bufsize=1, universal_newlines=True) + return process + + +class Test(): + def gazebo(self): + rospy.logwarn("[GAZEBO] Launching") + try: + rospy.wait_for_service("/gazebo/get_model_properties", TIMEOUT) + return True + except rospy.ROSException: + return False + + def px4(self): + rospy.logwarn("[PX4-SITL] Launching") + start_time = rospy.get_time() + args = ["./PX4-Autopilot/build/px4_sitl_default/bin/px4-commander","--instance", "0", "check"] + while rospy.get_time() - start_time < TIMEOUT: + process = spawn_process(args, insert_vglrun=False) + with process.stdout: + for line in iter(process.stdout.readline, ''): + if ("Prearm check: OK" in line): + return True + rospy.sleep(2) + return False + + def mavros(self, ns=""): + rospy.logwarn("[MAVROS] Launching") + try: + rospy.wait_for_service(ns + "/mavros/cmd/arming", TIMEOUT) + return True + except rospy.ROSException: + return False + + +class Launch(): + def __init__(self): + self.test = Test() + self.acceleration_enabled = check_device(DRI_PATH) + + # Start roscore + args = ["/opt/ros/noetic/bin/roscore"] + spawn_process(args, insert_vglrun=False) + + rospy.init_node("launch", anonymous=True) + + def start(self): + ######## LAUNCH GAZEBO ######## + args = ["/opt/ros/noetic/bin/roslaunch", + "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/gazebo.launch", + "--wait", + "--log" + ] + + attempt = 1 + while True: + spawn_process(args, insert_vglrun=self.acceleration_enabled) + if self.test.gazebo() == True: + break + if attempt == MAX_ATTEMPT: + rospy.logerr("[GAZEBO] Launch Failed") + return + attempt = attempt + 1 + + + ######## LAUNCH PX4 ######## + args = ["/opt/ros/noetic/bin/roslaunch", + "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/px4.launch", + "--log" + ] + + attempt = 1 + while True: + spawn_process(args, insert_vglrun=self.acceleration_enabled) + if self.test.px4() == True: + break + if attempt == MAX_ATTEMPT: + rospy.logerr("[PX4] Launch Failed") + return + attempt = attempt + 1 + + + ######## LAUNCH MAVROS ######## + args = ["/opt/ros/noetic/bin/roslaunch", + "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/mavros.launch", + "--log" + ] + + attempt = 1 + while True: + spawn_process(args, insert_vglrun=self.acceleration_enabled) + if self.test.mavros() == True: + break + if attempt == MAX_ATTEMPT: + rospy.logerr("[MAVROS] Launch Failed") + return + attempt = attempt + 1 + + +if __name__ == "__main__": + launch = Launch() + launch.start() + + with open("/drones_launch.log", "w") as f: + f.write("success") diff --git a/exercises/static/exercises/visual_lander/web-template/launch/mavros.launch b/exercises/static/exercises/visual_lander/web-template/launch/mavros.launch new file mode 100644 index 000000000..b899c0ec1 --- /dev/null +++ b/exercises/static/exercises/visual_lander/web-template/launch/mavros.launch @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/exercises/static/exercises/visual_lander/web-template/launch/px4.launch b/exercises/static/exercises/visual_lander/web-template/launch/px4.launch new file mode 100644 index 000000000..43f1f66a9 --- /dev/null +++ b/exercises/static/exercises/visual_lander/web-template/launch/px4.launch @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/exercises/static/exercises/visual_lander/web-template/visual_lander.world b/exercises/static/exercises/visual_lander/web-template/visual_lander.world new file mode 100644 index 000000000..f6b208469 --- /dev/null +++ b/exercises/static/exercises/visual_lander/web-template/visual_lander.world @@ -0,0 +1,63 @@ + + + + + + + model://car_color_beacon + 5 0 0.12 0 0 0 + + + + + model://grass_plane + + + + + model://logoJdeRobot + -5.4041 0.171664 0.0 0.0 0.0 0.0 + + + + 0 + 0.4 0.4 0.4 1.0 + 0.7 0.7 0.7 1 + 0 + + + 12 + + + + + + + model://sun + + + + + 0 0 -9.8066 + + + quick + 10 + 1.3 + 0 + + + 0 + 0.2 + 100 + 0.001 + + + 0.004 + 1 + 250 + 6.0e-6 2.3e-5 -4.2e-5 + + + + From 44e4fde6242983ac84596ab809cae63d122fed31 Mon Sep 17 00:00:00 2001 From: Pedro Arias Date: Sun, 2 Oct 2022 13:58:59 +0200 Subject: [PATCH 42/42] restore drone_cat_mouse and follow_turtlebot back to PX4 --- .../web-template/drone_cat_mouse.world | 126 +++----- .../drone_cat_mouse/web-template/gui.py | 2 +- .../drone_cat_mouse/web-template/gui_guest.py | 2 +- .../drone_cat_mouse/web-template/hal.py | 2 +- .../drone_cat_mouse/web-template/hal_guest.py | 2 +- .../launch/drone_cat_mouse.launch | 99 ------ .../web-template/launch/gazebo.launch | 26 ++ .../web-template/launch/launch.py | 164 ++++++++++ .../web-template/launch/mavros_cat.launch | 15 + .../web-template/launch/mavros_mouse.launch | 15 + .../web-template/launch/px4_cat.launch | 20 ++ .../web-template/launch/px4_mouse.launch | 21 ++ .../drone_cat_mouse/web-template/mouse.py | 2 +- .../web-template/assets/console.css | 35 +++ .../web-template/assets/console.js | 73 +++++ .../web-template/assets/gui.css | 30 ++ .../web-template/assets/img/download.png | Bin 0 -> 16128 bytes .../web-template/assets/img/four.png | Bin 0 -> 15373 bytes .../web-template/assets/img/gzweb_btn.png | Bin 0 -> 49064 bytes .../web-template/assets/img/logo.jpg | Bin 0 -> 57771 bytes .../web-template/assets/img/one.png | Bin 0 -> 13106 bytes .../web-template/assets/img/pause.png | Bin 0 -> 8874 bytes .../web-template/assets/img/reset.png | Bin 0 -> 27404 bytes .../web-template/assets/img/reset.svg | 1 + .../assets/img/stopwatch-solid.svg | 1 + .../web-template/assets/img/submit.png | Bin 0 -> 18473 bytes .../assets/img/terminal-solid.svg | 1 + .../web-template/assets/img/three.png | Bin 0 -> 19373 bytes .../web-template/assets/img/two.png | Bin 0 -> 19443 bytes .../web-template/assets/img/upload.png | Bin 0 -> 18056 bytes .../web-template/assets/local_functions.js | 44 +++ .../web-template/assets/main.css | 286 ++++++++++++++++++ .../web-template/assets/split_style.css | 63 ++++ .../web-template/assets/websocket_address.js | 1 + .../web-template/assets/ws_gui.js | 145 +++++++++ .../follow_turtlebot/web-template/exercise.py | 2 - .../web-template/follow_turtlebot.world | 128 ++++---- .../follow_turtlebot/web-template/gui.py | 2 +- .../follow_turtlebot/web-template/hal.py | 4 +- .../launch/follow_turtlebot.launch | 64 ---- .../web-template/launch/gazebo.launch | 24 ++ .../web-template/launch/launch.py | 130 ++++++++ .../launch/mavros_and_turtlebot.launch | 20 ++ .../web-template/launch/px4.launch | 20 ++ .../web-template/turtlebot.py | 6 + 45 files changed, 1249 insertions(+), 327 deletions(-) delete mode 100644 exercises/static/exercises/drone_cat_mouse/web-template/launch/drone_cat_mouse.launch create mode 100644 exercises/static/exercises/drone_cat_mouse/web-template/launch/gazebo.launch create mode 100644 exercises/static/exercises/drone_cat_mouse/web-template/launch/launch.py create mode 100644 exercises/static/exercises/drone_cat_mouse/web-template/launch/mavros_cat.launch create mode 100644 exercises/static/exercises/drone_cat_mouse/web-template/launch/mavros_mouse.launch create mode 100644 exercises/static/exercises/drone_cat_mouse/web-template/launch/px4_cat.launch create mode 100644 exercises/static/exercises/drone_cat_mouse/web-template/launch/px4_mouse.launch create mode 100644 exercises/static/exercises/follow_turtlebot/web-template/assets/console.css create mode 100644 exercises/static/exercises/follow_turtlebot/web-template/assets/console.js create mode 100644 exercises/static/exercises/follow_turtlebot/web-template/assets/gui.css create mode 100644 exercises/static/exercises/follow_turtlebot/web-template/assets/img/download.png create mode 100644 exercises/static/exercises/follow_turtlebot/web-template/assets/img/four.png create mode 100644 exercises/static/exercises/follow_turtlebot/web-template/assets/img/gzweb_btn.png create mode 100644 exercises/static/exercises/follow_turtlebot/web-template/assets/img/logo.jpg create mode 100644 exercises/static/exercises/follow_turtlebot/web-template/assets/img/one.png create mode 100644 exercises/static/exercises/follow_turtlebot/web-template/assets/img/pause.png create mode 100644 exercises/static/exercises/follow_turtlebot/web-template/assets/img/reset.png create mode 100644 exercises/static/exercises/follow_turtlebot/web-template/assets/img/reset.svg create mode 100644 exercises/static/exercises/follow_turtlebot/web-template/assets/img/stopwatch-solid.svg create mode 100644 exercises/static/exercises/follow_turtlebot/web-template/assets/img/submit.png create mode 100644 exercises/static/exercises/follow_turtlebot/web-template/assets/img/terminal-solid.svg create mode 100644 exercises/static/exercises/follow_turtlebot/web-template/assets/img/three.png create mode 100644 exercises/static/exercises/follow_turtlebot/web-template/assets/img/two.png create mode 100644 exercises/static/exercises/follow_turtlebot/web-template/assets/img/upload.png create mode 100644 exercises/static/exercises/follow_turtlebot/web-template/assets/local_functions.js create mode 100644 exercises/static/exercises/follow_turtlebot/web-template/assets/main.css create mode 100644 exercises/static/exercises/follow_turtlebot/web-template/assets/split_style.css create mode 100644 exercises/static/exercises/follow_turtlebot/web-template/assets/websocket_address.js create mode 100644 exercises/static/exercises/follow_turtlebot/web-template/assets/ws_gui.js delete mode 100644 exercises/static/exercises/follow_turtlebot/web-template/launch/follow_turtlebot.launch create mode 100644 exercises/static/exercises/follow_turtlebot/web-template/launch/gazebo.launch create mode 100644 exercises/static/exercises/follow_turtlebot/web-template/launch/launch.py create mode 100644 exercises/static/exercises/follow_turtlebot/web-template/launch/mavros_and_turtlebot.launch create mode 100644 exercises/static/exercises/follow_turtlebot/web-template/launch/px4.launch diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/drone_cat_mouse.world b/exercises/static/exercises/drone_cat_mouse/web-template/drone_cat_mouse.world index 78a8e0612..6e9981ecc 100644 --- a/exercises/static/exercises/drone_cat_mouse/web-template/drone_cat_mouse.world +++ b/exercises/static/exercises/drone_cat_mouse/web-template/drone_cat_mouse.world @@ -1,81 +1,53 @@ - - - - - - - model://sun - - - - - model://grass_plane - - - - - logo + + + + model://sun + + + + model://grass_plane + + + + logo model://logoJdeRobot 0 -4 0 0 0 0 - - - - - 0 - 0.4 0.4 0.4 1.0 - 0.7 0.7 0.7 1 - - - 12 - - - - - - - - - - - - EARTH_WGS84 - 47.3667 - 8.5500 - 500.0 - 0 - - - - - - - quick - 1000 - 1.3 - - - 0 - 0.2 - 100 - 0.001 - - - 0.004 - 1 - 250 - 6.0e-06 2.3e-05 -4.2e-05 - 0 0 -9.8 - - - - - false - - - - - - + + + + 0 + 0.4 0.4 0.4 1.0 + 0.7 0.7 0.7 1 + + + 12 + + + + + + + 0 0 -9.8066 + + + quick + 10 + 1.3 + 0 + + + 0 + 0.2 + 100 + 0.001 + + + 0.004 + 1 + 250 + 6.0e-6 2.3e-5 -4.2e-5 + + + \ No newline at end of file diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/gui.py b/exercises/static/exercises/drone_cat_mouse/web-template/gui.py index 7ab9f1055..74b57bf76 100644 --- a/exercises/static/exercises/drone_cat_mouse/web-template/gui.py +++ b/exercises/static/exercises/drone_cat_mouse/web-template/gui.py @@ -274,4 +274,4 @@ def run(self): dt = finish_time - start_time ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 if ms < self.ideal_cycle: - time.sleep((self.ideal_cycle-ms) / 1000.0) \ No newline at end of file + time.sleep((self.ideal_cycle-ms) / 1000.0) diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/gui_guest.py b/exercises/static/exercises/drone_cat_mouse/web-template/gui_guest.py index 7aac25b87..015f2d2d1 100644 --- a/exercises/static/exercises/drone_cat_mouse/web-template/gui_guest.py +++ b/exercises/static/exercises/drone_cat_mouse/web-template/gui_guest.py @@ -274,4 +274,4 @@ def run(self): dt = finish_time - start_time ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 if ms < self.ideal_cycle: - time.sleep((self.ideal_cycle-ms) / 1000.0) \ No newline at end of file + time.sleep((self.ideal_cycle-ms) / 1000.0) diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/hal.py b/exercises/static/exercises/drone_cat_mouse/web-template/hal.py index f039b4c15..de7d0570b 100644 --- a/exercises/static/exercises/drone_cat_mouse/web-template/hal.py +++ b/exercises/static/exercises/drone_cat_mouse/web-template/hal.py @@ -78,4 +78,4 @@ def takeoff(self, h=3): self.cat.takeoff(h) def land(self): - self.cat.land() \ No newline at end of file + self.cat.land() diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/hal_guest.py b/exercises/static/exercises/drone_cat_mouse/web-template/hal_guest.py index 0dbe1eaba..ac9b969f2 100644 --- a/exercises/static/exercises/drone_cat_mouse/web-template/hal_guest.py +++ b/exercises/static/exercises/drone_cat_mouse/web-template/hal_guest.py @@ -78,4 +78,4 @@ def takeoff(self, h=3): self.mouse.takeoff(h) def land(self): - self.mouse.land() \ No newline at end of file + self.mouse.land() diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/launch/drone_cat_mouse.launch b/exercises/static/exercises/drone_cat_mouse/web-template/launch/drone_cat_mouse.launch deleted file mode 100644 index 2fd263a6f..000000000 --- a/exercises/static/exercises/drone_cat_mouse/web-template/launch/drone_cat_mouse.launch +++ /dev/null @@ -1,99 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/launch/gazebo.launch b/exercises/static/exercises/drone_cat_mouse/web-template/launch/gazebo.launch new file mode 100644 index 000000000..59cca93c8 --- /dev/null +++ b/exercises/static/exercises/drone_cat_mouse/web-template/launch/gazebo.launch @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/launch/launch.py b/exercises/static/exercises/drone_cat_mouse/web-template/launch/launch.py new file mode 100644 index 000000000..62b8c6d67 --- /dev/null +++ b/exercises/static/exercises/drone_cat_mouse/web-template/launch/launch.py @@ -0,0 +1,164 @@ +#!/usr/bin/env python3 + +import stat +import rospy +from os import lstat +from subprocess import Popen, PIPE + + +DRI_PATH = "/dev/dri/card0" +EXERCISE = "drone_cat_mouse" +TIMEOUT = 30 +MAX_ATTEMPT = 2 + + +# Check if acceleration can be enabled +def check_device(device_path): + try: + return stat.S_ISCHR(lstat(device_path)[stat.ST_MODE]) + except: + return False + + +# Spawn new process +def spawn_process(args, insert_vglrun=False): + if insert_vglrun: + args.insert(0, "vglrun") + process = Popen(args, stdout=PIPE, bufsize=1, universal_newlines=True) + return process + + +class Test(): + def gazebo(self): + rospy.logwarn("[GAZEBO] Launching") + try: + rospy.wait_for_service("/gazebo/get_model_properties", TIMEOUT) + return True + except rospy.ROSException: + return False + + def px4(self, instance): + rospy.logwarn("[PX4-SITL] Launching") + start_time = rospy.get_time() + args = ["./PX4-Autopilot/build/px4_sitl_default/bin/px4-commander","--instance", str(instance), "check"] + while rospy.get_time() - start_time < TIMEOUT: + process = spawn_process(args, insert_vglrun=False) + with process.stdout: + for line in iter(process.stdout.readline, ''): + if ("Prearm check: OK" in line): + return True + rospy.sleep(2) + return False + + def mavros(self, ns=""): + rospy.logwarn("[MAVROS] Launching") + try: + rospy.wait_for_service(ns + "/mavros/cmd/arming", TIMEOUT) + return True + except rospy.ROSException: + return False + + +class Launch(): + def __init__(self): + self.test = Test() + self.acceleration_enabled = check_device(DRI_PATH) + + # Start roscore + args = ["/opt/ros/noetic/bin/roscore"] + spawn_process(args, insert_vglrun=False) + + rospy.init_node("launch", anonymous=True) + + def start(self): + ######## LAUNCH GAZEBO ######## + args = ["/opt/ros/noetic/bin/roslaunch", + "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/gazebo.launch", + "--wait", + "--log" + ] + + attempt = 1 + while True: + spawn_process(args, insert_vglrun=self.acceleration_enabled) + if self.test.gazebo() == True: + break + if attempt == MAX_ATTEMPT: + rospy.logerr("[GAZEBO] Launch Failed") + return + attempt = attempt + 1 + + + ######## LAUNCH PX4_CAT (INSTANCE 0) ######## + args = ["/opt/ros/noetic/bin/roslaunch", + "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/px4_cat.launch", + "--log" + ] + + attempt = 1 + while True: + spawn_process(args, insert_vglrun=self.acceleration_enabled) + if self.test.px4("0") == True: + break + if attempt == MAX_ATTEMPT: + rospy.logerr("[PX4_CAT] Launch Failed") + return + attempt = attempt + 1 + + + ######## LAUNCH MAVROS_CAT ######## + args = ["/opt/ros/noetic/bin/roslaunch", + "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/mavros_cat.launch", + "--log" + ] + + attempt = 1 + while True: + spawn_process(args, insert_vglrun=self.acceleration_enabled) + if self.test.mavros("/cat") == True: + break + if attempt == MAX_ATTEMPT: + rospy.logerr("[MAVROS_CAT] Launch Failed") + return + attempt = attempt + 1 + + ######## LAUNCH PX4_MOUSE (INSTANCE 1) ######## + args = ["/opt/ros/noetic/bin/roslaunch", + "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/px4_mouse.launch", + "--log" + ] + + attempt = 1 + while True: + spawn_process(args, insert_vglrun=self.acceleration_enabled) + if self.test.px4("1") == True: + break + if attempt == MAX_ATTEMPT: + rospy.logerr("[PX4_MOUSE] Launch Failed") + return + attempt = attempt + 1 + + + ######## LAUNCH MAVROS_MOUSE ######## + args = ["/opt/ros/noetic/bin/roslaunch", + "/RoboticsAcademy/exercises/" + EXERCISE + "/web-template/launch/mavros_mouse.launch", + "--log" + ] + + attempt = 1 + while True: + spawn_process(args, insert_vglrun=self.acceleration_enabled) + if self.test.mavros("/mouse") == True: + break + if attempt == MAX_ATTEMPT: + rospy.logerr("[MAVROS_MOUSE] Launch Failed") + return + attempt = attempt + 1 + + +if __name__ == "__main__": + launch = Launch() + launch.start() + + with open("/drones_launch.log", "w") as f: + f.write("success") \ No newline at end of file diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/launch/mavros_cat.launch b/exercises/static/exercises/drone_cat_mouse/web-template/launch/mavros_cat.launch new file mode 100644 index 000000000..cd698651e --- /dev/null +++ b/exercises/static/exercises/drone_cat_mouse/web-template/launch/mavros_cat.launch @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/launch/mavros_mouse.launch b/exercises/static/exercises/drone_cat_mouse/web-template/launch/mavros_mouse.launch new file mode 100644 index 000000000..5db201ea1 --- /dev/null +++ b/exercises/static/exercises/drone_cat_mouse/web-template/launch/mavros_mouse.launch @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/launch/px4_cat.launch b/exercises/static/exercises/drone_cat_mouse/web-template/launch/px4_cat.launch new file mode 100644 index 000000000..731b1b038 --- /dev/null +++ b/exercises/static/exercises/drone_cat_mouse/web-template/launch/px4_cat.launch @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/launch/px4_mouse.launch b/exercises/static/exercises/drone_cat_mouse/web-template/launch/px4_mouse.launch new file mode 100644 index 000000000..bc8ac1ce8 --- /dev/null +++ b/exercises/static/exercises/drone_cat_mouse/web-template/launch/px4_mouse.launch @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/exercises/static/exercises/drone_cat_mouse/web-template/mouse.py b/exercises/static/exercises/drone_cat_mouse/web-template/mouse.py index 6c6046801..7f1b44ec0 100644 --- a/exercises/static/exercises/drone_cat_mouse/web-template/mouse.py +++ b/exercises/static/exercises/drone_cat_mouse/web-template/mouse.py @@ -40,7 +40,7 @@ def reset_mouse(self): self.mouse.land() req = ModelState() - req.model_name = "firefly_1" + req.model_name = "iris_red" req.pose.position.x = 2.0 req.pose.position.y = 0.0 req.pose.position.z = 0.05 diff --git a/exercises/static/exercises/follow_turtlebot/web-template/assets/console.css b/exercises/static/exercises/follow_turtlebot/web-template/assets/console.css new file mode 100644 index 000000000..4e018c8aa --- /dev/null +++ b/exercises/static/exercises/follow_turtlebot/web-template/assets/console.css @@ -0,0 +1,35 @@ +h4{ + float:right; + padding: 10%; +} + +#Console{ + background-color: black; + font-family: 'Helvetica', Arial, Lucida Grande, sans-serif; + font-weight: bold; + width: 100%; + height: 200px; + color: white; + overflow: scroll; + margin-bottom: 1%; +} + +#Console ul{ + list-style-type: none; + padding-left: 5px; +} + +.terminal{ + width: 20px; + background-color: black; + border-style: none; + color: white; +} + +.command{ + margin-left: 5px; + width: 90%; + border-style: none; + background-color: black; + color: white; +} diff --git a/exercises/static/exercises/follow_turtlebot/web-template/assets/console.js b/exercises/static/exercises/follow_turtlebot/web-template/assets/console.js new file mode 100644 index 000000000..350832fbb --- /dev/null +++ b/exercises/static/exercises/follow_turtlebot/web-template/assets/console.js @@ -0,0 +1,73 @@ +// Keep a list of commands +var command_number = 0; + +// Get the input field and the div +var command = document.getElementsByClassName("command")[command_number]; +var python_console = document.getElementById("Console"); + +// Empty the command +command.value = ""; + +// Get the Console ul +var command_list = document.getElementById("Console").childNodes[1]; + + +// Function to go to next command line +function next_command(){ + // Make the current input readonly + command.readOnly = true; + + // Create and append new list item + var new_item = document.createElement("li"); + new_item.classList.add("Console-item"); + + var new_terminal = document.createElement("input"); + new_terminal.classList.add("terminal"); + new_terminal.setAttribute("value", ">>"); + + var new_command = document.createElement("input"); + new_command.classList.add("command"); + + new_item.appendChild(new_terminal); + new_item.appendChild(new_command); + + command_list.appendChild(new_item); + + // Maintain the content of the console + // Otherwise the computer will hang!! + if(command_number == 100){ + var command_to_delete = document.getElementsByClassName("Console-item")[0]; + command_to_delete.remove(); + command_number = command_number - 1; + } + + // Make way for the next terminal input + command_number = command_number + 1; + command = document.getElementsByClassName("command")[command_number]; +} + + +// Execute a function when the user releases a key +python_console.addEventListener("keyup", function(event){ + // Enter is pressed + if(event.keyCode == 13){ + // Prevent Default commands + event.preventDefault(); + + // Get the value and send to Python Interpreter + var console_input = "#con\n" + command.value; + websocket_gui.send(console_input); + + // Call the function + next_command(); + + // Focus on the next command + command.focus(); + } +}) + +// Execute a function when clicked +python_console.addEventListener("click", function(event){ + // Focus on the input that should current be active + command.focus(); +}) diff --git a/exercises/static/exercises/follow_turtlebot/web-template/assets/gui.css b/exercises/static/exercises/follow_turtlebot/web-template/assets/gui.css new file mode 100644 index 000000000..64bb88d61 --- /dev/null +++ b/exercises/static/exercises/follow_turtlebot/web-template/assets/gui.css @@ -0,0 +1,30 @@ +#canvas_container{ + float: left; + width: 50%; + height: 500px; +} + +#gui_canvas { + height: 350px; + width: 400px; +} + + +#gzweb{ + height: 40%; + width:100%; +} + +#lap_heading{ + padding-left: 10%; +} + +#lap_time{ + padding-left: 10%; +} + +#birds-eye{ + background: url("./img/map.jpg"); + height: 350px; + width: 340px; +} diff --git a/exercises/static/exercises/follow_turtlebot/web-template/assets/img/download.png b/exercises/static/exercises/follow_turtlebot/web-template/assets/img/download.png new file mode 100644 index 0000000000000000000000000000000000000000..e26385321b2e7fbf7071ed18e3135cf01f4e5ce1 GIT binary patch literal 16128 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 literal 0 HcmV?d00001 diff --git a/exercises/static/exercises/follow_turtlebot/web-template/assets/img/four.png b/exercises/static/exercises/follow_turtlebot/web-template/assets/img/four.png new file mode 100644 index 0000000000000000000000000000000000000000..1d04e1786dd838aefb741fe120ba0f3f003b7dde GIT binary patch literal 15373 zcmYj&2|U!>`~R8Im@<~S2_;J*-N=$GO_nLbq$t_{QiNq*muSj-TvYS!2 zkhKxYUa|~XvK!3&&**-Cum63$uK1kKdCs#x&pGG)iMXV%!^Lrc0|3B<*VQrv011CZ zf}QN}pQYPh=8lA6NZyP>U|alw*2pQP9lQ_qhf#3Mt9 z#hi=aszPY-e*2v$Qr|WEol0-Z{rrswgVHo|CH(a226C(YI@FBQSEsWQe%yo1!kk7A5DlCq%q2`rP=0*b6sG5YTVrVTGX(g+_9Nfn>i3Q%!c>F$B|yW zB3!({R9|$9!`$(BLkKaV%1Wpmp}ny_t<5d@du}&wniB;kTPMHXwkb8}!K60bFYryu zEGP8{xMXD>!hbxoB>h@1(5mh1yeCOGm9hL4Prep169K0mmUIafK8pZ+a#$txqcmy! z#Cfc9v@QI3r7h1nQ;f89H`Dg|?2GvanQUF;kZQL^-AQqY7;j#%RPg!DZwXR+&#$0)l3ZBcvB8F8AkYkX*Krg!R5^e9PqsN5lQ_Nd?pFAAu+`MTc;&uk;f z#QUb{r~FjDz^w1H#yZ|LUhH|Vj@S77>!ufus#$X@>dQeVw(5sSF!w0>QPGt}EwVW^ zo;ECNyf`I1dAIwQB?nr3D>8EqPj4s`5{oPR2a);$Py}B(j5@ol;6wVCY2EUUa?N+vOVSYd4gVQ|?>r`s!YJ_U1Xk&Wcxn-CZ{5?k%~Lraug7IN>Ay$(zFMw!s}5hiZm}!QGgl}r@4^Q{-$xbEuVBKJJUAN9hzpJCF!)BAx}?A zl0xz52FCR7^a@83zd+NbOVOU01V-nlxkbqs`s_WBVSCOb@OohR2m(JGv(yc>pXQr_ z2__`>>1P?;n<}Bhb42o?LfJMWBT-kc@Y#iE+`~udxUlq-%-+IBk3TM zJVfQgapW)E&98TjQvaK#N2>2wX}Z1-1tOD%xK~Z{mkiD8It4sU87tGwqE>Y{5 zNflg-2qEq_0vurcBvvWp?Nb~3g5&HTtqvVoM5KQy==Oi~Ia@sVGBx4WjKH&-LLA@D zB8ZuzMVWOcN>;|S10&?`SuYWXPnDab@__6oAHUz zs)3WXdYFugb3Ie1KTd{`R%W5@i>Gm3VaQIMSN`8N{I5FNIz`#=3iw1%UjPFS^MiG% zX_Um(IryGhZ$OMN2ktRTU@=6vw7_MzqogG=DVY}#YtuLsPx zZnP)zA9yKIxCn3p=0YinKH1+Ty9eASgU9T@yU|{q%{vPAL-VO0)LvQsIN2R4Z{VGZ z2Kp=i#wPKd3fjcA8He74y!w6IuZ3ue=?(VXJQZ6`h{v?gr;_o-Gq~ zIi|$U*5YYZ&>4+lM_796QfnmY9_j4r(n07p&(y4ru{kiLMG{Z{Ln#)~0CQ>~nektA z!bvNyPf5-%XR7mJUIO25gF!r;cVh>ic03#28MWURicPxd^`cO2NYqYBU6SU1JF0l& zD6oe4K;4YzCHWI-ygOwLvGhXh3Rm|5>!7P6jS^eEH{Z+22ohan8&Y-@7CgO6Y72Ox-2Vcqndz>5G{mm)8yB+61ft>0Rf*H`5*uCWK1ZY z&C`q%;z54IK1oXE*s`fITXQ*p{)Ofx?xh|qAUi{AX8Q@ocCE8klv{Ox-W9jk?_4tvQf9n3Q{(B9^O*giZg#5P zQQ&cVP^YmMik{l>%+1Of0TQZJcUC5foUhKalD^Ym4AL(H3z+zC9Ec6}X-}*Wc!}r@ z0F?6Pz}FG8+{ll=5qQs|{Fukp+&}hm;4b*jsm&WXEJaWfORb#|#QfLyW4R5k*;_Ij z#kbxj6&)V`myZo)w+Ck6SHjw!9TWfbb9qj(5zP;BLe}(1v(o$+o@#EAA1@I#@Fs}a zwRmc=*~+fn3;IIIRK!a~r(Z^f2}=_sMuwI2g-Y%?2C}A}M~% zoVSp;+fkq};5W=j7z-N-`*?H?iveptCXF<&%nGBTraik;xKw^%bxpi5c74{XGlV&u z%-U&v2IxExS7!-)0;)0z$Bc6mAp84*$w)8AlpE5aKEC5wBulR%7rVuT3Y>mirl=ed zAG{*NH#CiX;1+BRZVcDWonosRxl%=u_JMNVdHTE#SX?&o;cuv~ljy9d0JC39nai5% z1NOGunc4a(d& z8?49XkejlJ$Ouv;b9uF9WRfA7)k<#Cy)+q6F*j6kn19E{;;GUGm)b{rKxjN##B5x*3F#6YAG+fBbrd-j5{&{-l=SW ze&+E2QstYa-qWc^g_^#427JWRtG}4@P}X-WS^3Wxq+R6I&4sg_tGTIMYO5OGQiE2Q zpY7DY(y(Z-6enDZ@@h52i$E^FFkL2*EEo>8um?KoDerdAsG&wOL{+-bM^1vN2pSG^h~ z*VPX-xn8JQ_Pz$`)tk1}@o6HA`4bKUl8GL@-!Lo5M<~lvn~jGhO_swkW_?Ff*l$5aA}B3!iteh*9p+zO^S-wf0T4QD}5W_x9D zD+z$%gK6%sg|!PEDwi(eO`k-fWEm2-OOY`=|2+0yKm*6GT@r({SS>3X-fgpXtfzb% zVKEcArfEBLAaNb(wK~KmwQy=t68bIJ{oVpk_=VLv_)He`^wovRhq0dW^`Jf3qNa_Z zi|sGels?jh8Yy3$A!E}qg?>;^L_PsLA#S;s-5HxAKdxD*06^!xY`yCDeh1W~S^hCIx+$V}%>;4cGI-Hcf5mExjo2%F1$fsM8|B#0uy>z?BIo<+ z&^_b-jG$O+VBd4#+{qJIA{?+Q2(FQzWi8y;OqS@qc6A?x7U2(Dc{!0pLF;P(__kAl z#d5k8+RFk=U0vGvn21Q&4?t?96?(p-4RUla0g=M;t>2>ji8WS2A|U3V39yJy@U3V8 z?Ho9~Fv)EK`flNK4C_9A2)!Z;U`cZG2@TNhYeWHG z9!;W<_kyn76e?p+pT=MP3?$AVLWSx7gKn5*p}Uch!}liw0t1p7cK}}z1t26(vPqzy z>9TrTHC?|YE7F7-`@+4`E?!V(e~&`DuoR`?!bbU{qn6>1O_ zYv)=4Y?PJi)%g=6yRgEV;y8N);X`O=86Db4h5u0&q8Wi%otL zt>V2NAXVl)7&9$kb9?f4(yWNaFYG(k3QmAe=)VRDcq8)IY?z8XPeYNblj z0$dKho<`ZRoMvb1zCm?v;RqHeDrzkY9yRe@{Pb858Iu;k z;CRHTu*Q!LBc$%CLdBFNpMUDwn=nhQM2-BqsDmS0s0&QqJN_Q7652p+KPR0c?(r-s z2Sx!%9y?} z)r0mQGl+yibVq%slK*HaBE_|>Q;YrSOeMd4YJcOWFOH&tU8QF5EW`6m{C z@B|s+l>!@57e@Blyev06lhnvZ$@%>fe0e`P_VI!LW8~kJF4GoVV*y`}Y>(;@4HsJt zv#Ql;JQr($z_S_>;{wR8o?Ma$?EBVY)i{p~BMdg|1v&DhTjE>KBUr)q+e${xKyN_- zk#dY*VDQt3tve^f4{ZQ4r^Yii<3q!Ut-s<~4kB9ztkn^z z&X7{i)NH0YhLXd6Q@g_5XlA_Rm4$(;fPx<5)m%2e4{fOXLKW4-dVlo8w2@I7!>A4 zv_$DQ7Y9i3YI$4!3H^taRd42SPByGS3Z%6REe>M&V375p&bhY*wy%ym%lN@y=452O zY*--cX;aJbcP<@9iOO9I`YRpAW<$#7w*GriGwfjmnjPq@(z?OsYX#tLOIh%x1pY2U zuHTQppl9BYecLaEdKUi5;H-RWlymEjLGdUwgL_U_2k^>OFy)>M;FsIh9qsD6#@@8U zE|%FMRBwbFH4?kU3GMCIZ!spY842ThNe(3L7aC@ttb{tT8lX9Y#F-MG`000DRi%8; zvuXAyQH23MV$G`XZEQai*?iy=y81VZ1^anf>qAn&{P!=ztiI3nBSI|e3{U=?K%`93 z+w#f-@$K7MO(>Kr`LUF~Uz3#9-;DM(0#Wk4z6*=s+G)3LV&PEJi0whBlA=8&RFZth z^w0bKGAvIx;@o;qpPSq6tz|{gULV45xwQoKG-gMNEp7Xa%Z{=N+H9!@4^noSrqoy^ z_i(}V2b03U$Sz`QRlZSzqbDEA}3`jCUp<&4H6~>OHW#c09q?{Itf`SxyS3Z}SxBisxYuZ03Wi zrmuxPo<15z(Dz#CsC_#QEM!2}DpR?_M-E61urepltxarnJ!K@_(oYF|s`lj|Cap5R zF-0aR>u&db$C@~{JvSX5>a^D68s)rI#eca88=%3@QyoC%kX%<7p)lx_-+9*bIE^0EvI{rDEiDZ@IMzn@4#B+pvc?w`MGJXPBSR=l=Cl!hA=)9L;gU!!k+m4d-pq>SG++)?!ZjtLWgTwvf zrT0MQ#p!##AF4da6)%gRvAH;z0ZvzP3_V-|-jpMEf19X?M-zSB=RH%W1%_Z&7CZ`u zTxf^u%FA_Mwph|b04Xz1xqwPH{*@*K-S*oV#C$v1Z7kXH7|_0^>1Gb{VU}FxFS;f4 zUXYXFq$yT5@Xzm#F3a`tuiG`RPTry~P&k7;Sm|e`8Sp2~AN+nMpd>9?4R7Qgzz8w^pty0}gn$J}p|)a%C&(;Yu#{oO3VM(-`F7m($O`u<@I zcR|@xu*WXCck5r6>)sY`q_2{W1Czd?5WWnu`zfCsm|VtTlLHwOzQ?Ss$T7;Q?W=s;AR{qfidsTL`@|0L!2i6xfv4T=Dj15x<`mNK3l3J0shQ@Y}V(=3Unt7%B-KoXq8X zAJ{tlBP(HE49sW?j*Ea{Np{7VdcyPx%TmcBXJ$}*tnc@;!^QI)AP9i$uT{aRi5rpk z8_4c?i@yj(mh(!K+U-#Dj8Zz1j-Kz`HzCve%1`r8Q}t_fiab$n3cd`Sdyt$$Da&FD zwF&$?jNmN~*H_&b=I@gMf`4%%Z_mK!ygs%&RRuNzC6Aq%8%TI5@#Rz=+7EU^kQPfB zdEEgkWS%m$9l8kc|4;j|Ntw5`eYUDZIV%M)GD2X>Zrv_JPzI(%DC;H;F)8K23tswE|Am@w(tFG4 zwr-b_a1G`ijrm`MVV7+a$SVG2nfkow5hoAr*+h}ow&Sgb<}vHL7C0#DgUyJwhQ#r_ zkY^i_&YQpgW@oC}9YAF;{8hApaonW}fc+?Ti=uZucjB^*#M zwrv4|cJ&%#GPCx(MN5?m1gX#v{(l&Bc$mtho5PHl8F_|yfg{Rydc_aw)B&St8%duQec)8m-c0WJu-yl}*S6o>AMCVD zN+}$!?epXGgn5_8E#NoLk0LO?QT^O$k!G+)dBs*>^`-#lF->U6`H#>u31*H0!+~1U#`nbwXhZHe!I--H({?6 z;-Y2pC?=24S~AH&3B<4amVg^S*2z!Gv>io&UcC zx*m~7__r8`kh6cpt2WW|e_2RFqx-$?o_>bC_aYDCfQwVLIdPWNkNbY6>HVjpqYE2Q&2UEoduyI5o2J`r;L}s4pW^BEMUMFm~3T&JYU&zkhHm3*42e z#Ik^5L$}6{nx6h@>iJ>eO`YlJBBUG}F|EW`v@=>SAW4xG;En|+8+@48J91DI zi`3C0n3xoP51W@yHUAWS@{#F7HH_!hKD@x31JvGXpE_e*9$FyHGMYhbdD4$5k(tvr z;M$Go`k^zsMfYfPGRj}1Hjt${ET7*=C!b%RxIdMvj-{P^@0hC0crNC{2kwFI1#m++ zxmnK2hA-jEf)(6uG)i+34^7sVFj?G%+KU^BI{`qTv`_jReLd{^j8pWmD^0a5YQ&B!uO$J@Fc zs)X=AzF_N454+pC-LAU%F64!VMk%~HwbncA;PU&;XgYc7YGz&r*+NEaMuTA%Ryy@4P(^^Ed{#=b?D^9`ZGONII#C9(N5T9TJz4Xv z)yJ8zoTU#ZZtC!-9D*~lPje#697n4US%(O3C;N$_oWRTnc?5CKs*~0rKXohGPBafG zHZxEk-9S!+9+F_})2oqziN{NtfOe}9!O+&qz|mu=<*t-J`9;=dHBu(Y7_@r{?0DeR z{r{&PS+^>>j%nL5&XEew{sjijL#3AK8FfVNE*i&@FB1ggW#CH3`NXozOPPY@a zg>VDG5Ed37>}2aP@GJY_g#)ll!pL0vMl;)%Ke>yi?5Tc_??I@-|p1rXo> zC&rUtj#(NDfF-11B%OT3?bq7VK)dP zyn)Lrr>)-?Yh!I4+Qo=XQ82O4NZG}*#w>So%N5RPP+)qEu5p0J;P76!J5}~{GM!?@ zp{ZlRktwUK_xm|O$yhbU)NSU(tTxNysetY8av5U$d49&6z+CDQ&3?(&}?!9?1vl@r!?KK}K8@;pDx&Wzz|ISRa7k9zuc zncVtQAAYxLoVbSs6*jkjAAlzcK7Z<3WEQ~@f){4v=sR z&j^luC_oGP{NE1DN@T;Coq31XyoIV!;%;u2?Y{#_^;%(NimO>L>I4XP&f5w)&v)qP zyfUNfygH6?EPv<6v4j?Lk%ShIUz;d`*JIjE%$rSRPN7e;fapHc=={!m&qz~zx7`D-=Ll;j{fO0Ad$~vV2ieT`kv!F-zqc7u#XzbL z^POlS^)5R|udK3hbAc=C|LaenbEQY?hpR52hfgB+U#x~y%j}P4^Lv_){;9}aj%r4! z{96=_N0eeDQ6_*U;a27jI}(<=@I2mZa2P=1{N1gTGo{)cA|w9y)aMcIFXe-=uPG<} zHyvhWg`s<&yo?F>$)oPJgEUomC8I*snHBX9{}3`zi#loSOcSwr2}I5F;(z*Xjd%ZX zFs5053LUN5cpQjNe}v7DIP5l9Mw@6d(%jRr*+9q#qNl>U;wNbn(*HFWxZK+Wyhv;ZLXK>e+ZZFdxGK;$y$tcRyQzVwCK z?Llu0m@%)AE^?D~Y}5eryi(wL$0A4GiA5D&km1A&%A6m>X@IpTOBUGakO}-bsmTj} z(sx&E|L(W_NaK-RZ}c7(L~1K*&jlt*wu&8O5H4-M(T23k>h${yfS4rOK9GlRJOcm@ zI|VO-lWY*h_pP%L0OZ`hK#Fbys^8^y0-)O=o8^F_C@~?xYN-|;Xqg0l@!*5=NqbQg zT@9aP6ablyACH5VIw{(4vdgnKNW0eeH%b8@K54B+&3pd$Yar*m{C(DHf!A8WHZ%OC zCIH@40wTlflRmM>`ai0W`PioOS8goA5#H7$L`;4(HWH$|s9u@ywimAek4Dvg%J4we z8h$@#+I{2~@!!1zGT|JJvl32-6X&M3rM4^H2_28MxZl;b zz3j9L*&K!IVW@N;g`|#`+R&A2K+_%NHgWi;B2U|9vN;M~e0aY4quD#-!?5IhyiTY# zhs4g0+akU~h}5-$)bq;QD%g|)Bwlm1XExi~TQ>#b%@$j#b<&U+$`Ajzb@N>g3b%r@ zaDlUr38z(IGEV9sV!9t2MnZO}^PSDMi4G{-FkTV^723SkYzv$69ii*m*zM)?%@IxM z3*i)_u4ZI<@Y2TP={Sv_p|K$guOaaIP|mqXf>gCO$I_2kGYrrG%~z7j;u1Q)e&aR&0=1Df}su@FNdYaa2EqD zy70cLUd0&!Z&kPcdm!r*?A?x>?^TXgwUS{=!pWrZkP=S@Qgmj{oW! z_~vbwfA#_q1y6XGxmulo#+h^c2xVahn+#R zIWe&pP!GzoVUNg-=M6R^MI)p2<$s}c5OaqQm|t?!!k)bB347BYO?ZL2)|mIymN zyNpIN#ko}lNWV59ftmHe6sSLS=L0h5V!NM5U{^On8O0;X!Ti!-b*TDxa?gT3$l(9- zUKcHCzJR7TRq)gAWB34`qaawdN*Y)tz7EH#e%l?i0XH(IFTsMG|u(F)y%NHT#9F9HbT{x?5fOOA7A2%ECOa5tIcDG zn0v5BYQ}My_iToSl#gr?TtW+ok9Ub}oN{w|6pKHymw4li3HC(y$i8~OcN|0(g4&6< z7q2?b)C%qbP4~2~`+2=+Zx$gw(L%&P$6*uFL3AqaEx&aCNIKE4860h1;nWebC7x_^)* z;klK@0tP2>@4F^AF?AOK;9Yg;7E&6WHxWAv82))0Y#A;k!b-Lydv8vmV_wfeapTjz zxYYrzl{z@Fb1r&jT=}wtvIemJOkE&<(MH@}ZGSYpOU%-3d4d?Cc-hTnxindXC^HoT zttnTc;W7aq!~MKkvoHftV=WMN+#s>>4OcM4?eKbc^JHvs$R?TNvGR+_&$kG|^KEdNrt~UXQ}hysD1I*1W4%g~ zqHNNYyv~|p86rdLpJ90EphTmji_qEz$D`3QzrvXP^BIk#h17!qxqE`` zn~7Xul%Lr3gQ9M5Hy2^iWT-)ub_-g!o$^9K7?jpk$5p?D{kZs(w%sib!Qg}}X^D(o zCq{D+#lw3-a^ZE1)oG=X-aE#!tLq5IA>~gW-Z`Y+!8M{pAO0HlrSB(7^(dh?(;O*0 zL|ZJB<*%b)uHK{T@w+Br+L8~k>|&3m+NsN<9>L(wPnO>ae0wXR38*G+T7e6bOWNuK z+ler61r9^$N zAL!2aEAf%xS|$UqAqiU_u^wP2>amb`p<40GtQ{B@nX6(Ch2i}ju~OwX+vMJe<4nC> zFqJ*5PJZCBql#KA%ORxmJXd++Vl5 z1t4&)`b^2{OJi#=kqGtOU?g|Ye1YvGlAPP!_V zhDFk^R9Q)%Brz^I)GVQAA=u<#JL+11sro^+Ls9v!4#c{0&Cak4Pwj59?66uHjbCH#?Y_%Wfyr2Q}Wf@FZ-2&d@?= zG;0}{c(hj>2G!XIT<)6aWFH8$rw^%j%(CGQ*Bim$|Jufuc^QNxPg90&+Gi9MYR36p zR6Go2#g$YCF3V6KqqoJPLZ~yN88y*$;d7l@TWdIx0^mRPPJ+<*EVUId)@@tBIeElC8-Y+WGR|Vdto|!*0o8cj8J#`bSfE zz_WQ8gqF^famQOgr_*+{6*O<*8smlANZ4C6L{z7#T6l6VcpHd-lB4B$gq+ACwzLk2 zBO?ULiC85}o!NL$ZYcK9h)1kjFkWXFPMWFOcgNdS|_nTK79BH2!xLoqGH0xzob-^m$9Bci!x6cd)ssc545qU4x; zL-R{>eG?!~Q>*Zx-z?Y@+|m8pdY4#UAKrA8)y};RZ*yLN?Hi7~_B@m9=jY`NOPCSQ z%YJsB@$^UXY8AWfB+kVet8C@%F%P#IWCQwZZ7@A~`FTkiAX*ugozv?<082Rc6vtvay_aUd(K$q&KUNU#Or*EcFe@Zg_XF-x}tG+z|_8V_9 z^*((#+vd0XZ?Osx{Ve~!Y}^v@L_7u%cWj!N=rc&~9}&k5VTg&+Y*?r;-LDfZAKoQS z?^bZi1s8%!Gt;ZN5de2KTDN-8??IeXA%F#6QlhR8;@H4n3-_S6I`kgWkFSEh1KmS` zG{gd10wKClmD%lWka9B@Ng+S6a2F`QWkgkD0sIzOMe%}eV0ek0D#+8P7+S3ywUq?-vC1 z;f{&gd|zPEAMwISX|gmuh;jyjL%}2)*eAWRLerkCqHtInWMSOWeilb+(`uv}5cjOu zdxkVJs17iH8|Q|R7$SJ(s&*up{F`uDhOhS9m~3>K8A9b+_0yRrOJ}y{jkeX@T1W_V zM~x&JX2CBS$}?*q(i_4GNxuHu))gJ>jN2ZhHFfY*oUUI6D7>3l0X=y;b@HKmUaVxky&wcohtrGO;4>A|w zJ^@rN@4**68@g0jw8T<9>-?LxMY`d25CvlDcj1|^p4N8x?KY*q1qY%1BVFrF)_hsa z=B!0!_QD7YE)1*u0u@SvD?-qz3{9B@@%C-&_)3elkoeuzVYk(xPt^-Guy=CHawm%#YtTq9+@YE!B) z%Ze7ajHq+x1Kq03u0zE8IS7rvKsK7x(50*4C$Q0Mz|1Oh-0eFPj;oBQ=)JlO?2keq zviP7F?INj%^+IC5r1+LTG!DgYx5y=6aVgiR_w^~kd65q-;XqWX{`EL<*wh;RdlxE$ z3!*Tk@t&6#-!#;Ys({X-`s1`VsQ<3Qur=0K1@L|^HdEt?Zz0)!JMD!@cn6ITId3ta0 znr=2f2fk}AccJ-o8z=$ zJqwx&H>ms)vxLXP?|oJ|SF$3JBf2?;)-o>HNlqi?EdG*0GRN^u_{Gh4XENY;^?d|N^a>#MXJ^PFmi&mJ0R??fL({hP|C<u-`I zw^$oThzt+=a2cpVPn0z&-TCWD*$3}~U~5Lw^+r!=a$Df0CSu+U;#iF&P1t3852A5| zwFrGHnh$kV0F|Kwq3<&!o5Qp~e8+CKyMVL@Pp{52z|&PEw}#FjCZS>cQZuK&NVE0g zv27lQK^kn}vb$AjgjZJ5s#7XF)kI|HCAbB6)!1b8nhm98RGVyp8 z{y(-~cT$N1mkxc}Aa)6<*g{#9DDX?(NfdjGrw_ZPEond>37J*lG1Nv(IzZH}Zr>4_ zE576D(!t+!`wY{qpnKuH zN(yZdBm1PxoT{xju}evxiM6H1lzLGH(w>yrQtRVFn5?gWa$4*tci?5*r=^z>&NQvR z6V17mE-)3|VzsHRq>qZL_z)4zfcZV}1IjF;9PXT|^KzxUt((D5OTC&-NYVBv8ch16 z{$%n5jlS%?1UgM2;}&hU-0g8EM`VPme`V;WbQ-8Pi@+Kd$6boZc?m$*D^FR@ymj@v zUE}M)g+*i5KWBXIci(x|=HSZxt!)!NQu{ROOL2#1)SA$b$}MEMI8UU_XO5q8aZB49 zSY?)YbML-MAzna%iKF_gBu9g*;_6ueqf!CLRy|q&NBYE8U$vGoS_W0On6lEj`Z=38`U9QRG{e0VSm@aOck3eVd8^Zx)xa|2%h literal 0 HcmV?d00001 diff --git a/exercises/static/exercises/follow_turtlebot/web-template/assets/img/gzweb_btn.png b/exercises/static/exercises/follow_turtlebot/web-template/assets/img/gzweb_btn.png new file mode 100644 index 0000000000000000000000000000000000000000..bf614b27e0b73e6958a7719de29194f6f2d44883 GIT binary patch literal 49064 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 z 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

WvUl-@11DT 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 literal 0 HcmV?d00001 diff --git a/exercises/static/exercises/follow_turtlebot/web-template/assets/img/logo.jpg b/exercises/static/exercises/follow_turtlebot/web-template/assets/img/logo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..10a8562242f72f80bdbea29c2dd131e574479c41 GIT binary patch literal 57771 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 literal 0 HcmV?d00001 diff --git a/exercises/static/exercises/follow_turtlebot/web-template/assets/img/reset.png b/exercises/static/exercises/follow_turtlebot/web-template/assets/img/reset.png new file mode 100644 index 0000000000000000000000000000000000000000..29ae9cfe67df312bf27a279ce92adcc4604d983b GIT binary patch literal 27404 zcmYhj2{@GB8$bNa48}TCgzVWxXzXK)7L&D7NnxZ0$rc&A@uifqjVxm;OodcxgsfAN z%2*mAWK2A5CjK5 z!XaKB@NFr)Zv%Ye3bDdE^Ma3P-pjYa?|eZv=R-jIfq!2xE4BP^@JGqeqb{MR1M#5* z&+Atp0)cSQH^48%%QNW8!NBX@`SYgxAV?m<9y#nBUNAdCsJnP(`NJQ!RAiRBHQNR` z<)(h>2rL7Yy#K%b`;-3@Ro^QD&?H|o|tR#AdY09)gT8kH1ejE&$u7wRjG{8NOd;i++v9fmOa^T*%gm;Wc;`5Y0 z2_;tA! zv#b`Tr`2t^d|iefqRPydU;&Poymnl%kS7{2FW)QTa<+HBov5C5+(|@r{6e({q+&j)>l~b}+a* zmUlTEW&|@b?o#BP6Xmw_5y0MsM+7D1vxXG8Z1K1+tn>phcLtK~uQ zWM5C+W2GG-Y9wf+${*iTsRmiKUSbI=NTop*sw2iCH!Xc^z{~?kw{$&OoXU^_k#}6e z8T|2!TFgG{LNJED7=m*S>$j!5kd*!cX>*gteUdlP6+EyeEuCRG;jj~G3W*ydj*%no zqBkui>_j0?OVU|nD(viX*KRAHA#Qw47U6dK6Qis8c1IXiq%K5&3J*u5V&0SuDR}52 zM4)s%Mc!R8)mFkTA4K`_vULqIbOahUqa;i)O(_Uwr9z#@BMAP0wiE zo))8ADHl-}qH}oS$`FnIdv!P~kBhD@$_dk_3LpLa01-$$TO7zRe=xr}BQlLrP87?n zQbUFRb<%a(0i_ixSTrOBNr|Vu ziQe;iq=9CIU#TagI|-eHLW@i8j>_H1+?ZZA-NUoxN54i1DeB=NWh-xZgH5-*H{hnCj(h zf_40RLs`imwRNX2NI81QhjoZ0&0>U?3dUev;orEv-Kr)$yt;CTn%QWbc=Zna;ZG%d z0cqQfJ~%$~r~zZgG9oZSb9dW+b_GxW;i|g4l*^lv41f4e=^1QG#-#^i zd(0gETqTCl@{!IA?%Jba$M{RJ!d>{=)EOj&zH1$4)mj3TJuHdibjQo>5sLZHAZwHm zx;4cqpe7zv#L|W+mvDYKmC+*)>JTo<^Ui#ox8V;jDQR&R9J`t}G~WE@Xg;^!Hw@>93&h=0DY~Rog_Axk^I2py-P?+xgH1{|8Cd?z ziW8_3t12TGN+iThhT&H#$TD!cqe=PVGoA-O%Otj}&tKw^N09Ie&mscBzPAp5P=~1b zjR}dY^176TD9VLsKYxj6Nf#sjUryP%u%NS*LQMNW!5t2_fe!VC*McyNKI?|ww8?~k>PajJ8H^J^d+-5wM1HT*L?VD+c#pH8*@Lo-=i6T}K z-ziL3;(tbfI=+a}gz2*<+42gB zBGclvu$l}~YkW&@OajM?_87m?2IR(D9C?Z7p?z|K#&!4zVxpEIdzgC7RnC z4_H9$w&E?8eu;c|gtQmAW4$W4BZp|=%1(J}ktkAcONZ_sVI+qRU}FjJQ9bG=(2|Ii zng{r^C1Z&8I<%c-!M~>0VOQA8M;Dg+d%kjNB4L-wR4|5Ka872+prVVK;()Bk%T)Ik)oPp`IEIKYM&C=n~4w2BuVgnJaZj7ZkGuQoVU^sqS(r;$A9!{TO3-vmy1^3+MF?=WMPSlSp; z%`(6lqcI~Go3i0dCX64%9X*eLEjrAoC5|G7>-o?_i|3fPgZ{z zshO+5VJO8-{~YYV2bMPZT~n zEDoO`5BdRR4*zf6`9%l8LwEtAk_8^N!!G0xBEiE8Af&bO$#);+3g2CIhj5$c9%4n8 zIvR!Kli3voS@HV!Qqd3z1%<74eIcHt{5IEJTTOk^h!EkX9Z}M9lOB+^`n7x+Chv=6 z@v^0b!o^$$=m&upH)*Fc5$?hF7K+I+v*Fv7q6p3}ninbTV77>LS!K}yNa-1J>*n;* zYFu)4F9*eze|vMnoVd1R@p3NyO*}X8bUacy=FF)xOk1m58%HsSWN!O<-?ph$!T7+8 z=_>39Oy}kH#&%>d>mn-vn{PbMSFCJy8c2t4P5kSd0gfz##UT;WSsHlgko?qL+Np%C zKi6p%$|D-S+o%uxpz%q_W*<)hH=Qm0$i0>{|0k{$V-qCKZ7Cb;+&)mre^GJD75lUn z_yNYK6Neyq@Ig$SG@5)oASlj!OYKezFY9{SDK3p39WrW8L=nFLg z$K(wjXC7{}$rFNw=^= z$nf+#hCU7_OlH;WSYHJmJ`sR`_nI~^Tir)9BcYP}-wke3DeFzv)z$-IP}_F>ncQQ| z`@TIEzwqpYYjWt*v6nPGMD(XyBm?%fipn-Y%Oq^x?zMn?Z8Ud~gl{S9vw!nhAEqE} zaKxqT#u=~=4>OMCf9Es=@YGEH?o>u(=-s5W5*y-O1H;>GDo(V22m^uQVpwsAly(kJ zKvrlaC7%B$D^QFvH}0mq8viPw4Hqn{Q%cC*HxD<^rJiISHo-FXQRZ(UVav{pyU=K$ zU)eMWToRH$cv{RZTuFj$rgw>#o#-aHQ$2KBzw6gOh9DI3>;md!t2ONy-hhFzd1902 zzyQ*+EX^;9RqDE>(;l` zKN>pqYbFO|`__WDy5;8DiUjqW2MU+=Rn?LU$7C++xG|A99Yg2=`xd5F1MJnjU3GF2 z^MzMKxhe`sOKrXxnUE9i!&i4zr?*ai}Rxuehm-r$3->$!|-72g^S zRSwsR9#ygKo3MrJ*RL0mw>vRM%BFa+ zPa~3ij{RDJL`Ns{eMX%_o(6FD8&bdMx4bGzKBk_8uz0k1`td~WV2w;Oe)1COF?W&T z3zbXVnf*DVe^H0-%Z?&?2101p-*7n7FHWJMHi6~PzcO17;LUx%5;%6WUo_ZSWlpR; z!xc3kG%}MvuD_Os2zx$+V~qXg-uWq0axzN;B6>gqkx3HoFHrWj$jS_xSb>QC><8wv z6KUFb1uLIB6l}a)_{8Ld8oGgrIpXszM>(b)b3{>NQANY|3{IOZouGp|j*bE zzf1!rNktA}{NO_^;Y*TyB7Qv7@Dn#aESYfIDQ}PRFFh}lhm``vl2*p2aYEcd8j_Yh z(s;VoC-@EDRP`Had!DU+pt;JCcsJsc!jMMZ=niA+GuvGln*3fkY6*4Hwm$|^ijn&G zI-mI*A67xQ?VuLW(2#5@pi9)w8x;GOJc>o#Yd(Bc$AFNe9Z2KBhuJ5+xR5Nk0oh@v zkJML&q{iPFJkg_= zBTvYK=f@kwy@LNX9o2JAB&Qfq$84I8p_GcURiJ(`T$@7>KwV8lw2P=R657Uno*1tmFU5(SqMacVg;C zesRnrM^x5AJ`%P3vq`FM}0Bng`^Zpin9X1l|QI74bkqjmU!{=r(F z`PHsNL~=#60pVjNxycj1nGqKD;QYJO?hJwBHwk&_m)AM5v6DIG9@jQ+0Z}y zV9oFiAv?B6rEqEKIU)U`%|6stP8T~*_G1Ul9UoRqNblKJ@`rE5|90hV5!CF}vOQz0 z1-qv@CI9zY*9uf+<&d8hk`FUV-s0iBq?mI+P~?f%hVo%WqvDFMZ@cg3;q<5<9cFiv zvNfc9;dsh$Qp2T%?r#}1A)t{eJ+h2xRwwY(H*Ea9Lqcwof4SJ>4>LZ{EaI`4=qc6s zz_xPVx2JWVMe$c1k@1MFB+m{1wvovi6NI7Y;Y_Bd$WcU*1M|OCUEjw(WQnZ=Lv+K<&?1^A%_(W^yW_OBh{?m;;_scH`D2Ar z`}y;t5bZfd(LLl4sc*ufT}b)7E;VzCesCa)c!qv04!!vhF(6Q^f|l~aCqW0&Hxic& z_kHW4o6lZ*#l+*qVXDAT(|(hhXJz1rT;ScuwBv%lQ2D$G#7~3qTO| zfwCCv^lOWg)~V_N+Be2rflW{AYd3r1wdXj=z_)QyhLWt*DfpdVlkxL(3ik>!){lAS zDz80B?~I}LxiiHOlwOUT>hCPLr*K32#<0j_6OuImtS4etE+KA_K5Ng|_3qjC=_Qk? z#A>fg3&LmmI}_rvL{oB}uXKIs9yfZx{lfs^^xZs!+R=t2?5T1PG1RQRNNKYnc#Ze@{Z_=CL`(e$4(^sDmP)@cOBcqjX0@|C;OGXiZ+H5( zDDH9a{#1|;IvJp&1lLjssj(`p!Fy?sz)WGk0z?nAdbU}lJMBhey3>I)v9_%Zep%GDd0Za9LG*S{IQ%{Dd>+wGjBCB>!g(#UXY~M(7%VBY5AwWm0&& z-UN}kCnv%F*M{fZnEmf{lig7KdJ!SrUEbG$@ysYGZ!m%xVa5BbeEOf55U38lJS_wXC@@iieb4ij3wAB5%*?S7i)>C@$K>6Uh1mOF+7kZ!<43IXVmm% z*a+u~j;b3GpLT`?iBDC|N=HMQczicV8;W<4%&qs0i)+P7b6gU;9)ud?ISZQhPy8`j z$R#&@{h|35YbXjqq*yzXd=>G3p~76V&9DH3y#R^7Uf!Q#OsAv%pmx1;QnE)?!YhR< z5t@AT`F$0!FEY(fj@V2u2ib;(&pNm{Lv}phk(wtzEN0P&SLm~E!`vx*xM^CntNu+k z+wSr)8*;y7loFehU$2STAa*9=_2bLkth2oJrdTih;{Ax2yG)VdpY@OaogCSzQJ;fd zhSV1|i*SG7!=9ZmkunztiH#)ie^C0;c@@F683?_mGMx)-p`9A9>^-43U;FI2fu7OC ziz`WzRR!dc(dPoy4Umzzj~{n05fh&^mZB0)s^*%y|6ayMY8BqdTL3HQpI7g8dVWw7 z`mH?YPp+g-_`e0v`I&~IMt=}Nt`naSu|wD6Kg7JO`=yweO42(}B?F2SpNt*m=eZpa zU3%1Wjjf5rH!i>+`KimS3s)=xGX-7V(&zo#Gi#}k5zn_;Ek|bE1-fSKSdz7Prk-+@ zaTU5sw|sz5HtKR}c(Mf{-qu`bdczl{V=+DKqjTaBAV;sHaA(jxQmQTSd`3sv`PAkeL#7zK%md|wml+;nh9FfnVO1|1B zP8lF0AoEB1`Q+HI z_B1~S0`8i})Xv+nONK6in?GcT$ljLA>V3jVl_{5~&!J=yIdIiI32|a{n6&VBW%Yn< zAuU~MJCWKHBC7Ia+>Gy0CvX5a`@Z=7JyletpOZhR@(*Lj=4DiWx<>e0wqrdeXe74w zlH4>e^H}P}Ch)L~rp3d5!{yX&pmAi*VN7~EOL#$w4Hf6zk1h;toRb~@QzG2CYidug z7imQ;`?{6SZL%4>V4|?J$|u<8r26LqSe>xTh3k8?szgT!^$2-Ga% zuu`G3=AT3-adgz}zLY~q5A`l_-}C2BUwTt*7mYF+0HG-g>z5I>K5uW1Ja=*IwF~)x#;WH5CK~;KTWYPnm}+ z9-mu*?y34lu;v}yLwL31uAe*hYjb&bQK_J6J4^1W`r|FO0uM*$?FKF8rIN^1U22p~ z%;D=*QBNIvPJx`=sOs=jg%bsw6D6lYHpBGDA< znw9%kI7PBWsVW=HUJe~4XVif??zwu_0Ab(;*|s)2O5K_Vl*0JC@#Rg_#Ls!V zjT}#P)Wp;(4D}HzTN=+}f*u*;#X< zY-m}-e2!B!8mKLNQDZ8*4*5o(q@o-%J=$%2V$yvS)}@aBYn6tkW>7Hj>s3e5apgZ8 zh;5cD(mIV~f$Xxrkc?q_#&4Rke)Fse%NrkNi`;)h2zxp+b>`mS{SWt)V|Z^5l0IA% zzIIX&GP7s+bkF1u-Y*)+sos~~x_RUDm?xT>rZN77Z>k+1R!z7aiLKvNwpvrtK}-PR z&(0jwaJbPR>e7j^RdMH$MaXK)YQxxj(=q^M;W;@)gMY)9IK>k|7t-X>bloz#pvv(& z6sBDjTrG?u$#R*`6e3*S(PjO|we?Oz(%f5R#VyXwX+&UE^FmVV_0H=_1}Ovu1&dyp zJkD^YeO>y!tGSezk9}Y+dqCE)xYyaV{;QSe>>+T_PRjK5rF&U+`jHMWm zhDy_R@L|r1am8dfOC9(2*3PN*f3D8NyDXow=o>rw-iYC83#xdTzaCZ8T$HR?O*=GX0=)%aiDh)=oOUh+KNWQGT)_&?lkLBUsrb#&`4>jM2+%CQ|Q zYi`S9stW!Ac+OwcNnxx&#^XyDM$EQ!gEWG2$z}uBtCBO9&YdvXgYoLl2;1iY+p5(_ zxJTh8I|)`I?Pl`J<_mr7ns5r$z!~LvzJ&Thj8UIx zOvJ{I5LJ~5<$b$$7g0${+j;XcTci6&JQ`6tE#&P!dO+D{mM{+Qm_LZcuPEd2tVPNN zw3O?E%5<%GP*3DKXRJq&I+lPgN$G-2Pq?lwE*YXbehL1kfX%zPq#jUVV7iT@phaT+ z@qyJp)~QOq9xMsAwDP9Nns|Bn*DC-}GIj)s?d?#^HPn;a*>geYySEOTonVa7_IK$X zG@xn^Y_{EqJCwC_ehV4*aiX~+a`q^IagrxSgu`7KKiYm*K3DNQ&N5^gpIEYBP0+sM zYhIJx`Z^OVh~({Ix&c=o0a&9jYJJf+W5|nA}ktdXFW_+VOipP@SNc1qsuTbJ3 zet*2O*z1(c;p@(**SqoV0_E)=0)E9k>G_~8so7?_v`1(z#zU#|Qo+Rn&3>v zEGpD`7X}5Ap)OEAFtKGs(*Oca15}&`%h8^Se!Wr#l8^1gojn({XHQH3w#rrPT4B@UO6>0ULm}bma!77>4R@y0kA9%50HW#nkh|~2wdpvs}ezZlx2W6cO9a7 z$8R=;KpqvV0yBBAbUmS|-qmrfM2*&4_^_;yTY1T`KhnmcF8;Np?V_oRY2=w7WzZ?7 zYk&XgV*ED|FNc`O8#H3;7-#D7Rp|$*Rn-ONWrMWb8?POKM*NW37;^OPCa z4PX#esGS7P4DEH7HWPL7ztsc`ST-1d_)uZNll?`CNlKoU*dmZ)A1r>rNYW?OjskMkq@%b4D-)dqN&RD;E=hWsyCU0VXa|@IWs5-%HN>E-Z_cOnc0N7Q z-WH%7{4Yz<_XeZrRz<|#Zw$JD0kyrU)!FR}Pm~bj*jtT&05eO5I4E^@sXZPE@i}#F zz?_%|Lc$-k-CFIwoqm+;{4-UB1fqHB)1Mo0FX!V7z78*~MlnL;uH?8~zl$*&PPyql4<>EeM-s9P9P~Y zX=IpgzJTg|MxQlC4~u?_uU0M7z$3fV43J6l1pV@p`L%geXn8Av?zzF?2evr9TCa*i zNPYK7%KouIfJ!9KSy8+niIa58lP34` zIq`XpHLPkH^wDQQQzlMzbTXGBtXaTe`hCU11aztJ{!nl{P{lkfymY!vuH17T>EvI8C?ot1avb`gd zEVR~4-YzRj(6^GM&w8PU5#QuEDK6IQHO-lS3)Z!$kpM&9P0_$vYMu7|W()p3|zZH?mogjbxd5nf>=mbNN2ra3m5lmCh7% zj`}zS@4dS^M-x9`8@iC{0sm07|p_ia7-gF1XObt_Ac4*i9 zcYJADBecH4w^mM32aKYcg@X_-{R;6dKg3?zUtvuArp@e-x zoGtAFHK1n<^W>h~pL^fhf1-M^q-g>FWOn%ibMwp!^mVU4k^$?hv2 z@=b5A!RcR%ZAZQES(6%PE4M9d%vDuVSNByll7AWLB$BYMs5-vfevGX}H%vf$=0ova zo5(wTJVPD+_?2?$zz{y~xiPhqT$)?|W8B^&BE8B8k^L zxiryn5dd2I$3vdmsHG8L5F)xxt_)ge=5SU5+z|(c?lXPxoB4yMXj_z9MmZVD4~{we zZdlYhauFH`5uXQg(2J;~$qC}67Q0DV0&6tm)ru^%HQ;l=VtbSq9o0{FI7GFN zC&zaZEz%9RgnoHxFgDIH>Mb2cx<0^$|5v^KPa7ST8LH~&kPWX{K<)Z`x?ulCUC6y+ zZzy@wsVVSFeee240DTsLZs?>NvDuEcaCu+iETVbKnH2e-X9oL{_Ug@y-x@Rc^LvfHR7$=z+?B2hl=f zhUsDZM;=GqB0M&ko0fU_AO>*00I@IU652oD=8xx0m{s|8HE8e5=H2i7L}aOupFiH< zIls(IGQy!|x$8^27(VPdX#Z>vHU@M3<&|Kj%_M5|$FS~RTzqrOqz4op2;H0F%)i>T zWB=&GCb2P14%L;6v1;ZUzb?+Gru;r%ERZ`W!E&FFu-*$X(GVb^6Zb>c=nqOb3@3`Wv6*8<=d2j~Gojj?KXDBaKt$haVAMXxH{ za5abbm%8g?CQ@_0@Mh?0H%JLz0j%5vi^)ipUj17{wl~}nk3nwMA>{A-`u&yIg2e(n zq3oRQnW!@0=agtG;QaYEdP9%1^g7l&mSh&1o^U|rueJc4_;>u!jdgesD4aMMPawJ@ zlhnrly7B`EoMT;PK8Wn8gIaaMZ)e_Z2_dx6bU+Ubiem^SG`@p5W52Nk{HScVV z#gR_K7RnZvts}w~P)=*l$oi4AQ@l{_U=-~Wa3;g;u)}U%iHpm~0$6T0n1W)#3%L{W zzUF_wnQiY4B7q4XNi#*U3uE6##njbkykjMz7roI+WNSAr7sQ2RQ|3XA(snikK*qKm@#9 z?LWsJxad$eNP(}r(a=}J&+8j6xkOI?#j{!jzfZG`-dZHp}3{`bM#Sud#BiASAe^NN zBjJZbuf8EzW{8DiAbr2bgyv>~#hgfExcDfT85-(+FKFqLwVL14&@~|Z7VzGj^9itY z!1oY^zWyUmnKybw73+g)RS_B*yM!eCifG{j)TPKXpyh>bHthB?XL^wdXi1inXP9jS zpOjTW;-)MycjpBVUojw7{ol#W|1*Wo4_5O5U#<9D+iuI(Z^WTYAk|`hu-{p&o3GDh zQq*O%1M%65hp4NvL|>IQ0uQcW?6tKqeSPCkpxRPJC1=hyPkQt$+l#0!{leq!YLsd# zWbAG+!7(m2XFi}&IijLg`Qa@qSO2(|gm!(Xht??19DDJ%Dn)q`@9= z*8*CZAC+UnaZ5V5YA>L>itf#$V8vh=FPdDz2Kw#|HMX}1gVHXWU#&WTvbbCd@iu)1 z)4aRthhBw$irNZ!0FzMpw5j=D&px&|nV?t#|#N&f!_6i{Ldw}8D3w#xqej`aC?HVYv94Y++kyDUfF+-0m-XcvhamPq(pO)K181dM=`G* zP|rG`V#8kxLsH3LhivX9u6|qnL0NNDjELLwO5WE5=YUK7+5RUD0IlLhgSRf>6_uN; zKF?jzWx2M`B=z_LQluYK;p#%sNM{TWEoxTEOnsp~gqIy?H7x4ai$;6uur2CVW9-l$ zdrgHws3Qa|?kKqtM?;?Wrr;v&{2*(86EE(gpye{2FkC4~&1dbLn z5aZFZm;I8R1zy*o3M>2HWEZ3F0%D$%ibAL5R)_KNNuj6k)5hKlv7KLERV^;J!@G^W z%i5x5qpnB*_GIg3SpLz17aqWs9Gi(i@|t|PP)Uv%FDM4xgPRy8X21%5 z7a)2=Z1LfGa6X$_8z6}!P7=wrag+)^XMUKiG##STnXR=_4_6I~WtrLr?5mMB_!ZrS z>X3WClp+lKdEFcseKd_O-JiM{-dEfp(b|~W`ooQ4a=C*a|40p*267ja!=GJj z;^;=4>s=22w!YE2(6mQk>6ZQi`^WsY>(k?+koDpYYm?vZ+o$%E(@i$DNK7`vZ> zhUFW1^SYg2RIF*YHwk^8`zNN=ir_UiwnF(SoW_F5WWs9HR{{L*7BE}=G1ddDeKmkH>WOz&2bZ`m*!u8fk$=`oA z$-AM?mLC7J?`c~U4{fulUKUIY5N-c5kxn<^@=32hsS{uP3m3f6Tit<4rtV&^1zT6s z%fX%iM7P6CPT5&al)~q1ve{ADIYTc=`TPFfY>4(0pB-Ww1ktZq;7oA7j8~t6DHw6) zlvCmplhDG)qU^Er-??>SEhT^CAczslpu!s5Wqb=*06T$7==*#@X|^Bn1;*ROn(+?* zdz+&f3d2&<+qE8_Sz8j0W=PMz{rQKmHoijRCjQzq`>nbD_f5U)Il2&&^&ZS@MK{Nwj0pWE0hpx3Se<|&xfwR4u5kvGHPrL=1YTtS7;3-%Y zUyyu|`P81q*Foh#8SB6@q?SNVLybbDdFbqQ!Phn9k@4{DJf##Rd;f?x@98xlfVC(6 zhPy9y9ljc9)Aa?pyJok!%ly3gu1)Cbg#)WKpYSWYgI*9&3_R^$Vw+Vf6(P8j zw(>uuRIzjR7#hQ*hv`zU*mOL6fCxkGKwILI7CkKmi!6@?*%@vKuhKwRjSfN#8~@^U235nvRr!W8_!{l!yC zOqRUxUY)bM(3n>G3Yr?zg#PL&2@b`1C_L`;bVjS}O_0H(qsYNuQO^80H7>25qe`%B=ZTG{p0okr+o|Z zp0x&0W7A;k&VOlc`S=W4c%C{PET!|o*m?yG-Si{oG+K*t6{JVW-DJ%ZXnjlter-}W ze#j|R4Z6kJ?kq2N(RkBp{NW-AAJ*k`I=H4)67=aJxpoVrY^vCNgvVcy!by@Jr0w1( z?~9^EX`kx1h0r#6APF}!&4#%{JG|Fm^lSJ)lgmEG0x-F8>b^XnvZW1fImWUJON9|T zpfGx0BS~^z!5b?2-TO=oqm>kRN~=%}`T*y^P*Ao$HG+Ok7~MemhC9dC_UBPfXX$7YqkIb1->_v9!QohdzN+*ZQULQW990F#~9FBpGbA4|MbCO)12({faw} zOY8cEK6%iF)l4dycW2y9#rQK32m+$vrCI950M z=eZu59doQWn&~xMx*g~BuvDx`fcRBe=qc@YjuaN@q&IEGy2_|tyV5ol(Zu4##jr(U z>;ww5eD%?b%}*7L)aH4OQyL%dD6~jYd-h{n|5%*+dHPB9ZzIHlc@W{XiAFANQDK+$9!(-qc>tSfB)qDJ0!wel|})#JUu`~aKdK8G$@yl3VZ*AGk+g2 zUN3M))5$T@PnDW@>0j?0NigcAhXLc`rer3ld2;LTQPv>_M6j=BOXpG=J_VLe8qRwkxXv;It&0+#UQJ1O{lkq-6fCg-;$8cGj)6F9;&wRY|OGn8V) zolC0D=9DjI={!`7Uu?2oLZgFjQoe4=-rrU47%V-r4qzqg=neLXHuc&LllHZ~Cvg(t z!qD1-aKk{ceQ@$%VCO8TC>xH$<=Z#&#kmeXqh<6JV?hmYZq1_;(*enH9R!?_p-W<0 zY0ysJhQ;^=$npjrW`)pvW-J3>Ly-gU330^H$^C7qoEBKV$#F^gTZ5j+Ym+?2l^jE7 z=sV(GfJb&cxZ&}LK5P2dZ2Yn!gW)iFupRbh{y_aZ`gLOy%>C{LXpMN-;H$6;B)WQZ zbg=8ljfI`ZH&=!PIH$#s)Vq|)_x;LBGJ>;nU_d$eC~yoz)*#V5Ga*4(WkW_x92#P^ z2v&uPIW!7%{ww#7h6BDZj1&t{%Iaf`BFiq0T%1@fAK`TtajFn4XtXU1;@wuF5Xd}N zOm>I&W8RCPvX?e5Z5uh?qr?#R05b9qU)H&Q=%D|s3{gdZC|zIkYoSxX{}R5G$c~yQ z`_Vx2eW_bza(|1*Xz#~2;hRO|2=;6KxvW+R10R?UPVu4Rj#DJurB3F~^pb{-_iXB1 z0BMKs+V8^BTek5p^8Venc*jz*cNm&$DGa&qdQgw51rUK1byQxY(C7uoLy!97AB6nB zmFZxr3IMw;HYzi$;`g=FtWnZG=v~l`u|)edZn2gIU-_q`0K^uXBUc6M2?y~CWxEOx4}6y*mqJn^?cOaP%aw8=c;uIhmL9fXehhafjpKH(IH!GgT*9L~#XHPo#h zJvlz8P}OpU*r4POh(1cB{{1nG^B=%909mfof8#v;W>nDnZTUg*PS8g;o8g?lJ+9ziy>%LUp~F4)Y7q^UEcTVktg9!SCZE^@|6nnA)yrcC|kS2XD^ zNpQzfZd%}KCj?zQ?~wi2oczH8o|qQ43g!_#Mw|Oa9w!-T*_E=_kK1| z)Rt7yvweMSDK3kgA~P*e;RIRAj?#Nz_%QyUdO|8sCI5Ft^_`Tk+mue?ChMFQivvMH2(gSIlgA~f`pu0;>-@S^O#;>RZojYHir+1O@ z8D#gM<{L#baQ3pGg~OXsrW#XHU(Bj5l4?|hC}*U$EaabUwzEbtH8jEkvhGDcOL#f1 zqT5rF<;PUf8~wlk7XW58;T!~dKqKN*=7iQN3Aq)S)rpj+#6@C6)1uywDE!JpfUKM} zzIR3+Xb==m&W)Wu6IjlQB{ugffj7?R-jD&##sqsjvw!1hJ>QxjdJ|B1dyGkVC4J~` z*ikbN=MZ7YGWcn4J}p%1*43|h_%JHiW{1T_gnLG9Xgg@9jlO zf^Shqb&C?)$^=7tOj>ed@iFk9HH3FKi00i1agLus^=mqkC(h@Zcih1asEDA*g=}l; z&Cl3Z?mT#nGUo9hE7V5U?OU%>r`nV^#j7|@n9j~GyHZNDAB*KM6@AbC^*QP`uw>!< z0?N(oOEX)NKsRLRA)L=$$$ujmZ;ic4J4vc&Z2TgMZM~})^#FlF;%}rn-%0~F2CseG znJM=3$I9B5f7S*mB_E?cyH@(K%YOhs7G29Gr0%vyZt;d6VfX{)(ndh2GPd``5SEce zaon=tdVC}G%w9xu0RF~YGs;ap>E+a6^wWlzxYFLK3B$GJxvDIRbVVhLM&IGtdXG6S zRVi2iH7ZS60k&3G%?mwjrp=HIV~X~aHi~VaH2A9rdVoj_a|9IsQGX}x2AyE818&fR z=$F-+L4l=rhHzj`L*TLEluJFwZrLzC4n6=4z8I0x#)73P74qNRP?j`j0u}UwaI#aWC}pBxL!E zmUsWbbQ%9wT4K@mX#7ifY3~OZAv1L|x}e%(A{Yla<$eQ4l{@(~T}}5Y%G|0xwDZi- z?BbE3CByMMe#H$~*IRv6pNBo@*J=UN0q{Zi*B%H#RXOBB0M4$F^n`*Gn$B|xj$5*p{LMzJ%@lYleu9wwp9?w-qq8S+)#zJUem%YR2IQ^!|$P zI|w*qX55M0T@ zw{@1Yi7qf8c|5e|XE4S-2@1e@0dYx|Tk?z1e}81EIETVI@wN^zxDS&U18a9jY+nkx}Mirsi zJbUSBMw&L4cKRPs=J8Q#Udl~3GkbfIw{^U?#?UHA$Qy|MN`2$^4yPsH-jmzTM41Fe zieTc^qoz5J2tYe(xlY1l+3BZ1e1d<|nymBTQ;=Y88inM4~NHc2NlBj9OZN&00`@k&d1S=vm5Bx+MVFXQ3(n z4t3F1sQ>{v)%c(-7>v;Vw?Q$m^c51@8cH##SJ`}m%CdYHcc*0Fct)VBrTBQ6|YV0Ai4zDBDXp3tEF>$pV} z(PR7INTtddW2ylzU~VDr#bWo#WVpH~8)OM6*~AW{7rJQ<{Y`1ApxH%PqTooLFHJGw zLW9{=w1O7D4nJ8hS9+UY{pTr=5{RGhdZOc|AXJxTYnyhoJp^x0`;{2<^V+cGwWKbck}F~#P*l!VhtQfKk(_P=v%(D9{T>fV?BjezZ9)j= z*Bi>ys>nnnj40(5W-ptu$8+X?KCArc^6M*R#;JYUegLCUsQ+SPe{I3wLV3yX_{MsI z)?w0PDH)c-0{j`tEAkB@XS{^W4)g!De|t@U|8u;IjQ`nss`)9)>F0sa#~d;Z>uwo6 z__y0_G|%`O=#vxm%;Bl0tD*Vps0XO7O+qfAq01<|oW~f#+wGGnVtbIoQD%}gB@SHJ ziQ-5HE2^49QW9<)3uEpG}fm z=B)#w#a=jOXu|8GsHV?*tde7ZA#^gi#-K0c*8k)-|zQolD64MD7$cxX+8WVo0#hat4cWQVga|+08~zujliNxTghyEkttNOlk;0G z|Cbu(r%&eT?Ml`P<6?rHVp|ZU5FJdw@blq=pFa({ysQ7Y!+YIM-bURstpg zlS;L8NppA|84{Phq09Kt*W{dC5``!=QD*4}i~$*onKNOp38R0+zg%*w34l+`*)qHP z(ZN}mN5Ana`gAdHy})09*7?|;=+tU%9$T&lE=`R$Aq-`oyqX zP7M}#p!q@*lyD@1rFn$Xo8IjJ3>%j~b5W;vFge%HrOr?6SZ1GYtmnN9wrp zpuc}wFpmj-)8e1fNtz^uz9Iwv&GRT9n8>KBpWf1T%TE-$gCD0>rM{Iy!=a~~Pkqq? z;R#Crkpi&Y!1D9i?7$m31Wqt9Ft~qnQT*a)U<_fN`j{K@4zh8)81Agp@Yh*~EDC&D zU%C5j*e9pyekTwOv*F~}_18r^Rn_{%$1k(r8CUW))l74^kbmgf@%=0|c`p^Jv~SUg z9Ro^76kXf{ftcrb3JN{hwixeH$2NfJLJ6e~mt@gx(beELx!Z1AxSigAHt+?19c=Jd z#~-^qs3lQokarN*cTck^H8GyfWV7-@ zU`h$4{l$rxTFB+2A4)uRk2kj0T+C1|Z_y(x2)PfWiB#4PPi*(wxSjbZU*WWOKgs9W zLMZ=+EF-+dQFA)w8VwdAy=cT#9r{i(C#Hn43v;5jA;t%94KVn5y*feraqz@L+K0}C zsxP9D;UmD$irPZ>pQxy~6xRd;`dDt8_41;Kn5)bfcegRK%+M#N2jS7aR}&g-(yNY4 z%&`pu$<89L=8=zn{~{^Av@7p&JAEJ8(4L91QjU?DUkhv#uAl_niV%?ik+iQ66P;?~ z9zLvp&JVSvSImF;zd(n7GL&#vQ|nPt(!J*x=s;@zB6I1#z6gAlyf{qjm1k0Vr|-G8 z?UW}ZPuwVQy1cviiqB!j0xAWt3g41xcc$H>wH-pb3@kGNTwsS~u+8AS`Z{#BFX&W- z)~1gals--xcQ*bpJ0&^eAt39w-0-S<%8mZxRO7ZE43(J9TSjdAy~c+>_U7jUg9?%y zw&`LQY!nDxH*Lm9UUNirS`VN6S=lP{Vo|XIlyjNlq;E@_U6Kk7 zYlOV2dH+cxVbF zAXIXes>Y{99uB^iStab>h)H(4t4%J`3Ylq_h&=CTp>$5!WNR!EXgPl1tE7uXFMo?Q zCNUUqmc!fmP1q-#@4kVJDt;(u_5L^}c?Jag-=Kar?ZW-%t`0*#cVQ=1tmLPhA4A+D zqpZ;Ac3(H=H9_+;9C_J7V`aQaOuC;0%a_UJ>oeZ!wYry13XFHo@r~AA8Q{XgZbKna z#O?6``-RGe!;5j4Ea@=(inVN9&y7&g1s75;?9G~tS4xxani)HLi*iUe;$EX^5bm`kGHZVH=!_ zQB3}ZU%=oOxE$}Q23cBrMnyx{va4Q$V|+~%u8}YOqV|J@TGF3za0ngrnn;DFU2Dk$ zc;g-PfRn$PF1}wnZ}{gJtu#YZB<+ zI%raP5+VJH>OnxWTNYz^cj@=Fw$Q2Rai-p!*>o5*^)|G&y8ZqmrK0A;&Y37dV?mamQ7MLZd zW|4JWcT3N!FHg`QnaxdKn-6aJPHNZ#93Q)*=rtr&W}0Rjn~p8b)!4Y*b`ex8{NJu$ z94bQsx6-7PwPW{!$ltuAz!2-qPUJ7(z4kxV^Q|=b9vlQSNAdb|9EGNcdB{%IN^*oQ zQpNAhuSb?!ToZoV;c0=YyNUVI|FdcSe~@fDN@55P?6W(XQ!!YZyT6CNby(CkD8|`g zBLcVW(ZN_uma?TwT%g3K?RxUczcKV0DEJo%Kzh^qVHtsnaLPxt4zOiABE&!U3GF6* zZbk>&v6qROaLD4@GDUH>z)tsdE6h0Sl&=jyR=)BmTg`pj*GF9kvii)^meCD=zUPCI z1V`bS1Unj?uUMbBP3L>q0a-~6);-{}iy@*7&IK&mj<`x?}#G~Jg*(3WxE?3i_tXc5;S@M;*(CibO?PNS@(=P&4lB|R{#Ne%GoiOS0tHD81kY? zZ(X_xO`S)%;I0$Z5RoGWWLxt)PbUfD-ryMES&cinvm$f< zirQRGo2}e%L;D zka?<&OE%+nLN}>?{*Pg@2DvFTA&2<Xz$Y6*S=neK&0@J4)3e}js7fj0TTSa0rkkT11Cum9`P#I|8?b4G{DxUz#(fXQ#_SOcR+f-=>c-t&RT3bZQG#|WhY z+1DT+33sfD2d$hSV7t@tTzs{Q+xgVtk?Zpt(aHVGj1aj8&4*=a98f_HIhG!rz5rTQ zRDTp6{**6Cn|uUwB980tvT{DF*>H>&@A2Dcz7K3ZoG5OX=tQ>&CFw=|-zMB<=UmZ6 zH-*4QfUD_)EvhFUqFA1LaeYDjrk=Mm1Y)@bt!M(;U@AJ_!@nMo z57nr)bK&hOjm|gPpEccKzq@RV4!#WHfdz^hZrS-&XQk`pBiW9(eddqhB|cCMaWCc9 zect@;%sY7_7w}&+SMOQu!^)%t;X(iqD9+a}6`HcnMuJAWy7%*rEqS>o5gLYmZ^zxh z2Og@Z1K042SM_>Zw84Au+91>{W~#J?8BI2X3t@sFYe=;dM+M+%tJbK*~8)I zKx^YO3-vi=P-2iD-v|@aZU26Jk$m!-^?l!MD)**>5c}+?>k4Tl2 zD}3}pgp!lV1>>PoQWzYaRE$;wj11NX##-}%M!))%cF#?C35UDs}C zgSmQVpZf#Ppt|JHuRHiPXw<@S&@jH*`fsmP!AqKM;eLXt8XLlP`Q1X~^(V&M?%o}9 zn)b^n_(<3X#63yw)%V(?`$WRSK#fcDO->2ZB2(MtPisWj**9?uDH((REO+QKotexxgWwqt>f(JCln?ur!#%2d5{KZD>3 zI44~1qgi`onSy)Bigy7S#UBL*flg(K_elH+-Ga~_#+o`|NX%w^;S?$xGHYOwbHU|M zNV#XcQ<@e#V|)Eb*Tt8Ul9UJNwRjLbwLI7$ln!}VfkLUwiJ>iu@W+qluT#1WXmx6~ z`-b(`;TPa@Q%?)Em)`t1uN1u)Ejaj230(PW2n3{ESU;3t#J6gQO-A4Arv3Qkr+ZD^ z|7a5R+z{-3d*`Er%<;KU+Llp92UfguPJ$`r==}?lk#ghoSA#ZwUp`QWmAQ~dGRN8K zTim${_2;HXW^E-=+`5ECtU;0)O7W&$iKkb{>je2+gQkjG5$%;n%kWX4iF_e*s@J$` zPVkJIWI#G74zEYRyk1{+Bd>nW2v19wC=6A21A&2isU$3hupcnl`5^C6HDu(wUDC1S$!!&Cu z^}xC1q76^Yw1YtAj&cXTNq@E5%~A~lAInj!8ZqKRpyxq6t$V!nc5=6tA^z_y+QYOkLB%j-mOOx?A2ZWO^B2WvO>en=Df}oGtX3@fHQ1YI8Mf5aX<(`Q zzU1Ja_6Fw)!bl4jC1{gdInb7~cfaQkOiG2JO%YnU|NaI)V2;lJx%}1OyT%;oWZaQ( zF76jRIIGN}EK{td;<$f0=~hE*)mb;}ran`Qq=r=wOYnEHf@(bOJG`(Y57I@iPr(79oQ^Xu0GgnyVn@l$kwA_8%7n+2%wpY zIW?t}MKVM+?9TQXZEDdWOnx_)tB}9J%?5T|ZsUbHoO;gG=x}zg5B4wjN9rN`*m|ki zg%e}JC;d5#9p_=_U?AJ+pE&eh+Dim=4ss3}G;?F=w)@oSV^#J(+>i}ha+)m`dj?R7W8vRKCE9l^54KWH%jOxw}!)Q&u1B>*%u**JXOe{4@p&%o>9o{GJ+ws zd0RW4^buwIBS^3BRn03LPsiLXrWDQPIs{}na7yi&(PP*AtXzaNQZT7(O1HKo{|KH* z0d=Wvh%w8tljPOTx%z6Y=;9dgayLP~hu#)`Ie`7|#n4~O-HyNW2&~S=u~_p zxXy`_1T^72iG)!DFxfuwOi4`3!GL)B+LQmSZIEa){9=gzlx%`e=4 zU_ckd{kQZgx77Wf4q31^r0F?o>xf2Q!*C5;2s(~!%DJL6ALnzP`jjF}TD1>C3J?A` zKzP^_6(MX=URUV?Gy3NrsA%ZW0vr$Al<@!<>vyVdY*Pz4(il5XE{NiWuk$VzD5ScL z{s;d;7#erw?uUIOkLjNguS*lDNF?ljF|d66KZN5b&a*xssfU}QgARU(NtMTI!5MGk z^M`OHBl!}(Xm-blPM7o*fgUn_^~MsGZ$}ATf6A2F1zMm<;GXW-A`L+2t@t@4hYK|( zUwW+U#W&IkXJhXGa^hW3>o4XXgj|-$3ChU+MeDE;4XR*E3xq7nGnDoVXrV;anieCj zjVG9_iyQF=zNzOx0ec8XGR5tKM2u?+emyC&d{i1miJ?#__M2p_76VuRhxLluabMH_%Q~?A2wDVeZEfkw-Tw!EQMh+N0flW0hk`LpWgQ8H)MXF&A6bZ1TpE zn5ngRZ#M5%2FOt(fltg_0MdeB_)yZY_2i^a*KzY8yLJ58oJL|iKf^sL<8_PfU@>;* z+sF@e^()MyQ^n)Qbe++`0u?F@uiCOK);;;+F75~s6+k`$sP;@?nD(AA-q_b*+y9*? zj6IC6f)_Jv@{*h8x>+gNopYfFpw)%cD*j?qkE2BER`318Phjo>2tVsBnG(-73N0TN ze3JYfR2TSxwbq8BUVPnPtWh3bM;x*Zcn^T-ImXp8=F@D0=Ol`z3opnm{i=eqc}{v*Pk` zsg^`}-PPKZs=;#9*g!YClQvnQAy(^R`d*$l0>(pU$`AbYE07enp9?AzgLF zE#kC<9&gTfeVd^Np8>vYx&Lx5d$6h%K&(M^{;GrG?y3(pz)2et;t`4fN==r5u12H=ZBgg1;9-%NW-0^w8`U@5yBKYKiT-wj%QlB@!d ze`f_Ct{Llpli|k}yQtFZkZs~n>%m73zFrU$PCrMf@4A0#LfUX{0BE=i z)2)EDe(64FcBmkbj^} z@gKo;LY&kI4ou$Z@(HDzfJ>Hl_n}MTZPKbLFwgjbiAW#hoddne&HJa^i;wT@nnUe? z7>0FFY(v89USztpsY_=>HVKJPn-W)K;luZ52eSGjyU0kT3G0B(7^!DssW(h-e;&{lz(S(Q+|uvh7t8TJKVtGO|IjOIOaGa2$T0_ z#XGDiyy)Npu=xkDxfzJkX;lHrm2E8a3-keWa4dlP-^IU#8UZ$rFV}^s+^;`JNT$tE zchKqJ7bylRXfz@+NlUFU29pKbNe>kJr}VM)0f(!!mk~XYgxR(>{N+1Ps}JcOJ-iuJS+0v36|iM1OOkzTssPxsfTfPBx@X+DW}nb4i(`H^Y1#rf^)HCh z3jo?t4$3MX#ac??&p9kdIdE(J5IxI$fzK1&g=Dt=0M*p>Q=bDJ%nNA`)O-{*1 zV1{d4{k`+qyQBr-&TZk}Gz3Dq7s5zqqc6E18#6OM^{}deDae$%@%K68&ZEYHcARKZ?`8KN6B;JO7fY%FwgvYeP?7bq&~3nX`?Hji2X}?q?4nu$*qrh@nf< zf4dn+zq?y5u(Z9&hrETA?46zBh6NnWQ)#HT8Ws<{xx%~$-ZTI~1$Ai1ocV?KSzoZ( zmRAK{t%W|WikvNVE6@#(GI~!w{NSt}oG}{KeLLRGxa(|~Nda;WSwE1gW4Qx4XTwY)gX32WoY zQ^jr&XZLa&XNtQtzA>wDV79uERwZ0a=y{d*0#x8Xq-ZS`9D_$6FHwYis+h>1y$WGJ zqEPaX&M=}LqxE{y`C7n#?s(-jjwHEG4%LNRQ2YOwz4BQS>{1DKc|GwWp$68-s(LU? zM7}NoAPi2h?P<7x^H`x=cm~-AGX`%RF>x$IHcG`p)lOuZY>6A8zCcU{hVf= zg+21o>`|uPzmE3qG=uc3kOQA3Qz|%kENAp!mdfHu|5J;iNKqzO#({zDBBJxm%*hYP z3Z2SKjlNu{b6WmdHoh^mFoNcf^rXc^k8O$s`LsNYhzdc z>p_Sb7{h|3!@+o%>49EQF}OnciPjm?x8;ZhchhG@v-!ZrI26+j4X9(|@>|#QKj9^} zzp6Q%a0)!9J}0ACcfpqe@a0W;F13FeU;<1}lakAFd@a~l%OuLW0#;l@x$Mz-=5_xr z3A*o#tosRu6*z+bj251B3u}!H!cE}TH{`jrkHcUdsr`=dV=; zgJQfxotu{~D6wU+q95=Go|-_><99x?9hc*k0~n5vaBTiBBr#X}Ygnl!_n zF@V1L_>!wo`T*hHvL(RA4PTz>hEeJ%RZ)y<4Hh#c17lN0U>1a&BZnH8QN)qFbRsSk z)k@i^C+p=D^wC0`X0&592rD*wn{EYd4CL3`p=G0gCLF-)$kF=U{L@>~!vr#%yt)+{ z-4vy+;ac9k5(x&uG;xL;4S6weyW)BiZ~a#T_Kp1R07~M0MH`NZ?dU;vb$Lf<_CA