diff --git a/manager/manager/launcher/launcher_robot_display_view.py b/manager/manager/launcher/launcher_robot_display_view.py index bb72abf..d164e10 100644 --- a/manager/manager/launcher/launcher_robot_display_view.py +++ b/manager/manager/launcher/launcher_robot_display_view.py @@ -4,16 +4,16 @@ import time import os import stat - +from typing import List, Any class LauncherRobotDisplayView(ILauncher): display: str - internal_port: str - external_port: str + internal_port: int + external_port: int height: int width: int - running = False - threads = [] + running: bool = False + threads: List[Any] = [] def run(self, callback): DRI_PATH = os.path.join("/dev/dri", os.environ.get("DRI_NAME", "card0")) diff --git a/manager/manager/launcher/launcher_visualization.py b/manager/manager/launcher/launcher_visualization.py index 9f9b38c..901eb47 100644 --- a/manager/manager/launcher/launcher_visualization.py +++ b/manager/manager/launcher/launcher_visualization.py @@ -1,6 +1,6 @@ from src.manager.libs.process_utils import get_class, class_from_module -from typing import Optional -from pydantic import BaseModel +from typing import Optional, ClassVar +from pydantic import BaseModel, ConfigDict from src.manager.libs.process_utils import get_class, class_from_module, get_ros_version @@ -57,7 +57,7 @@ "type": "module", "width": 1024, "height": 768, - "module": "gazebo_view", + "module": "robot_display_view", "display": ":2", "external_port": 6080, "internal_port": 5900, @@ -82,25 +82,18 @@ ], "physic_rae": [ { + "type": "module", "module": "console", "display": ":1", "external_port": 1108, "internal_port": 5901, - }, - { - "type": "module", - "width": 1024, - "height": 768, - "module": "robot_display_view", - "display": ":2", - "external_port": 2303, - "internal_port": 5902, - }, + } ], } class LauncherVisualization(BaseModel): + running: ClassVar[bool] module: str = ".".join(__name__.split(".")[:-1]) visualization: str launchers: Optional[ILauncher] = [] diff --git a/manager/manager/launcher/launcher_world.py b/manager/manager/launcher/launcher_world.py index 8e761cc..05c0350 100644 --- a/manager/manager/launcher/launcher_world.py +++ b/manager/manager/launcher/launcher_world.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Optional, ClassVar from pydantic import BaseModel from src.manager.libs.process_utils import get_class, class_from_module, get_ros_version @@ -48,11 +48,27 @@ } ], }, - "physical": {}, + "physical": {"1": [ + { + "type": "module", + "module": "ros_api", + "parameters": [], + "launch_file": [], + } + ], + "2": [ + { + "type": "module", + "module": "ros2_api", + "parameters": [], + "launch_file": [], + } + ],}, } class LauncherWorld(BaseModel): + running: ClassVar[bool] world: str launch_file_path: str module: str = ".".join(__name__.split(".")[:-1]) diff --git a/manager/manager/manager.py b/manager/manager/manager.py index 82f0f8e..551fe66 100644 --- a/manager/manager/manager.py +++ b/manager/manager/manager.py @@ -8,6 +8,8 @@ import psutil import shutil import time +import base64 +import zipfile if "noetic" in str(subprocess.check_output(["bash", "-c", "echo $ROS_DISTRO"])): import rosservice @@ -32,7 +34,6 @@ from src.manager.libs.process_utils import stop_process_and_children from src.manager.manager.lint.linter import Lint - class Manager: states = [ "idle", @@ -172,9 +173,7 @@ def on_connect(self, event): "radi_version": subprocess.check_output( ["bash", "-c", "echo $IMAGE_TAG"] ), - "ros_version": subprocess.check_output( - ["bash", "-c", "echo $ROS_DISTRO"] - ), + "ros_version": self.ros_version, "gpu_avaliable": check_gpu_acceleration(), }, command="introspection", @@ -200,17 +199,33 @@ def on_launch_world(self, event): Note: The method logs the start of the launch transition and the configuration details for debugging and traceability. """ - try: config_dict = event.kwargs.get("data", {}) + try: + if (config_dict["type"] == "zip"): + return self.on_launch_world_zip(config_dict) + except Exception: + pass configuration = ConfigurationManager.validate(config_dict) except ValueError as e: - LogManager.logger.error(f"Configuration validotion failed: {e}") + LogManager.logger.error(f"Configuration validation failed: {e}") self.world_launcher = LauncherWorld(**configuration.model_dump()) self.world_launcher.run() LogManager.logger.info("Launch transition finished") + def on_launch_world_zip(self, data): + print("BT Studio application") + + # Unzip the app + if data["code"].startswith('data:'): + _, _, code = data["code"].partition('base64,') + with open('/workspace/worlds/universe.zip', 'wb') as result: + result.write(base64.b64decode(code)) + zip_ref = zipfile.ZipFile("/workspace/worlds/universe.zip", 'r') + zip_ref.extractall("/workspace/worlds") + zip_ref.close() + def on_prepare_visualization(self, event): LogManager.logger.info("Visualization transition started") @@ -220,7 +235,7 @@ def on_prepare_visualization(self, event): ) self.visualization_launcher.run() - if visualization_type == "gazebo_rae": + if visualization_type == "gazebo_rae" or visualization_type == "physic_rae": self.gui_server = Server(2303, self.update) self.gui_server.start() LogManager.logger.info("Visualization transition finished") @@ -257,9 +272,15 @@ def add_frequency_control(self, code): def on_run_application(self, event): - superthin = False + code_path = "/workspace/code/exercise.py" # Extract app config application_configuration = event.kwargs.get("data", {}) + try: + if (application_configuration["type"] == "bt-studio"): + return self.on_run_bt_studio_application(application_configuration) + except Exception: + pass + application_file_path = application_configuration["template"] exercise_id = application_configuration["exercise_id"] code = application_configuration["code"] @@ -271,14 +292,14 @@ def on_run_application(self, event): application_folder = application_file_path + "/ros2_humble/" if not os.path.isfile(application_folder + "exercise.py"): - superthin = True + code_path = "/workspace/code/academy.py" # Make code backwards compatible code = code.replace("from GUI import GUI","import GUI") code = code.replace("from HAL import HAL","import HAL") # Create executable app - errors = self.linter.evaluate_code(code, exercise_id) + errors = self.linter.evaluate_code(code, exercise_id, self.ros_version) if errors == "": code = self.add_frequency_control(code) @@ -287,29 +308,45 @@ def on_run_application(self, event): f.close() shutil.copytree(application_folder, "/workspace/code", dirs_exist_ok=True) - if superthin: - self.application_process = subprocess.Popen( - ["python3", "/workspace/code/academy.py"], - stdout=sys.stdout, - stderr=subprocess.STDOUT, - bufsize=1024, - universal_newlines=True, - ) - else: - self.application_process = subprocess.Popen( - ["python3", "/workspace/code/exercise.py"], - stdout=sys.stdout, - stderr=subprocess.STDOUT, - bufsize=1024, - universal_newlines=True, - ) + self.application_process = subprocess.Popen( + ["python3", code_path], + stdout=sys.stdout, + stderr=subprocess.STDOUT, + bufsize=1024, + universal_newlines=True, + ) self.unpause_sim() else: - print("errors") + with open('/dev/pts/1', 'w') as console: + console.write(errors + "\n\n") + raise Exception(errors) LogManager.logger.info("Run application transition finished") + def on_run_bt_studio_application(self, data): + print("BT Studio application") + + # Unzip the app + if data["code"].startswith('data:'): + _, _, code = data["code"].partition('base64,') + with open('/workspace/code/app.zip', 'wb') as result: + result.write(base64.b64decode(code)) + zip_ref = zipfile.ZipFile("/workspace/code/app.zip", 'r') + zip_ref.extractall("/workspace/code") + zip_ref.close() + + self.application_process = subprocess.Popen( + ["python3", "/workspace/code/execute_docker.py"], + stdout=sys.stdout, + stderr=subprocess.STDOUT, + bufsize=1024, + universal_newlines=True, + ) + self.unpause_sim() + + LogManager.logger.info("Run application transition finished") + def on_terminate_application(self, event): if self.application_process: @@ -363,7 +400,7 @@ def on_disconnect(self, event): python = sys.executable os.execl(python, python, *sys.argv) - def process_messsage(self, message): + def process_message(self, message): if message.command == "gui": self.gui_server.send(message.data) return @@ -467,7 +504,7 @@ def signal_handler(sign, frame): time.sleep(0.1) else: message = self.queue.get() - self.process_messsage(message) + self.process_message(message) except Exception as e: if message is not None: ex = ManagerConsumerMessageException(id=message.id, message=str(e))