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 ef3c72c4e..1365ec1de 100644
--- a/scripts/Dockerfile
+++ b/scripts/Dockerfile
@@ -60,4 +60,4 @@ EXPOSE 1831
ENTRYPOINT [ "./entrypoint.sh" ]
-# CMD ["--help"]
\ No newline at end of file
+# CMD ["--help"]
diff --git a/scripts/instructions.json b/scripts/instructions.json
index 1aa963c34..ca49b1a93 100644
--- a/scripts/instructions.json
+++ b/scripts/instructions.json
@@ -89,8 +89,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",