From e631ed751303d66749f4d024cd4bbf29982c3cb2 Mon Sep 17 00:00:00 2001 From: Javier Izquierdo Hernandez Date: Wed, 18 Feb 2026 11:25:38 +0100 Subject: [PATCH 01/15] Upload package changes --- .gitignore | 2 +- .idea/.gitignore | 8 - client_libraries/comms_manager.js | 170 ----------------- ...application_manager-0.0.3-py3-none-any.whl | Bin 0 -> 63551 bytes .../robotics_application_manager-0.0.3.tar.gz | Bin 0 -> 54318 bytes launch.py | 14 ++ manager/comms/consumer.py | 106 ----------- manager/libs/applications/brain_exercise.py | 21 -- .../libs/applications/compatibility/client.py | 49 ----- .../compatibility/exercise_wrapper.py | 75 -------- .../compatibility/exercise_wrapper_ros2.py | 180 ------------------ .../physical_robot_exercise_wrapper_ros2.py | 164 ---------------- .../robotics_application_wrapper.py | 111 ----------- .../libs/applications/robotics_application.py | 24 --- .../__pycache__/__init__.cpython-38.pyc | Bin 151 -> 0 bytes ...ython_application_interface.cpython-38.pyc | Bin 2020 -> 0 bytes .../robotics_python_application_interface.py | 28 --- manager/manager/config.json | 33 ---- manager/manager/launcher/__init__.py | 0 manager/manager/launcher/launcher_drones.py | 51 ----- .../manager/launcher/launcher_drones_gzsim.py | 39 ---- .../manager/launcher/launcher_drones_ros2.py | 48 ----- .../launcher/launcher_robot_display_view.py | 55 ------ manager/manager/launcher/launcher_ros.py | 92 --------- manager/manager/launcher/launcher_ros_api.py | 82 -------- .../launcher/launcher_teleoperator_ros2.py | 39 ---- manager/manager/lint/__init__.py | 0 manager/ram_logging/__init__.py | 0 pyproject.toml | 36 ++++ .../PKG-INFO | 169 ++++++++++++++++ .../SOURCES.txt | 55 ++++++ .../dependency_links.txt | 0 .../requires.txt | 11 ++ .../top_level.txt | 1 + robotics_application_manager/__init__.py | 2 + .../comms/__init__.py | 0 .../comms/consumer_message.py | 0 .../comms/new_consumer.py | 10 - .../comms/thread.py | 0 .../comms/websocket_server.py | 0 .../libs/__init__.py | 0 .../libs}/file_watchdog.py | 0 .../libs/launch_world_model.py | 0 .../libs/process_utils.py | 0 .../libs}/server.py | 0 .../libs/singleton.py | 0 .../manager}/__init__.py | 0 .../manager/docker_thread}/__init__.py | 0 .../manager/docker_thread/docker_thread.py | 0 .../manager/editor/serializers.py | 0 .../manager/launcher}/__init__.py | 0 .../manager/launcher/launcher_console.py | 0 .../manager/launcher/launcher_gazebo.py | 0 .../manager/launcher/launcher_gzsim.py | 0 .../manager/launcher/launcher_interface.py | 0 .../manager/launcher/launcher_o3de.py | 0 .../manager/launcher/launcher_o3de_api.py | 0 .../manager/launcher/launcher_robot.py | 0 .../launcher/launcher_robot_ros2_api.py | 0 .../manager/launcher/launcher_ros2_api.py | 0 .../manager/launcher/launcher_rviz.py | 0 .../manager/launcher/launcher_rviz_ros2.py | 0 .../launcher/launcher_state_monitor.py | 4 +- .../manager/launcher/launcher_tools.py | 0 .../manager/launcher/launcher_web_gui.py | 2 +- .../manager/launcher/launcher_world.py | 0 .../manager/lint}/__init__.py | 0 .../manager/lint/linter.py | 0 .../manager/lint/pylint_checker.py | 0 .../manager/lint/pylint_checker_style.py | 0 .../manager/lint/pylintrc | 0 .../manager/manager.py | 28 +-- .../manager/vnc/vnc_server.py | 0 .../ram_logging}/__init__.py | 0 .../ram_logging/log_manager.py | 0 75 files changed, 300 insertions(+), 1409 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 client_libraries/comms_manager.js create mode 100644 dist/robotics_application_manager-0.0.3-py3-none-any.whl create mode 100644 dist/robotics_application_manager-0.0.3.tar.gz create mode 100644 launch.py delete mode 100644 manager/comms/consumer.py delete mode 100644 manager/libs/applications/brain_exercise.py delete mode 100644 manager/libs/applications/compatibility/client.py delete mode 100644 manager/libs/applications/compatibility/exercise_wrapper.py delete mode 100644 manager/libs/applications/compatibility/exercise_wrapper_ros2.py delete mode 100644 manager/libs/applications/compatibility/physical_robot_exercise_wrapper_ros2.py delete mode 100644 manager/libs/applications/compatibility/robotics_application_wrapper.py delete mode 100644 manager/libs/applications/robotics_application.py delete mode 100644 manager/manager/application/__pycache__/__init__.cpython-38.pyc delete mode 100644 manager/manager/application/__pycache__/robotics_python_application_interface.cpython-38.pyc delete mode 100644 manager/manager/application/robotics_python_application_interface.py delete mode 100644 manager/manager/config.json delete mode 100644 manager/manager/launcher/__init__.py delete mode 100644 manager/manager/launcher/launcher_drones.py delete mode 100644 manager/manager/launcher/launcher_drones_gzsim.py delete mode 100644 manager/manager/launcher/launcher_drones_ros2.py delete mode 100644 manager/manager/launcher/launcher_robot_display_view.py delete mode 100644 manager/manager/launcher/launcher_ros.py delete mode 100644 manager/manager/launcher/launcher_ros_api.py delete mode 100644 manager/manager/launcher/launcher_teleoperator_ros2.py delete mode 100644 manager/manager/lint/__init__.py delete mode 100644 manager/ram_logging/__init__.py create mode 100644 pyproject.toml create mode 100644 robotics_application_manager.egg-info/PKG-INFO create mode 100644 robotics_application_manager.egg-info/SOURCES.txt rename manager/__init__.py => robotics_application_manager.egg-info/dependency_links.txt (100%) create mode 100644 robotics_application_manager.egg-info/requires.txt create mode 100644 robotics_application_manager.egg-info/top_level.txt create mode 100644 robotics_application_manager/__init__.py rename {manager => robotics_application_manager}/comms/__init__.py (100%) rename {manager => robotics_application_manager}/comms/consumer_message.py (100%) rename {manager => robotics_application_manager}/comms/new_consumer.py (94%) rename {manager => robotics_application_manager}/comms/thread.py (100%) rename {manager => robotics_application_manager}/comms/websocket_server.py (100%) rename {manager => robotics_application_manager}/libs/__init__.py (100%) rename {manager/libs/applications/compatibility => robotics_application_manager/libs}/file_watchdog.py (100%) rename {manager => robotics_application_manager}/libs/launch_world_model.py (100%) rename {manager => robotics_application_manager}/libs/process_utils.py (100%) rename {manager/libs/applications/compatibility => robotics_application_manager/libs}/server.py (100%) rename {manager => robotics_application_manager}/libs/singleton.py (100%) rename {manager/libs/applications => robotics_application_manager/manager}/__init__.py (100%) rename {manager/libs/applications/compatibility => robotics_application_manager/manager/docker_thread}/__init__.py (100%) rename {manager => robotics_application_manager}/manager/docker_thread/docker_thread.py (100%) rename {manager => robotics_application_manager}/manager/editor/serializers.py (100%) rename {manager/manager => robotics_application_manager/manager/launcher}/__init__.py (100%) rename {manager => robotics_application_manager}/manager/launcher/launcher_console.py (100%) rename {manager => robotics_application_manager}/manager/launcher/launcher_gazebo.py (100%) rename {manager => robotics_application_manager}/manager/launcher/launcher_gzsim.py (100%) rename {manager => robotics_application_manager}/manager/launcher/launcher_interface.py (100%) rename {manager => robotics_application_manager}/manager/launcher/launcher_o3de.py (100%) rename {manager => robotics_application_manager}/manager/launcher/launcher_o3de_api.py (100%) rename {manager => robotics_application_manager}/manager/launcher/launcher_robot.py (100%) rename {manager => robotics_application_manager}/manager/launcher/launcher_robot_ros2_api.py (100%) rename {manager => robotics_application_manager}/manager/launcher/launcher_ros2_api.py (100%) rename {manager => robotics_application_manager}/manager/launcher/launcher_rviz.py (100%) rename {manager => robotics_application_manager}/manager/launcher/launcher_rviz_ros2.py (100%) rename {manager => robotics_application_manager}/manager/launcher/launcher_state_monitor.py (88%) rename {manager => robotics_application_manager}/manager/launcher/launcher_tools.py (100%) rename {manager => robotics_application_manager}/manager/launcher/launcher_web_gui.py (94%) rename {manager => robotics_application_manager}/manager/launcher/launcher_world.py (100%) rename {manager/manager/application => robotics_application_manager/manager/lint}/__init__.py (100%) rename {manager => robotics_application_manager}/manager/lint/linter.py (100%) rename {manager => robotics_application_manager}/manager/lint/pylint_checker.py (100%) rename {manager => robotics_application_manager}/manager/lint/pylint_checker_style.py (100%) rename {manager => robotics_application_manager}/manager/lint/pylintrc (100%) rename {manager => robotics_application_manager}/manager/manager.py (98%) rename {manager => robotics_application_manager}/manager/vnc/vnc_server.py (100%) rename {manager/manager/docker_thread => robotics_application_manager/ram_logging}/__init__.py (100%) rename {manager => robotics_application_manager}/ram_logging/log_manager.py (100%) diff --git a/.gitignore b/.gitignore index f95d89c..eb9bdfe 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ # Byte-compiled file __pycache__/ -/.idea +.idea/ # IDEs .vscode diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 13566b8..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/client_libraries/comms_manager.js b/client_libraries/comms_manager.js deleted file mode 100644 index 1daf55e..0000000 --- a/client_libraries/comms_manager.js +++ /dev/null @@ -1,170 +0,0 @@ -import * as log from "loglevel"; -import { v4 as uuidv4 } from "uuid"; - -const CommsManager = (address) => { - let websocket = null; - - log.enableAll(); - - const events = { - RESPONSES: ["ack", "error"], - UPDATE: "update", - STATE_CHANGED: "state-changed", - VERSION: "version", - }; - - //region Observer pattern methods - const observers = {}; - let currentState = null; - - const subscribe = (suscribingEvents, callback) => { - if (typeof suscribingEvents === "string") { - suscribingEvents = [suscribingEvents]; - } - for (let i = 0, length = suscribingEvents.length; i < length; i++) { - let event = suscribingEvents[i]; - observers[event] = observers[event] || []; - observers[event].push(callback); - if (event === events.STATE_CHANGED && currentState !== null) { - callback({ command: events.STATE_CHANGED, data: currentState }); - } - } - }; - - const unsubscribe = (events, callback) => { - if (typeof events === "string") { - events = [events]; - } - for (let i = 0, length = events.length; i < length; i++) { - observers[events[i]] = observers[events[i]] || []; - observers[events[i]].splice(observers[events[i]].indexOf(callback)); - } - }; - - const unsuscribeAll = () => { - for (const event in observers) { - observers[event].length = 0; - } - }; - - const subscribeOnce = (event, callback) => { - subscribe(event, (response) => { - callback(response); - unsubscribe(event, callback); - }); - }; - - const dispatch = (message) => { - if (message.command === events.STATE_CHANGED) { - currentState = message.data; - } - - const subscriptions = observers[message.command] || []; - let length = subscriptions.length; - while (length--) { - subscriptions[length](message); - } - }; - //endregion - - // Send and receive method - const connect = () => { - return new Promise((resolve, reject) => { - websocket = new WebSocket(address); - - websocket.onopen = () => { - log.debug(`Connection with ${address} opened`); - send("connect") - .then(() => { - resolve(); - }) - .catch(() => { - reject(); - }); - }; - - websocket.onclose = (e) => { - // TODO: Rethink what to do when connection is interrupted, - // maybe try to reconnect and not clear the suscribers? - unsuscribeAll(); - if (e.wasClean) { - log.debug( - `Connection with ${address} closed, all suscribers cleared` - ); - } else { - log.debug(`Connection with ${address} interrupted`); - } - }; - - websocket.onerror = (e) => { - log.debug(`Error received from websocket: ${e.type}`); - reject(); - }; - - websocket.onmessage = (e) => { - const message = JSON.parse(e.data); - dispatch(message); - }; - }); - }; - - const send = (message, data) => { - // Sending messages to remote manager - return new Promise((resolve, reject) => { - const id = uuidv4(); - - if (!websocket || websocket.readyState !== WebSocket.OPEN) { - reject({ - id: "", - command: "error", - data: { - message: "Websocket not connected", - }, - }); - } - - subscribeOnce(["ack", "error"], (response) => { - if (id === response.id) { - if (response.command === "ack") { - resolve(response); - } else { - reject(response); - } - } - }); - - const msg = JSON.stringify({ - id: id, - command: message, - data: data, - }); - websocket.send(msg); - }); - }; - - // Messages and events - const commands = { - connect: connect, - launch: (configuration) => send("launch", configuration), - run: () => send("run"), - stop: () => send("stop"), - pause: () => send("pause"), - resume: () => send("resume"), - reset: () => send("reset"), - terminate: () => send("terminate"), - disconnect: () => send("disconnect"), - }; - - return { - ...commands, - - send: send, - - events: events, - subscribe: subscribe, - unsubscribe: unsubscribe, - suscribreOnce: subscribeOnce, - }; -}; - -export default CommsManager; diff --git a/dist/robotics_application_manager-0.0.3-py3-none-any.whl b/dist/robotics_application_manager-0.0.3-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..ae3d58a64f4915992a8b3efc97c34fed782ac03a GIT binary patch literal 63551 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Ŷw2QB5}KNUtU* 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 literal 0 HcmV?d00001 diff --git a/dist/robotics_application_manager-0.0.3.tar.gz b/dist/robotics_application_manager-0.0.3.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..2e40c98b9ae7b80a57bc8b8ba69ba2b6983bd93a GIT binary patch literal 54318 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

u4DF^TTlHp`-3ku_jjwqJhE9e?3=l`V=+`!Yr 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%0Dn0CKb`hv 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 None: - try: - while True: - self.client.run_forever(ping_timeout=None, ping_interval=0) - if self._stop.isSet(): - return - except Exception as ex: - LogManager.logger.exception(ex) - - def stop(self) -> None: - self._stop.set() - self.client.close() - - def send(self, data): - self.client.send(data) - - def on_message(self, ws, message): - self.callback(self.name, message) - - def on_error(self, ws, error): - LogManager.logger.error(error) - - def on_close(self, ws, status, msg): - LogManager.logger.info( - f"Connection with {self.name} closed, status code: {status}, close message: {msg}" - ) - - def on_open(self, ws): - LogManager.logger.info(f"Connection with {self.name} opened") diff --git a/manager/libs/applications/compatibility/exercise_wrapper.py b/manager/libs/applications/compatibility/exercise_wrapper.py deleted file mode 100644 index 443d94f..0000000 --- a/manager/libs/applications/compatibility/exercise_wrapper.py +++ /dev/null @@ -1,75 +0,0 @@ -import json -import signal -import subprocess -import sys -import threading -import time -import importlib -from threading import Thread - -from manager.libs.applications.compatibility.client import Client -from manager.libs.process_utils import stop_process_and_children -from manager.ram_logging.log_manager import LogManager -from manager.manager.application.robotics_python_application_interface import ( - IRoboticsPythonApplication, -) -from manager.manager.lint.linter import Lint - - -class CompatibilityExerciseWrapper: - def __init__(self): - self.running = False - self.linter = Lint() - self.brain_ready_event = threading.Event() - self.pick = None - self.exercise = None - self.run() - - def save_pick(self, pick): - self.pick = pick - - def send_pick(self, pick): - self.gui_connection.send("#pick" + json.dumps(pick)) - print("#pick" + json.dumps(pick)) - - def handle_client_gui(self, msg): - if msg["msg"] == "#pick": - self.pick = msg["data"] - else: - self.gui_connection.send(msg["msg"]) - - def _run_server(self, cmd): - process = subprocess.Popen( - f"{cmd}", - shell=True, - stdout=sys.stdout, - stderr=subprocess.STDOUT, - bufsize=1024, - universal_newlines=True, - ) - return process - - def run(self): - self.exercise = self._run_server( - f"python3 $EXERCISE_FOLDER/entry_point/exercise.py" - ) - - def stop(self): - pass - - def resume(self): - pass - - def pause(self): - pass - - @property - def is_alive(self): - return self.running - - def terminate(self): - - if self.exercise is not None: - stop_process_and_children(self.exercise) - - self.running = False diff --git a/manager/libs/applications/compatibility/exercise_wrapper_ros2.py b/manager/libs/applications/compatibility/exercise_wrapper_ros2.py deleted file mode 100644 index 08129e3..0000000 --- a/manager/libs/applications/compatibility/exercise_wrapper_ros2.py +++ /dev/null @@ -1,180 +0,0 @@ -import json -import logging -import os.path -import subprocess -import sys -import threading -import time -from threading import Thread - -from manager.libs.applications.compatibility.client import Client -from manager.libs.process_utils import stop_process_and_children -from manager.ram_logging.log_manager import LogManager -from manager.manager.application.robotics_python_application_interface import ( - IRoboticsPythonApplication, -) -from manager.manager.lint.linter import Lint - - -class CompatibilityExerciseWrapperRos2(IRoboticsPythonApplication): - def __init__(self, exercise_command, gui_command, update_callback): - super().__init__(update_callback) - - home_dir = os.path.expanduser("~") - self.running = False - self.linter = Lint() - self.brain_ready_event = threading.Event() - # TODO: review hardcoded values - process_ready, self.exercise_server = self._run_exercise_server( - f"python3 {exercise_command}", - f"{home_dir}/ws_code.log", - "websocket_code=ready", - ) - if process_ready: - LogManager.logger.info(f"Exercise code {exercise_command} launched") - time.sleep(1) - self.exercise_connection = Client( - "ws://127.0.0.1:1905", "exercise", self.server_message - ) - self.exercise_connection.start() - else: - self.exercise_server.kill() - raise RuntimeError(f"Exercise {exercise_command} could not be run") - - process_ready, self.gui_server = self._run_exercise_server( - f"python3 {gui_command}", f"{home_dir}/ws_gui.log", "websocket_gui=ready" - ) - if process_ready: - LogManager.logger.info(f"Exercise gui {gui_command} launched") - time.sleep(1) - self.gui_connection = Client( - "ws://127.0.0.1:2303", "gui", self.server_message - ) - self.gui_connection.start() - else: - self.gui_server.kill() - raise RuntimeError(f"Exercise GUI {gui_command} could not be run") - - self.running = True - - self.start_send_freq_thread() - - def send_freq(self, exercise_connection, is_alive): - """Send the frequency of the brain and gui to the exercise server""" - while is_alive(): - exercise_connection.send("""#freq{"brain": 20, "gui": 10, "rtf": 100}""") - time.sleep(1) - - def start_send_freq_thread(self): - """Start a thread to send the frequency of the brain and gui to the exercise server""" - daemon = Thread( - target=lambda: self.send_freq( - self.exercise_connection, lambda: self.is_alive - ), - daemon=False, - name="Monitor frequencies", - ) - daemon.start() - - def _run_exercise_server(self, cmd, log_file, load_string, timeout: int = 5): - process = subprocess.Popen( - f"{cmd}", - shell=True, - stdout=sys.stdout, - stderr=subprocess.STDOUT, - bufsize=1024, - universal_newlines=True, - ) - - process_ready = False - while not process_ready: - try: - f = open(log_file, "r") - if f.readline() == load_string: - process_ready = True - f.close() - time.sleep(0.2) - except Exception as e: - LogManager.logger.debug(f"waiting for server string '{load_string}'...") - time.sleep(0.2) - - return process_ready, process - - def server_message(self, name, message): - if name == "gui": # message received from GUI server - LogManager.logger.debug(f"Message received from gui: {message[:30]}") - self._process_gui_message(message) - elif name == "exercise": # message received from EXERCISE server - if message.startswith("#exec"): - self.brain_ready_event.set() - LogManager.logger.info(f"Message received from exercise: {message[:30]}") - self._process_exercise_message(message) - - def _process_gui_message(self, message): - payload = json.loads(message[4:]) - self.update_callback(payload) - self.gui_connection.send("#ack") - - def _process_exercise_message(self, message): - comand = message[:5] - if message == comand: - payload = comand - else: - payload = json.loads(message[5:]) - self.update_callback(payload) - self.exercise_connection.send("#ack") - - def call_service(self, service, service_type): - command = f"ros2 service call {service} {service_type}" - subprocess.call( - f"{command}", - shell=True, - stdout=sys.stdout, - stderr=subprocess.STDOUT, - bufsize=1024, - universal_newlines=True, - ) - - def run(self): - self.call_service("/unpause_physics", "std_srvs/srv/Empty") - self.exercise_connection.send("#play") - - def stop(self): - self.call_service("/pause_physics", "std_srvs/srv/Empty") - self.call_service("/reset_world", "std_srvs/srv/Empty") - self.exercise_connection.send("#rest") - - def resume(self): - self.call_service("/unpause_physics", "std_srvs/srv/Empty") - self.exercise_connection.send("#play") - - def pause(self): - self.call_service("/pause_physics", "std_srvs/srv/Empty") - self.exercise_connection.send("#stop") - - def restart(self): - # pause_cmd = "ros2 service call /restart_simulation std_srvs/srv/Empty" - # subprocess.call(f"{pause_cmd}", shell=True, stdout=sys.stdout, stderr=subprocess.STDOUT, bufsize=1024, - # universal_newlines=True) - pass - - @property - def is_alive(self): - return self.running - - def load_code(self, code: str): - errors = self.linter.evaluate_code(code) - if errors == "": - self.brain_ready_event.clear() - self.exercise_connection.send(f"#code {code}") - self.brain_ready_event.wait() - else: - raise Exception(errors) - - def terminate(self): - self.running = False - self.exercise_connection.stop() - self.gui_connection.stop() - - stop_process_and_children(self.exercise_server) - stop_process_and_children(self.gui_server) diff --git a/manager/libs/applications/compatibility/physical_robot_exercise_wrapper_ros2.py b/manager/libs/applications/compatibility/physical_robot_exercise_wrapper_ros2.py deleted file mode 100644 index 97d7adc..0000000 --- a/manager/libs/applications/compatibility/physical_robot_exercise_wrapper_ros2.py +++ /dev/null @@ -1,164 +0,0 @@ -import json -import logging -import os.path -import subprocess -import sys -import threading -import time -from threading import Thread - -from manager.libs.applications.compatibility.client import Client -from manager.libs.process_utils import stop_process_and_children -from manager.ram_logging.log_manager import LogManager -from manager.manager.application.robotics_python_application_interface import ( - IRoboticsPythonApplication, -) -from manager.manager.lint.linter import Lint - - -class CompatibilityExerciseWrapperRos2(IRoboticsPythonApplication): - def __init__(self, exercise_command, gui_command, update_callback): - super().__init__(update_callback) - - home_dir = os.path.expanduser("~") - self.running = False - self.linter = Lint() - self.brain_ready_event = threading.Event() - # TODO: review hardcoded values - process_ready, self.exercise_server = self._run_exercise_server( - f"python3 {exercise_command}", - f"{home_dir}/ws_code.log", - "websocket_code=ready", - ) - if process_ready: - LogManager.logger.info(f"Exercise code {exercise_command} launched") - time.sleep(1) - self.exercise_connection = Client( - "ws://127.0.0.1:1905", "exercise", self.server_message - ) - self.exercise_connection.start() - else: - self.exercise_server.kill() - raise RuntimeError(f"Exercise {exercise_command} could not be run") - - process_ready, self.gui_server = self._run_exercise_server( - f"python3 {gui_command}", f"{home_dir}/ws_gui.log", "websocket_gui=ready" - ) - if process_ready: - LogManager.logger.info(f"Exercise gui {gui_command} launched") - time.sleep(1) - self.gui_connection = Client( - "ws://127.0.0.1:2303", "gui", self.server_message - ) - self.gui_connection.start() - else: - self.gui_server.kill() - raise RuntimeError(f"Exercise GUI {gui_command} could not be run") - - self.running = True - - self.start_send_freq_thread() - - def send_freq(self, exercise_connection, is_alive): - """Send the frequency of the brain and gui to the exercise server""" - while is_alive(): - exercise_connection.send("""#freq{"brain": 20, "gui": 10, "rtf": 100}""") - time.sleep(1) - - def start_send_freq_thread(self): - """Start a thread to send the frequency of the brain and gui to the exercise server""" - daemon = Thread( - target=lambda: self.send_freq( - self.exercise_connection, lambda: self.is_alive - ), - daemon=False, - name="Monitor frequencies", - ) - daemon.start() - - def _run_exercise_server(self, cmd, log_file, load_string, timeout: int = 5): - process = subprocess.Popen( - f"{cmd}", - shell=True, - stdout=sys.stdout, - stderr=subprocess.STDOUT, - bufsize=1024, - universal_newlines=True, - ) - - process_ready = False - while not process_ready: - try: - f = open(log_file, "r") - if f.readline() == load_string: - process_ready = True - f.close() - time.sleep(0.2) - except Exception as e: - LogManager.logger.debug(f"waiting for server string '{load_string}'...") - time.sleep(0.2) - - return process_ready, process - - def server_message(self, name, message): - if name == "gui": # message received from GUI server - LogManager.logger.debug(f"Message received from gui: {message[:30]}") - self._process_gui_message(message) - elif name == "exercise": # message received from EXERCISE server - if message.startswith("#exec"): - self.brain_ready_event.set() - LogManager.logger.info(f"Message received from exercise: {message[:40]}") - self._process_exercise_message(message) - - def _process_gui_message(self, message): - payload = json.loads(message[4:]) - self.update_callback(payload) - self.gui_connection.send("#ack") - - def _process_exercise_message(self, message): - comand = message[:5] - if message == comand: - payload = comand - else: - payload = json.loads(message[5:]) - self.update_callback(payload) - self.exercise_connection.send("#ack") - - def run(self): - self.exercise_connection.send("#play") - - def stop(self): - self.exercise_connection.send("#rest") - - def resume(self): - self.exercise_connection.send("#play") - - def pause(self): - self.exercise_connection.send("#stop") - - def restart(self): - # pause_cmd = "ros2 service call /restart_simulation std_srvs/srv/Empty" - # subprocess.call(f"{pause_cmd}", shell=True, stdout=sys.stdout, stderr=subprocess.STDOUT, bufsize=1024, - # universal_newlines=True) - pass - - @property - def is_alive(self): - return self.running - - def load_code(self, code: str): - errors = self.linter.evaluate_code(code) - if errors == "": - self.brain_ready_event.clear() - self.exercise_connection.send(f"#code {code}") - self.brain_ready_event.wait() - else: - raise Exception(errors) - - def terminate(self): - self.running = False - self.exercise_connection.stop() - self.gui_connection.stop() - - stop_process_and_children(self.exercise_server) - stop_process_and_children(self.gui_server) diff --git a/manager/libs/applications/compatibility/robotics_application_wrapper.py b/manager/libs/applications/compatibility/robotics_application_wrapper.py deleted file mode 100644 index 400e70a..0000000 --- a/manager/libs/applications/compatibility/robotics_application_wrapper.py +++ /dev/null @@ -1,111 +0,0 @@ -import os.path -import subprocess -import sys -import time - -import psutil - -from manager.libs.process_utils import stop_process_and_children -from manager.manager.application.robotics_python_application_interface import ( - IRoboticsPythonApplication, -) -from manager.manager.lint.linter import Lint -from manager.manager.docker_thread.docker_thread import DockerThread - -from manager.libs.applications.compatibility.client import Client -from manager.libs.process_utils import stop_process_and_children -from manager.ram_logging.log_manager import LogManager -from manager.manager.application.robotics_python_application_interface import ( - IRoboticsPythonApplication, -) -from manager.manager.lint.linter import Lint - -import os - - -class RoboticsApplicationWrapper(IRoboticsPythonApplication): - def __init__(self, update_callback): - super().__init__(update_callback) - self.running = False - self.linter = Lint() - time.sleep(5) - self.start_console() - self.user_process = None - self.entrypoint_path = None - - def _create_process(self, cmd): - # print("creando procesos") - process = subprocess.Popen( - f"{cmd}", - shell=True, - stdout=sys.stdout, - stderr=subprocess.STDOUT, - bufsize=1024, - universal_newlines=True, - cwd="/workspace/code", - ) - psProcess = psutil.Process(pid=process.pid) - return psProcess - - def terminate(self): - self.running = False - if self.user_process != None: - stop_process_and_children(self.user_process) - self.user_process = None - - def load_code(self, path: str): - self.entrypoint_path = path - - def run(self): - self.user_process = self._create_process( - f"DISPLAY=:2 python3 {self.entrypoint_path}" - ) - self.running = True - - def stop(self): - stop_process_and_children(self.user_process) - self.user_process = None - - def restart(self): - pass - - def resume(self): - self.suspend_resume("resume") - - def pause(self): - if self.user_process != None: - self.suspend_resume("pause") - - @property - def is_alive(self): - return self.running - - def start_console(self): - # 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(self): - sys.stderr.close() - sys.stdout.close() - sys.stdin.close() - - def suspend_resume(self, signal): - # collect processes to stop - children = self.user_process.children(recursive=True) - children.append(self.user_process) - - # send signal to processes - for p in children: - try: - if signal == "pause": - p.suspend() - if signal == "resume": - p.resume() - except psutil.NoSuchProcess: - pass diff --git a/manager/libs/applications/robotics_application.py b/manager/libs/applications/robotics_application.py deleted file mode 100644 index c62b627..0000000 --- a/manager/libs/applications/robotics_application.py +++ /dev/null @@ -1,24 +0,0 @@ -from manager.manager.application.robotics_python_application_interface import ( - IRoboticsPythonApplication, -) - - -class RoboticsApplication(IRoboticsPythonApplication): - def terminate(self): - pass - - def load_code(self, code: str): - pass - - def run(self): - pass - - def stop(self): - pass - - def restart(self): - pass - - @property - def is_alive(self): - pass diff --git a/manager/manager/application/__pycache__/__init__.cpython-38.pyc b/manager/manager/application/__pycache__/__init__.cpython-38.pyc deleted file mode 100644 index 76c82566d2ab1db40e46a075551d69f0a479f7d5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 151 zcmWIL<>g`k0#>o06cGIwL?8o3AjbiSi&=m~3PUi1CZpdOEUBG^yA|*^D;}~j2MBHX$)$M#gs4!3dWk9!2XZma+JzPS$JmQV)f3HQ z@B%&ZI(y|2xN%}8VYf@wY!t8T@p{JNZ|wE#yH2OYu$;9o)lY}9U-VKgIL2OL)!W#- zW|B#3iuKAKF=@YLeaFM`|4K3A0}d-TvLqYX(vtR+jT}`|b?N-DWlh$nwb7bflMNhi zNLMydy0RtLQ8r~;c2KtDhU}tTmz#16Wm|RRw%nPrD{HhNcX9oD+1}}!cpMww(`OU& zDUOblB=9|9{5bOL5*hWWzQU?0=u7L8af}GPSi`=Rb<;!&qqrx6U@W}PeLJgZ6`TXj zx@J&A@#p;@U+vfvQiYR&25%6GNPJXKNO8k3fPZv6nc?x0j{A{Oa4tM`luR<07%(^&O9NkfFPoQ2bRkjAoq)-ud8+{r&KI=Ok3kGfvFP@!zQ{eep^~NClNcX~2 zo1PygfeKY*dO<8CC->J@wlj>)X|7T71R#dK4RqwtpoaEn-qf*|HH*pgn#Xil5mPb2 zvv5XUs9&7jT|w+G2?Ejxn&{-(n#11WbWrc2IwyQkL5QZSu^lX7(SAq<-N`#Lp2t`fbj?q#~GWU=96djy90h#&H|~ z-B9RILze-%)NE2iUmnoUGjkJ&69r}hJ2ZBaf@VERkU)8ks-{oDwOnV@aZ8VF%`EU| THIsL_$lT|1;uqxSH&*r++qLHX diff --git a/manager/manager/application/robotics_python_application_interface.py b/manager/manager/application/robotics_python_application_interface.py deleted file mode 100644 index 26ac826..0000000 --- a/manager/manager/application/robotics_python_application_interface.py +++ /dev/null @@ -1,28 +0,0 @@ -class IRoboticsPythonApplication: - def __init__(self, update_callback): - self.update_callback = update_callback - - def load_code(self, code: str) -> bool: - raise NotImplementedError("Exercise brains must implement load_code") - - def run(self): - raise NotImplementedError("Exercise brains must implement run") - - def stop(self): - raise NotImplementedError("Exercise brains must implement stop") - - def pause(self): - raise NotImplementedError("Exercise brains must implement pause") - - def resume(self): - raise NotImplementedError("Exercise brains must implement resume") - - def restart(self): - raise NotImplementedError("Exercise brains must implement restart") - - def terminate(self): - raise NotImplementedError("Exercise brains must implement terminate") - - @property - def is_alive(self): - raise NotImplementedError("Exercise brains must implement is_alive") diff --git a/manager/manager/config.json b/manager/manager/config.json deleted file mode 100644 index f8c71e3..0000000 --- a/manager/manager/config.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "application": { - "type": "python", - "entry_point": "$EXERCISE_FOLDER/entry_point/exercise.py", - "class_name": "Exercise", - "params": "" - }, - "launch": { - "0": { - "type": "module", - "module": "ros_api", - "resource_folders": [ - "$EXERCISE_FOLDER/launch" - ], - "model_folders": [ - "$CUSTOM_ROBOTS_FOLDER/f1/models" - ], - "plugin_folders": [ - ], - "parameters": [], - "launch_file": "$EXERCISE_FOLDER/launch/simple_line_follower_ros_headless_${circuit}.launch" - }, - "1": { - "type": "module", - "module": "console", - "display": ":1", - "internal_port": 5901, - "external_port": 1108, - "height": 1080, - "width": 1920 - } - } -} \ No newline at end of file diff --git a/manager/manager/launcher/__init__.py b/manager/manager/launcher/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/manager/manager/launcher/launcher_drones.py b/manager/manager/launcher/launcher_drones.py deleted file mode 100644 index 7aff0be..0000000 --- a/manager/manager/launcher/launcher_drones.py +++ /dev/null @@ -1,51 +0,0 @@ -import os -import subprocess -from manager.manager.launcher.launcher_interface import ILauncher, LauncherException -from manager.manager.docker_thread.docker_thread import DockerThread -from manager.libs.process_utils import wait_for_xserver -from typing import List, Any -import psutil -import threading - - -class LauncherDrones(ILauncher): - exercise_id: str - type: str - module: str - parameters: List[str] - launch_file: str - process: Any = None - threads: List[Any] = [] - - # holder for roslaunch process - launch: Any = None - - def run(self, callback: callable = None): - # Start X server in display - xserver_cmd = f"/usr/bin/Xorg -quiet -noreset +extension GLX +extension RANDR +extension RENDER -logfile ./xdummy.log -config ./xorg.conf :0" - xserver_thread = DockerThread(xserver_cmd) - xserver_thread.start() - wait_for_xserver(":0") - self.threads.append(xserver_thread) - self.launch_file = os.path.expandvars(self.launch_file) - - # Inicia el proceso en un hilo separado - self.launch = DockerThread(f"python3 {self.launch_file}") - self.launch.start() - self.threads.append(self.launch) - - def is_running(self): - return True - - def terminate(self): - try: - for thread in self.threads: - if thread.is_alive(): - thread.terminate() - thread.join() - self.threads.remove(thread) - except Exception as e: - print("Exception shutting down ROS") - - def died(self): - pass diff --git a/manager/manager/launcher/launcher_drones_gzsim.py b/manager/manager/launcher/launcher_drones_gzsim.py deleted file mode 100644 index b3b9628..0000000 --- a/manager/manager/launcher/launcher_drones_gzsim.py +++ /dev/null @@ -1,39 +0,0 @@ -import os -from src.manager.manager.launcher.launcher_interface import ILauncher -from src.manager.manager.docker_thread.docker_thread import DockerThread -from src.manager.libs.process_utils import wait_for_xserver -from typing import List, Any - - -class LauncherDronesGzsim(ILauncher): - type: str - module: str - launch_file: str - threads: List[Any] = [] - - def run(self, callback): - # Start X server in display - xserver_cmd = f"/usr/bin/Xorg -quiet -noreset +extension GLX +extension RANDR +extension RENDER -logfile ./xdummy.log -config ./xorg.conf :0" - xserver_thread = DockerThread(xserver_cmd) - xserver_thread.start() - wait_for_xserver(":0") - self.threads.append(xserver_thread) - - # expand variables in configuration paths - world_file = os.path.expandvars(self.launch_file) - - # Launching gzserver and Aerostack2 nodes - as2_launch_cmd = f"ros2 launch jderobot_drones as2_default_gazebo_sim.launch.py world_file:={world_file}" - - as2_launch_thread = DockerThread(as2_launch_cmd) - as2_launch_thread.start() - self.threads.append(as2_launch_thread) - - def is_running(self): - return True - - def terminate(self): - if self.is_running(): - for thread in self.threads: - thread.terminate() - thread.join() diff --git a/manager/manager/launcher/launcher_drones_ros2.py b/manager/manager/launcher/launcher_drones_ros2.py deleted file mode 100644 index e4c5d6f..0000000 --- a/manager/manager/launcher/launcher_drones_ros2.py +++ /dev/null @@ -1,48 +0,0 @@ -import os -from manager.manager.launcher.launcher_interface import ILauncher -from manager.manager.docker_thread.docker_thread import DockerThread -from manager.libs.process_utils import wait_for_xserver -from typing import List, Any - - -class LauncherDronesRos2(ILauncher): - type: str - module: str - launch_file: str - threads: List[Any] = [] - - def run(self, callback): - # Start X server in display - xserver_cmd = f"/usr/bin/Xorg -quiet -noreset +extension GLX +extension RANDR +extension RENDER -logfile ./xdummy.log -config ./xorg.conf :0" - xserver_thread = DockerThread(xserver_cmd) - xserver_thread.start() - wait_for_xserver(":0") - self.threads.append(xserver_thread) - - # expand variables in configuration paths - world_file = os.path.expandvars(self.launch_file) - - # Launching MicroXRCE and Aerostack2 nodes - as2_launch_cmd = f"ros2 launch jderobot_drones as2_default_classic_gazebo.launch.py world_file:={world_file}" - - as2_launch_thread = DockerThread(as2_launch_cmd) - as2_launch_thread.start() - self.threads.append(as2_launch_thread) - - # Launching gzserver and PX4 - px4_launch_cmd = f"$AS2_GZ_ASSETS_SCRIPT_PATH/default_run.sh {world_file}" - - px4_launch_thread = DockerThread(px4_launch_cmd) - px4_launch_thread.start() - self.threads.append(px4_launch_thread) - - def is_running(self): - return True - - def terminate(self): - if self.is_running(): - for thread in self.threads: - if thread.is_alive(): - thread.terminate() - thread.join() - self.threads.remove(thread) diff --git a/manager/manager/launcher/launcher_robot_display_view.py b/manager/manager/launcher/launcher_robot_display_view.py deleted file mode 100644 index 09e153d..0000000 --- a/manager/manager/launcher/launcher_robot_display_view.py +++ /dev/null @@ -1,55 +0,0 @@ -from manager.manager.launcher.launcher_interface import ILauncher -from manager.manager.docker_thread.docker_thread import DockerThread -from manager.manager.vnc.vnc_server import Vnc_server -import time -import os -import stat - - -class LauncherRobotDisplayView(ILauncher): - display: str - internal_port: str - external_port: str - height: int - width: int - running = False - threads = [] - - def run(self, config_file, callback): - DRI_PATH = self.get_dri_path() - ACCELERATION_ENABLED = self.check_device(DRI_PATH) - - robot_display_vnc = Vnc_server() - - if ACCELERATION_ENABLED: - robot_display_vnc.start_vnc_gpu( - self.display, self.internal_port, self.external_port, DRI_PATH - ) - # Write display config and start the console - console_cmd = f"export VGL_DISPLAY={DRI_PATH}; export DISPLAY={self.display}; /usr/bin/Xorg -noreset +extension GLX +extension RANDR +extension RENDER -logfile ./xdummy.log -config ./xorg.conf {self.display}" - else: - robot_display_vnc.start_vnc( - self.display, self.internal_port, self.external_port - ) - # Write display config and start the console - console_cmd = f"export DISPLAY={self.display};/usr/bin/Xorg -noreset +extension GLX +extension RANDR +extension RENDER -logfile ./xdummy.log -config ./xorg.conf {self.display}" - - console_thread = DockerThread(console_cmd) - console_thread.start() - self.threads.append(console_thread) - - self.running = True - - def is_running(self): - return self.running - - def terminate(self): - for thread in self.threads: - if thread.is_alive(): - thread.terminate() - thread.join() - self.threads.remove(thread) - self.running = False - - def died(self): - pass diff --git a/manager/manager/launcher/launcher_ros.py b/manager/manager/launcher/launcher_ros.py deleted file mode 100644 index af91171..0000000 --- a/manager/manager/launcher/launcher_ros.py +++ /dev/null @@ -1,92 +0,0 @@ -import os -import shutil -import subprocess -import traceback -from typing import Any, List - -from manager.launcher.launcher_interface import ILauncher, LauncherException - - -class LauncherRos(ILauncher): - """ - Launcher for ROS/Gazebo - - It's configuration should follow this spec: - - { - "type": "module", - "module": "ros", - "resource_folders": [ - "$EXERCISE_FOLDER/launch" - ], - "model_folders": [ - "$CUSTOM_ROBOTS/f1/models" - ], - "plugin_folders": [ - ], - "parameters": [], - "launch_file": "$EXERCISE_FOLDER/launch/simple_line_follower_ros_headless_default.launch" - } - """ - - exercise_id: str - type: str - module: str - resource_folders: List[str] - model_folders: List[str] - plugin_folders: List[str] - parameters: List[str] - launch_file: str - - ros_command_line: str = shutil.which("roslaunch") - process: Any = None - - def run(self): - try: - # generate entry_point environment variable - os.environ["EXERCISE_FOLDER"] = ( - f"{os.environ.get('EXERCISES_STATIC_FOLDER')}/{self.exercise_id}" - ) - - # expand variables in configuration paths - resource_folders = [ - os.path.expandvars(path) for path in self.resource_folders - ] - model_folders = [os.path.expandvars(path) for path in self.model_folders] - plugin_folders = [os.path.expandvars(path) for path in self.plugin_folders] - launch_file = os.path.expandvars(self.launch_file) - - env = dict(os.environ) - env["GAZEBO_RESOURCE_PATH"] = ( - f"{env.get('GAZEBO_RESOURCE_PATH', '')}:{':'.join(resource_folders)}" - ) - env["GAZEBO_MODEL_PATH"] = ( - f"{env.get('GAZEBO_MODEL_PATH', '')}:{':'.join(model_folders)}" - ) - env["GAZEBO_PLUGIN_PATH"] = ( - f"{env.get('GAZEBO_PLUGIN_PATH', '')}:{':'.join(plugin_folders)}" - ) - - parameters = " ".join(self.parameters) - command = f"{self.ros_command_line} {parameters} {launch_file}" - self.process = subprocess.Popen( - command, - env=env, - shell=True, - # stdin=subprocess.PIPE, - # stdout=subprocess.PIPE, - # stderr=subprocess.STDOUT - ) - # print(self.process.communicate()) - except Exception as ex: - traceback.print_exc() - raise ex - - def is_running(self): - return True if self.process.poll() is None else False - - def terminate(self): - if self.is_running(): - self.process.terminate() - else: - raise LauncherException("The process is not running") diff --git a/manager/manager/launcher/launcher_ros_api.py b/manager/manager/launcher/launcher_ros_api.py deleted file mode 100644 index b133936..0000000 --- a/manager/manager/launcher/launcher_ros_api.py +++ /dev/null @@ -1,82 +0,0 @@ -import os -import time -from typing import List, Any -from manager.manager.docker_thread.docker_thread import DockerThread -from manager.libs.process_utils import wait_for_xserver -from manager.libs.process_utils import wait_for_process_to_start -import roslaunch -import rospy - - -from manager.manager.launcher.launcher_interface import ILauncher, LauncherException - -import logging - - -class RosProcessListener(roslaunch.pmon.ProcessListener): - def __init__(self, *args, **kwargs): - self.callback = kwargs.get("callback", None) - - def process_died(self, name, exit_code): - print(f"ROS process {name} terminated with code {exit_code}") - if self.callback is not None: - self.callback(name, exit_code) - - -class LauncherRosApi(ILauncher): - type: str - module: str - launch_file: str - threads: List[Any] = [] - - # holder for roslaunch process - launch: Any = None - listener: Any = None - - def run(self, callback: callable = None): - logging.getLogger("roslaunch").setLevel(logging.CRITICAL) - - # Start X server in display - xserver_cmd = f"/usr/bin/Xorg -quiet -noreset +extension GLX +extension RANDR +extension RENDER -logfile ./xdummy.log -config ./xorg.conf :0" - xserver_thread = DockerThread(xserver_cmd) - xserver_thread.start() - wait_for_xserver(":0") - self.threads.append(xserver_thread) - - self.listener = RosProcessListener(callback=callback) - uuid = roslaunch.rlutil.get_or_generate_uuid(None, False) - roslaunch.configure_logging(uuid) - self.launch = roslaunch.parent.ROSLaunchParent( - uuid, [self.launch_file], process_listeners=[self.listener] - ) - self.launch.start() - - wait_for_process_to_start("rosmaster", timeout=60) - wait_for_process_to_start("gzserver", timeout=60) - - if not self.launch.pm.is_alive(): - raise LauncherException("Exception launching ROS") - - def is_running(self): - return self.launch.pm.is_alive() - - def wait_for_shutdown(self, timeout=30): - print("Waiting for ROS and Gazebo to shutdown") - start_time = rospy.Time.now().to_sec() - while not rospy.is_shutdown() and self.is_running(): - if rospy.Time.now().to_sec() - start_time > timeout: - print("Timeout while waiting for ROS and Gazebo to shutdown") - break - rospy.sleep(0.5) - - def terminate(self): - try: - for thread in self.threads: - if thread.is_alive(): - thread.terminate() - thread.join() - self.threads.remove(thread) - self.launch.shutdown() - self.wait_for_shutdown() - except Exception as e: - print("Exception shutting down ROS") diff --git a/manager/manager/launcher/launcher_teleoperator_ros2.py b/manager/manager/launcher/launcher_teleoperator_ros2.py deleted file mode 100644 index 409ad21..0000000 --- a/manager/manager/launcher/launcher_teleoperator_ros2.py +++ /dev/null @@ -1,39 +0,0 @@ -from manager.manager.launcher.launcher_interface import ILauncher -from manager.manager.docker_thread.docker_thread import DockerThread -import time -import os -import stat - - -class LauncherTeleoperatorRos2(ILauncher): - running = False - threads = [] - - def run(self, callback): - DRI_PATH = self.get_dri_path() - ACCELERATION_ENABLED = self.check_device(DRI_PATH) - - if ACCELERATION_ENABLED: - teleop_cmd = f"export VGL_DISPLAY={DRI_PATH}; vglrun python3 /opt/jderobot/utils/model_teleoperator.py 0.0.0.0" - else: - teleop_cmd = f"python3 /opt/jderobot/utils/model_teleoperator.py 0.0.0.0" - - teleop_thread = DockerThread(teleop_cmd) - teleop_thread.start() - self.threads.append(teleop_thread) - - self.running = True - - def is_running(self): - return self.running - - def terminate(self): - for thread in self.threads: - if thread.is_alive(): - thread.terminate() - thread.join() - self.threads.remove(thread) - self.running = False - - def died(self): - pass diff --git a/manager/manager/lint/__init__.py b/manager/manager/lint/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/manager/ram_logging/__init__.py b/manager/ram_logging/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..c245866 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,36 @@ +[project] +name = "robotics_application_manager" +version = "0.0.3" +authors = [ + { name="Example Author", email="author@example.com" }, +] +description = "Robotics Application Manager" +readme = "README.md" +requires-python = ">=3.9" +classifiers = [ + "Programming Language :: Python :: 3", + "Operating System :: OS Independent", +] +license = "GPL-3.0-only" +license-files = ["LICENSE*"] +dependencies = [ + "pydantic==2.4.2", + "transitions==0.9.0", + "pylint==3.3.1", + "websocket-client==1.5.2", + "argparse==1.4.0", + "six==1.16.0", + "psutil==5.9.0", + "watchdog==2.1.5", + "jedi", + "black==24.10.0", + "websocket_server==0.6.4" +] + +[project.urls] +Homepage = "https://github.com/JdeRobot/RoboticsApplicationManager" +Issues = "https://github.com/JdeRobot/RoboticsApplicationManager/issues" + +[build-system] +requires = ["setuptools >= 82.0.0"] +build-backend = "setuptools.build_meta" \ No newline at end of file diff --git a/robotics_application_manager.egg-info/PKG-INFO b/robotics_application_manager.egg-info/PKG-INFO new file mode 100644 index 0000000..e820770 --- /dev/null +++ b/robotics_application_manager.egg-info/PKG-INFO @@ -0,0 +1,169 @@ +Metadata-Version: 2.4 +Name: robotics_application_manager +Version: 0.0.3 +Summary: Robotics Application Manager +Author-email: Example Author +License-Expression: GPL-3.0-only +Project-URL: Homepage, https://github.com/JdeRobot/RoboticsApplicationManager +Project-URL: Issues, https://github.com/JdeRobot/RoboticsApplicationManager/issues +Classifier: Programming Language :: Python :: 3 +Classifier: Operating System :: OS Independent +Requires-Python: >=3.9 +Description-Content-Type: text/markdown +License-File: LICENSE +Requires-Dist: pydantic==2.4.2 +Requires-Dist: transitions==0.9.0 +Requires-Dist: pylint==3.3.1 +Requires-Dist: websocket-client==1.5.2 +Requires-Dist: argparse==1.4.0 +Requires-Dist: six==1.16.0 +Requires-Dist: psutil==5.9.0 +Requires-Dist: watchdog==2.1.5 +Requires-Dist: jedi +Requires-Dist: black==24.10.0 +Requires-Dist: websocket_server==0.6.4 +Dynamic: license-file + +# Robotics Application Manager (RAM) Documentation + +## Table of Contents + +1. [Project Overview](#project-overview) +2. [Main Class: `Manager`](#main-class-manager) + - [Purpose and Functionality](#purpose-and-functionality) + - [States and Transitions](#states-and-transitions) + - [Key Methods](#key-methods) + - [Interactions with Other Components](#interactions-with-other-components) +3. [Usage Examples](#usage-examples) + +## Project Overview + +The Robotics Application Manager (RAM) is an advanced manager for executing robotic applications. It operates as a state machine, managing the lifecycle of robotic applications from initialization to termination and uses the following ports to communicate: + +- **7063**: Connexion with other applications (Robotics Academy, BT Studio, Unibotics) +- **6080-6090**: Tools VNC + +## Main Class: `Manager` + +### Purpose and Functionality + +The `Manager` class is the core of RAM, orchestrating operations and managing transitions between various application states. + +### States and Transitions + +- **States**: + - `idle`: The initial state, waiting for a connection. + - `connected`: Connected and ready to initiate processes. + - `world_ready`: The world environment is set up and ready. + - `tools_ready`: Tools are prepared and ready. + - `application_running`: A robotic application is actively running. + - `paused`: The application is paused. +- **Transitions**: + - `connect`: Moves from `idle` to `connected`. + - `launch_world`: Initiates the world setup from `connected`. + - `prepare_tools`: Prepares the tools in `world_ready`. + - `run_application`: Starts the application in `tools_ready` or `paused`. + - `pause`: Pauses the running application. + - `resume`: Resumes a paused application. + - `terminate`: Stops the application and goes back to `tools_ready`. + - `stop`: Completely stops the application. + - `disconnect`: Disconnects from the current session and returns to `idle`. +- **Stateless Transitions**: + - `gui`: Redirects content to the gui webserver. + - `style_check`: Triggers on_style_check. + - `code_analysis`: Triggers on_code_analysis. + - `code_format`: Triggers on_code_format. + - `code_autocomplete`: Triggers on_code_autocomplete. + +### Key Methods + +- `on_connect(self, event)`: Manages the transition to the 'connected' state. +- `on_launch_world(self, event)`: Prepares and launches the robotic world. +- `on_prepare_tools(self, event)`: Sets up tools. +- `on_run_application(self, event)`: Executes the robotic application. +- `on_pause(self, msg)`: Pauses the running application. +- `on_resume(self, msg)`: Resumes the paused application. +- `on_terminate(self, event)`: Terminates the running application. +- `on_disconnect(self, event)`: Handles disconnection and cleanup. +- `on_style_check(self, event)`: Check the style of the user code. +- `on_code_analysis(self, event)`: Analyzes the style and format of the user code using pylint. +- `on_code_format(self, event)`: Formats the user code using black. +- `on_code_autocomplete(self, event)`: Searches for all available code completions using Jedi. +- **Exception Handling**: Details how specific errors are managed in each method. + +### Interactions with Other Components + +#### Interaction Between `Manager` and `ManagerConsumer` + +1. **Message Queue Integration**: `ManagerConsumer` puts received messages into `manager_queue` for `Manager` to process. +2. **State Updates and Commands**: `Manager` sends state updates or commands to the client through `ManagerConsumer`. +3. **Client Connection Handling**: `Manager` relies on `ManagerConsumer` for client connection and disconnection handling. +4. **Error Handling**: `ManagerConsumer` communicates exceptions back to the client and `Manager`. +5. **Lifecycle Management**: `Manager` controls the start and stop of the `ManagerConsumer` WebSocket server. + +#### Interaction Between `Manager` and `LauncherWorld` + +1. **World Initialization and Launching**: `Manager` initializes `LauncherWorld` with specific configurations, such as world type (e.g., `gazebo`, `drones`) and the launch file path. +2. **Dynamic Module Management**: `LauncherWorld` dynamically launches modules based on the world configuration and ROS version, as dictated by `Manager`. +3. **State Management and Transition**: The state of `Manager` is updated in response to the actions performed by `LauncherWorld`. For example, once the world is ready, `Manager` may transition to the `world_ready` state. +4. **Termination and Cleanup**: `Manager` can instruct `LauncherWorld` to terminate the world environment through its `terminate` method. `LauncherWorld` ensures a clean and orderly shutdown of all modules and resources involved in the world setup. +5. **Error Handling and Logging**: `Manager` handles exceptions and errors that may arise during the world setup or termination processes, ensuring robust operation. + +#### Interaction Between `Manager` and `LauncherTools` + +1. **Visualization Setup**: `Manager` initializes `LauncherTools` with a specific tools configuration, which can include tools like `console`, `simulator`, `web_gui`, etc. +2. **Module Launching for Tools**: `LauncherTools` dynamically launches tools modules based on the configuration provided by `Manager`. +3. **State Management and Synchronization**: Upon successful setup of the tools, `Manager` can update its state (e.g., to `tools_ready`) to reflect the readiness of the tools. +4. **Termination of Tools**: `Manager` can instruct `LauncherTools` to terminate the current tools setup using its `terminate` method. +5. **Error Handling and Logging**: `Manager` is equipped to manage exceptions and errors that might occur during the setup or termination of the tools. + +#### Interaction Between `Manager` and `application_process` + +1. **Application Execution**: `Manager` initiates the `application_process` when transitioning to the `application_running` state. +2. **Application Configuration and Launching**: Before launching the `application_process`, `Manager` configures the necessary parameters. +3. **Process Management**: `Manager` monitors and controls the `application_process`. +4. **Error Handling and Logging**: `Manager` is responsible for handling any errors or exceptions that occur during the execution of the `application_process`. +5. **State Synchronization**: The state of the `application_process` is closely synchronized with the state machine in `Manager`. + +#### Interaction Between `Manager` and `Server` (Specific to RoboticsAcademy Applications) (Now inside tool web_gui) + +1. **Dedicated WebSocket Server for GUI Updates**: `Server` is used exclusively for RoboticsAcademy applications that require real-time interaction with a web-based GUI. +2. **Client Communication for GUI Module**: For RoboticsAcademy applications with a GUI module, `Server` handles incoming and outgoing messages. +3. **Real-time Interaction and Feedback**: `Server` allows for real-time feedback and interaction within the browser-based GUI. +4. **Conditional Operation Based on Application Type**: `Manager` initializes and controls `Server` based on the specific needs of the RoboticsAcademy application being executed. +5. **Error Handling and Logging**: `Manager` ensures robust error handling for `Server`. + +## Usage Example + +1. **Connecting to RAM**: + + - Initially, the RAM is in the `idle` state. + - A client (e.g., a user interface or another system) connects to RAM, triggering the `connect` transition and moving RAM to the `connected` state. + +2. **Launching the World**: + + - Once connected, the client can request RAM to launch a robotic world by sending a `launch_world` command. + - RAM transitions to the `world_ready` state after successfully setting up the world environment. + +3. **Setting Up Tools**: + + - After the world is ready, the client requests RAM to prepare the tools with a `prepare_tools` command. + - RAM transitions to the `tools_ready` state, indicating that the tools are set up and ready. + +4. **Running an Application**: + + - The client then requests RAM to run a specific robotic application, moving RAM into the `application_running` state. + - The application executes, and RAM handles its process management, including monitoring and error handling. + +5. **Pausing and Resuming Application**: + + - The client can send `pause` and `resume` commands to RAM to control the application's execution. + - RAM transitions to the `paused` state when paused and returns to `application_running` upon resumption. + +6. **Stopping the Application**: + + - Finally, the client can send a `stop` command to halt the application. + - RAM stops the application and transitions back to the `tools_ready` state, ready for new commands. + +7. **Disconnecting**: + - Once all tasks are completed, the client can disconnect from RAM, which then returns to the `idle` state, ready for a new session. diff --git a/robotics_application_manager.egg-info/SOURCES.txt b/robotics_application_manager.egg-info/SOURCES.txt new file mode 100644 index 0000000..7b77dff --- /dev/null +++ b/robotics_application_manager.egg-info/SOURCES.txt @@ -0,0 +1,55 @@ +LICENSE +README.md +pyproject.toml +robotics_application_manager/__init__.py +robotics_application_manager.egg-info/PKG-INFO +robotics_application_manager.egg-info/SOURCES.txt +robotics_application_manager.egg-info/dependency_links.txt +robotics_application_manager.egg-info/requires.txt +robotics_application_manager.egg-info/top_level.txt +robotics_application_manager/comms/__init__.py +robotics_application_manager/comms/consumer_message.py +robotics_application_manager/comms/new_consumer.py +robotics_application_manager/comms/thread.py +robotics_application_manager/comms/websocket_server.py +robotics_application_manager/libs/__init__.py +robotics_application_manager/libs/file_watchdog.py +robotics_application_manager/libs/launch_world_model.py +robotics_application_manager/libs/process_utils.py +robotics_application_manager/libs/server.py +robotics_application_manager/libs/singleton.py +robotics_application_manager/manager/__init__.py +robotics_application_manager/manager/manager.py +robotics_application_manager/manager/docker_thread/__init__.py +robotics_application_manager/manager/docker_thread/docker_thread.py +robotics_application_manager/manager/editor/serializers.py +robotics_application_manager/manager/launcher/__init__.py +robotics_application_manager/manager/launcher/launcher_console.py +robotics_application_manager/manager/launcher/launcher_gazebo.py +robotics_application_manager/manager/launcher/launcher_gzsim.py +robotics_application_manager/manager/launcher/launcher_interface.py +robotics_application_manager/manager/launcher/launcher_o3de.py +robotics_application_manager/manager/launcher/launcher_o3de_api.py +robotics_application_manager/manager/launcher/launcher_robot.py +robotics_application_manager/manager/launcher/launcher_robot_ros2_api.py +robotics_application_manager/manager/launcher/launcher_ros2_api.py +robotics_application_manager/manager/launcher/launcher_rviz.py +robotics_application_manager/manager/launcher/launcher_rviz_ros2.py +robotics_application_manager/manager/launcher/launcher_state_monitor.py +robotics_application_manager/manager/launcher/launcher_tools.py +robotics_application_manager/manager/launcher/launcher_web_gui.py +robotics_application_manager/manager/launcher/launcher_world.py +robotics_application_manager/manager/lint/__init__.py +robotics_application_manager/manager/lint/linter.py +robotics_application_manager/manager/lint/pylint_checker.py +robotics_application_manager/manager/lint/pylint_checker_style.py +robotics_application_manager/manager/vnc/vnc_server.py +robotics_application_manager/ram_logging/__init__.py +robotics_application_manager/ram_logging/log_manager.py +test/test_connect_disconnect_transitions.py +test/test_connected_to_world_ready.py +test/test_resume_and_pause_transitions.py +test/test_terminate_transitions.py +test/test_tools_ready_to_application_running.py +test/test_utils.py +test/test_world_ready_to_tools_ready.py \ No newline at end of file diff --git a/manager/__init__.py b/robotics_application_manager.egg-info/dependency_links.txt similarity index 100% rename from manager/__init__.py rename to robotics_application_manager.egg-info/dependency_links.txt diff --git a/robotics_application_manager.egg-info/requires.txt b/robotics_application_manager.egg-info/requires.txt new file mode 100644 index 0000000..6a7cf61 --- /dev/null +++ b/robotics_application_manager.egg-info/requires.txt @@ -0,0 +1,11 @@ +pydantic==2.4.2 +transitions==0.9.0 +pylint==3.3.1 +websocket-client==1.5.2 +argparse==1.4.0 +six==1.16.0 +psutil==5.9.0 +watchdog==2.1.5 +jedi +black==24.10.0 +websocket_server==0.6.4 diff --git a/robotics_application_manager.egg-info/top_level.txt b/robotics_application_manager.egg-info/top_level.txt new file mode 100644 index 0000000..4835f1a --- /dev/null +++ b/robotics_application_manager.egg-info/top_level.txt @@ -0,0 +1 @@ +robotics_application_manager diff --git a/robotics_application_manager/__init__.py b/robotics_application_manager/__init__.py new file mode 100644 index 0000000..20532d0 --- /dev/null +++ b/robotics_application_manager/__init__.py @@ -0,0 +1,2 @@ +from .manager.manager import Manager +from .ram_logging.log_manager import LogManager diff --git a/manager/comms/__init__.py b/robotics_application_manager/comms/__init__.py similarity index 100% rename from manager/comms/__init__.py rename to robotics_application_manager/comms/__init__.py diff --git a/manager/comms/consumer_message.py b/robotics_application_manager/comms/consumer_message.py similarity index 100% rename from manager/comms/consumer_message.py rename to robotics_application_manager/comms/consumer_message.py diff --git a/manager/comms/new_consumer.py b/robotics_application_manager/comms/new_consumer.py similarity index 94% rename from manager/comms/new_consumer.py rename to robotics_application_manager/comms/new_consumer.py index 7c04b84..5bdf3be 100644 --- a/manager/comms/new_consumer.py +++ b/robotics_application_manager/comms/new_consumer.py @@ -18,16 +18,6 @@ from manager.ram_logging.log_manager import LogManager -class Client: - """Represents a client connected to the WebSocket server.""" - - def __init__(self, **kwargs): - """Initialize a Client instance with id, handler, and address.""" - self.id = kwargs["id"] - self.handler = kwargs["handler"] - self.address = kwargs["address"] - - class ManagerConsumer: """ Websocket server consumer for new Robotics Application Manager aka: RAM. diff --git a/manager/comms/thread.py b/robotics_application_manager/comms/thread.py similarity index 100% rename from manager/comms/thread.py rename to robotics_application_manager/comms/thread.py diff --git a/manager/comms/websocket_server.py b/robotics_application_manager/comms/websocket_server.py similarity index 100% rename from manager/comms/websocket_server.py rename to robotics_application_manager/comms/websocket_server.py diff --git a/manager/libs/__init__.py b/robotics_application_manager/libs/__init__.py similarity index 100% rename from manager/libs/__init__.py rename to robotics_application_manager/libs/__init__.py diff --git a/manager/libs/applications/compatibility/file_watchdog.py b/robotics_application_manager/libs/file_watchdog.py similarity index 100% rename from manager/libs/applications/compatibility/file_watchdog.py rename to robotics_application_manager/libs/file_watchdog.py diff --git a/manager/libs/launch_world_model.py b/robotics_application_manager/libs/launch_world_model.py similarity index 100% rename from manager/libs/launch_world_model.py rename to robotics_application_manager/libs/launch_world_model.py diff --git a/manager/libs/process_utils.py b/robotics_application_manager/libs/process_utils.py similarity index 100% rename from manager/libs/process_utils.py rename to robotics_application_manager/libs/process_utils.py diff --git a/manager/libs/applications/compatibility/server.py b/robotics_application_manager/libs/server.py similarity index 100% rename from manager/libs/applications/compatibility/server.py rename to robotics_application_manager/libs/server.py diff --git a/manager/libs/singleton.py b/robotics_application_manager/libs/singleton.py similarity index 100% rename from manager/libs/singleton.py rename to robotics_application_manager/libs/singleton.py diff --git a/manager/libs/applications/__init__.py b/robotics_application_manager/manager/__init__.py similarity index 100% rename from manager/libs/applications/__init__.py rename to robotics_application_manager/manager/__init__.py diff --git a/manager/libs/applications/compatibility/__init__.py b/robotics_application_manager/manager/docker_thread/__init__.py similarity index 100% rename from manager/libs/applications/compatibility/__init__.py rename to robotics_application_manager/manager/docker_thread/__init__.py diff --git a/manager/manager/docker_thread/docker_thread.py b/robotics_application_manager/manager/docker_thread/docker_thread.py similarity index 100% rename from manager/manager/docker_thread/docker_thread.py rename to robotics_application_manager/manager/docker_thread/docker_thread.py diff --git a/manager/manager/editor/serializers.py b/robotics_application_manager/manager/editor/serializers.py similarity index 100% rename from manager/manager/editor/serializers.py rename to robotics_application_manager/manager/editor/serializers.py diff --git a/manager/manager/__init__.py b/robotics_application_manager/manager/launcher/__init__.py similarity index 100% rename from manager/manager/__init__.py rename to robotics_application_manager/manager/launcher/__init__.py diff --git a/manager/manager/launcher/launcher_console.py b/robotics_application_manager/manager/launcher/launcher_console.py similarity index 100% rename from manager/manager/launcher/launcher_console.py rename to robotics_application_manager/manager/launcher/launcher_console.py diff --git a/manager/manager/launcher/launcher_gazebo.py b/robotics_application_manager/manager/launcher/launcher_gazebo.py similarity index 100% rename from manager/manager/launcher/launcher_gazebo.py rename to robotics_application_manager/manager/launcher/launcher_gazebo.py diff --git a/manager/manager/launcher/launcher_gzsim.py b/robotics_application_manager/manager/launcher/launcher_gzsim.py similarity index 100% rename from manager/manager/launcher/launcher_gzsim.py rename to robotics_application_manager/manager/launcher/launcher_gzsim.py diff --git a/manager/manager/launcher/launcher_interface.py b/robotics_application_manager/manager/launcher/launcher_interface.py similarity index 100% rename from manager/manager/launcher/launcher_interface.py rename to robotics_application_manager/manager/launcher/launcher_interface.py diff --git a/manager/manager/launcher/launcher_o3de.py b/robotics_application_manager/manager/launcher/launcher_o3de.py similarity index 100% rename from manager/manager/launcher/launcher_o3de.py rename to robotics_application_manager/manager/launcher/launcher_o3de.py diff --git a/manager/manager/launcher/launcher_o3de_api.py b/robotics_application_manager/manager/launcher/launcher_o3de_api.py similarity index 100% rename from manager/manager/launcher/launcher_o3de_api.py rename to robotics_application_manager/manager/launcher/launcher_o3de_api.py diff --git a/manager/manager/launcher/launcher_robot.py b/robotics_application_manager/manager/launcher/launcher_robot.py similarity index 100% rename from manager/manager/launcher/launcher_robot.py rename to robotics_application_manager/manager/launcher/launcher_robot.py diff --git a/manager/manager/launcher/launcher_robot_ros2_api.py b/robotics_application_manager/manager/launcher/launcher_robot_ros2_api.py similarity index 100% rename from manager/manager/launcher/launcher_robot_ros2_api.py rename to robotics_application_manager/manager/launcher/launcher_robot_ros2_api.py diff --git a/manager/manager/launcher/launcher_ros2_api.py b/robotics_application_manager/manager/launcher/launcher_ros2_api.py similarity index 100% rename from manager/manager/launcher/launcher_ros2_api.py rename to robotics_application_manager/manager/launcher/launcher_ros2_api.py diff --git a/manager/manager/launcher/launcher_rviz.py b/robotics_application_manager/manager/launcher/launcher_rviz.py similarity index 100% rename from manager/manager/launcher/launcher_rviz.py rename to robotics_application_manager/manager/launcher/launcher_rviz.py diff --git a/manager/manager/launcher/launcher_rviz_ros2.py b/robotics_application_manager/manager/launcher/launcher_rviz_ros2.py similarity index 100% rename from manager/manager/launcher/launcher_rviz_ros2.py rename to robotics_application_manager/manager/launcher/launcher_rviz_ros2.py diff --git a/manager/manager/launcher/launcher_state_monitor.py b/robotics_application_manager/manager/launcher/launcher_state_monitor.py similarity index 88% rename from manager/manager/launcher/launcher_state_monitor.py rename to robotics_application_manager/manager/launcher/launcher_state_monitor.py index 8e645b5..c2b9446 100644 --- a/manager/manager/launcher/launcher_state_monitor.py +++ b/robotics_application_manager/manager/launcher/launcher_state_monitor.py @@ -1,8 +1,8 @@ -from manager.libs.applications.compatibility.server import Server +from manager.libs.server import Server from manager.ram_logging.log_manager import LogManager from manager.comms.new_consumer import ManagerConsumer from typing import Optional -from manager.libs.applications.compatibility.file_watchdog import FileWatchdog +from manager.libs.file_watchdog import FileWatchdog class LauncherStateMonitor: diff --git a/manager/manager/launcher/launcher_tools.py b/robotics_application_manager/manager/launcher/launcher_tools.py similarity index 100% rename from manager/manager/launcher/launcher_tools.py rename to robotics_application_manager/manager/launcher/launcher_tools.py diff --git a/manager/manager/launcher/launcher_web_gui.py b/robotics_application_manager/manager/launcher/launcher_web_gui.py similarity index 94% rename from manager/manager/launcher/launcher_web_gui.py rename to robotics_application_manager/manager/launcher/launcher_web_gui.py index 6d5c9e3..367e003 100644 --- a/manager/manager/launcher/launcher_web_gui.py +++ b/robotics_application_manager/manager/launcher/launcher_web_gui.py @@ -1,4 +1,4 @@ -from manager.libs.applications.compatibility.server import Server +from manager.libs.server import Server from manager.ram_logging.log_manager import LogManager from manager.comms.new_consumer import ManagerConsumer from typing import Optional diff --git a/manager/manager/launcher/launcher_world.py b/robotics_application_manager/manager/launcher/launcher_world.py similarity index 100% rename from manager/manager/launcher/launcher_world.py rename to robotics_application_manager/manager/launcher/launcher_world.py diff --git a/manager/manager/application/__init__.py b/robotics_application_manager/manager/lint/__init__.py similarity index 100% rename from manager/manager/application/__init__.py rename to robotics_application_manager/manager/lint/__init__.py diff --git a/manager/manager/lint/linter.py b/robotics_application_manager/manager/lint/linter.py similarity index 100% rename from manager/manager/lint/linter.py rename to robotics_application_manager/manager/lint/linter.py diff --git a/manager/manager/lint/pylint_checker.py b/robotics_application_manager/manager/lint/pylint_checker.py similarity index 100% rename from manager/manager/lint/pylint_checker.py rename to robotics_application_manager/manager/lint/pylint_checker.py diff --git a/manager/manager/lint/pylint_checker_style.py b/robotics_application_manager/manager/lint/pylint_checker_style.py similarity index 100% rename from manager/manager/lint/pylint_checker_style.py rename to robotics_application_manager/manager/lint/pylint_checker_style.py diff --git a/manager/manager/lint/pylintrc b/robotics_application_manager/manager/lint/pylintrc similarity index 100% rename from manager/manager/lint/pylintrc rename to robotics_application_manager/manager/lint/pylintrc diff --git a/manager/manager/manager.py b/robotics_application_manager/manager/manager.py similarity index 98% rename from manager/manager/manager.py rename to robotics_application_manager/manager/manager.py index be85314..1fef216 100644 --- a/manager/manager/manager.py +++ b/robotics_application_manager/manager/manager.py @@ -35,16 +35,16 @@ from manager.comms.consumer_message import ManagerConsumerMessageException from manager.comms.new_consumer import ManagerConsumer -from manager.libs.process_utils import check_gpu_acceleration, get_class_from_file +from manager.libs.process_utils import ( + check_gpu_acceleration, + get_class_from_file, + stop_process_and_children, +) from manager.libs.launch_world_model import ConfigurationManager +from manager.ram_logging.log_manager import LogManager from manager.manager.launcher.launcher_world import LauncherWorld from manager.manager.launcher.launcher_robot import LauncherRobot from manager.manager.launcher.launcher_tools import LauncherTools -from manager.ram_logging.log_manager import LogManager -from manager.manager.application.robotics_python_application_interface import ( - IRoboticsPythonApplication, -) -from manager.libs.process_utils import stop_process_and_children from manager.manager.lint.linter import Lint from manager.manager.editor.serializers import serialize_completions @@ -442,6 +442,8 @@ def on_style_check_application(self, event): Raises: Exception: with the errors found in the linter """ + # TODO: redo + # Extract app config app_cfg = event.kwargs.get("data", {}) try: @@ -962,17 +964,3 @@ def signal_handler(sign, frame): ) self.consumer.send_message(ex) LogManager.logger.error(e, exc_info=True) - - -if __name__ == "__main__": - import argparse - - parser = argparse.ArgumentParser() - parser.add_argument( - "host", type=str, help="Host to listen to (0.0.0.0 or all hosts)" - ) - parser.add_argument("port", type=int, help="Port to listen to") - args = parser.parse_args() - - RAM = Manager(args.host, args.port) - RAM.start() diff --git a/manager/manager/vnc/vnc_server.py b/robotics_application_manager/manager/vnc/vnc_server.py similarity index 100% rename from manager/manager/vnc/vnc_server.py rename to robotics_application_manager/manager/vnc/vnc_server.py diff --git a/manager/manager/docker_thread/__init__.py b/robotics_application_manager/ram_logging/__init__.py similarity index 100% rename from manager/manager/docker_thread/__init__.py rename to robotics_application_manager/ram_logging/__init__.py diff --git a/manager/ram_logging/log_manager.py b/robotics_application_manager/ram_logging/log_manager.py similarity index 100% rename from manager/ram_logging/log_manager.py rename to robotics_application_manager/ram_logging/log_manager.py From e190703789704962f99734660a9d9b817c076683 Mon Sep 17 00:00:00 2001 From: Javier Izquierdo Hernandez Date: Wed, 18 Feb 2026 12:31:23 +0100 Subject: [PATCH 10/15] Fix links --- launch.py | 2 +- robotics_application_manager/__init__.py | 3 +-- .../comms/__init__.py | 4 ++++ .../comms/new_consumer.py | 6 ++--- .../comms/websocket_server.py | 2 +- robotics_application_manager/libs/__init__.py | 14 +++++++++++ .../libs/file_watchdog.py | 2 +- .../libs/process_utils.py | 2 +- robotics_application_manager/libs/server.py | 2 +- .../libs/singleton.py | 9 ------- .../manager/docker_thread/__init__.py | 1 + .../manager/editor/__init__.py | 1 + .../manager/launcher/__init__.py | 3 +++ .../manager/launcher/launcher_console.py | 8 +++---- .../manager/launcher/launcher_gazebo.py | 10 ++++---- .../manager/launcher/launcher_gzsim.py | 10 ++++---- .../manager/launcher/launcher_o3de.py | 13 +++++----- .../manager/launcher/launcher_o3de_api.py | 24 +++++++++++-------- .../manager/launcher/launcher_robot.py | 10 +++++--- .../launcher/launcher_robot_ros2_api.py | 7 ++++-- .../manager/launcher/launcher_ros2_api.py | 7 ++++-- .../manager/launcher/launcher_rviz.py | 17 +++++++------ .../manager/launcher/launcher_rviz_ros2.py | 6 ++--- .../launcher/launcher_state_monitor.py | 7 +++--- .../manager/launcher/launcher_tools.py | 7 +++--- .../manager/launcher/launcher_web_gui.py | 6 ++--- .../manager/launcher/launcher_world.py | 10 +++++--- .../manager/lint/__init__.py | 1 + .../manager/manager.py | 24 +++++++++++-------- .../manager/vnc/__init__.py | 1 + .../manager/vnc/vnc_server.py | 12 +++++----- .../ram_logging/__init__.py | 1 + .../ram_logging/log_manager.py | 11 ++++++++- 33 files changed, 144 insertions(+), 99 deletions(-) delete mode 100644 robotics_application_manager/libs/singleton.py create mode 100644 robotics_application_manager/manager/editor/__init__.py create mode 100644 robotics_application_manager/manager/vnc/__init__.py diff --git a/launch.py b/launch.py index d5c5604..c9e0f79 100644 --- a/launch.py +++ b/launch.py @@ -1,4 +1,4 @@ -from .robotics_application_manager import Manager +from robotics_application_manager.manager.manager import Manager if __name__ == "__main__": import argparse diff --git a/robotics_application_manager/__init__.py b/robotics_application_manager/__init__.py index 20532d0..bbb5a12 100644 --- a/robotics_application_manager/__init__.py +++ b/robotics_application_manager/__init__.py @@ -1,2 +1 @@ -from .manager.manager import Manager -from .ram_logging.log_manager import LogManager +from robotics_application_manager.ram_logging.log_manager import LogManager diff --git a/robotics_application_manager/comms/__init__.py b/robotics_application_manager/comms/__init__.py index e69de29..6671462 100644 --- a/robotics_application_manager/comms/__init__.py +++ b/robotics_application_manager/comms/__init__.py @@ -0,0 +1,4 @@ +from .new_consumer import ManagerConsumer +from .consumer_message import ManagerConsumerMessageException, ManagerConsumerMessage +from .thread import ThreadWithLoggedException, WebsocketServerThread +from .websocket_server import WebsocketServer diff --git a/robotics_application_manager/comms/new_consumer.py b/robotics_application_manager/comms/new_consumer.py index 5bdf3be..0f2568c 100644 --- a/robotics_application_manager/comms/new_consumer.py +++ b/robotics_application_manager/comms/new_consumer.py @@ -10,12 +10,12 @@ from uuid import uuid4 from datetime import datetime -from manager.comms.consumer_message import ( +from .consumer_message import ( ManagerConsumerMessageException, ManagerConsumerMessage, ) -from manager.comms.websocket_server import WebsocketServer -from manager.ram_logging.log_manager import LogManager +from .websocket_server import WebsocketServer +from robotics_application_manager import LogManager class ManagerConsumer: diff --git a/robotics_application_manager/comms/websocket_server.py b/robotics_application_manager/comms/websocket_server.py index 70479bd..0a67118 100644 --- a/robotics_application_manager/comms/websocket_server.py +++ b/robotics_application_manager/comms/websocket_server.py @@ -14,7 +14,7 @@ import threading from socketserver import ThreadingMixIn, TCPServer, StreamRequestHandler -from manager.comms.thread import WebsocketServerThread +from .thread import WebsocketServerThread logger = logging.getLogger(__name__) logging.basicConfig() diff --git a/robotics_application_manager/libs/__init__.py b/robotics_application_manager/libs/__init__.py index e69de29..9dd88f8 100644 --- a/robotics_application_manager/libs/__init__.py +++ b/robotics_application_manager/libs/__init__.py @@ -0,0 +1,14 @@ +from .file_watchdog import FileWatchdog +from .launch_world_model import ConfigurationModel, ConfigurationManager +from .process_utils import ( + get_ros_version, + check_gpu_acceleration, + get_user_world, + wait_for_xserver, + wait_for_process_to_start, + stop_process_and_children, + class_from_module, + get_class_from_file, + get_class, +) +from .server import Server diff --git a/robotics_application_manager/libs/file_watchdog.py b/robotics_application_manager/libs/file_watchdog.py index 7cc9231..6545f49 100644 --- a/robotics_application_manager/libs/file_watchdog.py +++ b/robotics_application_manager/libs/file_watchdog.py @@ -4,7 +4,7 @@ from watchdog.events import FileSystemEventHandler import watchdog.observers -from manager.ram_logging.log_manager import LogManager +from robotics_application_manager import LogManager class Handler(FileSystemEventHandler): diff --git a/robotics_application_manager/libs/process_utils.py b/robotics_application_manager/libs/process_utils.py index b83a28e..14c28c9 100644 --- a/robotics_application_manager/libs/process_utils.py +++ b/robotics_application_manager/libs/process_utils.py @@ -11,7 +11,7 @@ import psutil -from manager.ram_logging.log_manager import LogManager +from robotics_application_manager import LogManager def get_class(kls): diff --git a/robotics_application_manager/libs/server.py b/robotics_application_manager/libs/server.py index d7b1f7c..7bb488c 100644 --- a/robotics_application_manager/libs/server.py +++ b/robotics_application_manager/libs/server.py @@ -3,7 +3,7 @@ from websocket_server import WebsocketServer -from manager.ram_logging.log_manager import LogManager +from robotics_application_manager import LogManager class Server(threading.Thread): diff --git a/robotics_application_manager/libs/singleton.py b/robotics_application_manager/libs/singleton.py deleted file mode 100644 index aceb996..0000000 --- a/robotics_application_manager/libs/singleton.py +++ /dev/null @@ -1,9 +0,0 @@ -def singleton(cls): - instances = {} - - def get_instance(): - if cls not in instances: - instances[cls] = cls() - return instances[cls] - - return get_instance() diff --git a/robotics_application_manager/manager/docker_thread/__init__.py b/robotics_application_manager/manager/docker_thread/__init__.py index e69de29..dd99779 100644 --- a/robotics_application_manager/manager/docker_thread/__init__.py +++ b/robotics_application_manager/manager/docker_thread/__init__.py @@ -0,0 +1 @@ +from .docker_thread import DockerThread diff --git a/robotics_application_manager/manager/editor/__init__.py b/robotics_application_manager/manager/editor/__init__.py new file mode 100644 index 0000000..2fcbebd --- /dev/null +++ b/robotics_application_manager/manager/editor/__init__.py @@ -0,0 +1 @@ +from .serializers import serialize_completions diff --git a/robotics_application_manager/manager/launcher/__init__.py b/robotics_application_manager/manager/launcher/__init__.py index e69de29..39ed308 100644 --- a/robotics_application_manager/manager/launcher/__init__.py +++ b/robotics_application_manager/manager/launcher/__init__.py @@ -0,0 +1,3 @@ +from .launcher_tools import LauncherTools +from .launcher_world import LauncherWorld +from .launcher_robot import LauncherRobot diff --git a/robotics_application_manager/manager/launcher/launcher_console.py b/robotics_application_manager/manager/launcher/launcher_console.py index 0611fef..af94190 100644 --- a/robotics_application_manager/manager/launcher/launcher_console.py +++ b/robotics_application_manager/manager/launcher/launcher_console.py @@ -1,7 +1,7 @@ -from manager.manager.launcher.launcher_interface import ILauncher -from manager.manager.docker_thread.docker_thread import DockerThread -from manager.manager.vnc.vnc_server import Vnc_server -from manager.libs.process_utils import check_gpu_acceleration +from .launcher_interface import ILauncher +from robotics_application_manager.manager.docker_thread import DockerThread +from robotics_application_manager.manager.vnc import Vnc_server +from robotics_application_manager.libs import check_gpu_acceleration import os import stat from typing import List, Any diff --git a/robotics_application_manager/manager/launcher/launcher_gazebo.py b/robotics_application_manager/manager/launcher/launcher_gazebo.py index f7f6333..5002cef 100644 --- a/robotics_application_manager/manager/launcher/launcher_gazebo.py +++ b/robotics_application_manager/manager/launcher/launcher_gazebo.py @@ -1,13 +1,13 @@ import sys -from manager.manager.launcher.launcher_interface import ILauncher -from manager.manager.docker_thread.docker_thread import DockerThread -from manager.manager.vnc.vnc_server import Vnc_server -from manager.libs.process_utils import ( +from .launcher_interface import ILauncher +from robotics_application_manager.manager.docker_thread import DockerThread +from robotics_application_manager.manager.vnc import Vnc_server +from robotics_application_manager.libs import ( wait_for_process_to_start, ) import subprocess from typing import List, Any -from manager.ram_logging.log_manager import LogManager +from robotics_application_manager import LogManager def call_service(service, service_type, request_data="{}"): diff --git a/robotics_application_manager/manager/launcher/launcher_gzsim.py b/robotics_application_manager/manager/launcher/launcher_gzsim.py index 053cafa..261b19a 100644 --- a/robotics_application_manager/manager/launcher/launcher_gzsim.py +++ b/robotics_application_manager/manager/launcher/launcher_gzsim.py @@ -1,8 +1,8 @@ import sys -from manager.manager.launcher.launcher_interface import ILauncher -from manager.manager.docker_thread.docker_thread import DockerThread -from manager.manager.vnc.vnc_server import Vnc_server -from manager.libs.process_utils import ( +from .launcher_interface import ILauncher +from robotics_application_manager.manager.docker_thread import DockerThread +from robotics_application_manager.manager.vnc import Vnc_server +from robotics_application_manager.libs import ( wait_for_process_to_start, check_gpu_acceleration, ) @@ -11,7 +11,7 @@ import os import stat from typing import List, Any -from manager.ram_logging.log_manager import LogManager +from robotics_application_manager import LogManager def call_gzservice(service, reqtype, reptype, timeout, req): diff --git a/robotics_application_manager/manager/launcher/launcher_o3de.py b/robotics_application_manager/manager/launcher/launcher_o3de.py index f633dc0..0bb9af1 100644 --- a/robotics_application_manager/manager/launcher/launcher_o3de.py +++ b/robotics_application_manager/manager/launcher/launcher_o3de.py @@ -1,8 +1,8 @@ import sys -from manager.manager.launcher.launcher_interface import ILauncher -from manager.manager.docker_thread.docker_thread import DockerThread -from manager.manager.vnc.vnc_server import Vnc_server -from manager.libs.process_utils import ( +from .launcher_interface import ILauncher +from robotics_application_manager.manager.docker_thread import DockerThread +from robotics_application_manager.manager.vnc import Vnc_server +from robotics_application_manager.libs import ( wait_for_process_to_start, check_gpu_acceleration, ) @@ -11,7 +11,8 @@ import os import stat from typing import List, Any -from manager.ram_logging.log_manager import LogManager +from robotics_application_manager import LogManager + class LauncherO3de(ILauncher): running: bool = False @@ -50,7 +51,7 @@ def unpause(self): pass def reset(self): - #TODO: add reset + # TODO: add reset pass def get_dri_path(self): diff --git a/robotics_application_manager/manager/launcher/launcher_o3de_api.py b/robotics_application_manager/manager/launcher/launcher_o3de_api.py index b5c8697..5824c6b 100644 --- a/robotics_application_manager/manager/launcher/launcher_o3de_api.py +++ b/robotics_application_manager/manager/launcher/launcher_o3de_api.py @@ -4,10 +4,13 @@ import time import stat -from manager.manager.launcher.launcher_interface import ILauncher, LauncherException -from manager.manager.docker_thread.docker_thread import DockerThread -from manager.manager.vnc.vnc_server import Vnc_server -from manager.libs.process_utils import ( +from .launcher_interface import ( + ILauncher, + LauncherException, +) +from robotics_application_manager.manager.docker_thread import DockerThread +from robotics_application_manager.manager.vnc import Vnc_server +from robotics_application_manager.libs import ( wait_for_process_to_start, check_gpu_acceleration, ) @@ -15,6 +18,7 @@ import logging + class LauncherO3deApi(ILauncher): display: str internal_port: int @@ -31,19 +35,19 @@ def run(self, callback): DRI_PATH = self.get_dri_path() ACCELERATION_ENABLED = self.check_device(DRI_PATH) - #TODO: add run here + # TODO: add run here xserver_cmd = f"/usr/bin/Xorg -quiet -noreset +extension GLX +extension RANDR +extension RENDER -logfile ./xdummy.log -config ./xorg.conf :0" xserver_thread = DockerThread(xserver_cmd) xserver_thread.start() self.threads.append(xserver_thread) - - LevelSelect=f'echo "LoadLevel Levels/{self.launch_file}" > data/workspace/ROS2Demo/autoexec.cfg' - + + LevelSelect = f'echo "LoadLevel Levels/{self.launch_file}" > data/workspace/ROS2Demo/autoexec.cfg' + LevelSelect_thread = DockerThread(LevelSelect) LevelSelect_thread.start() self.threads.append(LevelSelect_thread) - + if ACCELERATION_ENABLED: # Starts xserver, x11vnc and novnc self.gz_vnc.start_vnc_gpu( @@ -61,7 +65,7 @@ def run(self, callback): gzclient_thread.start() self.threads.append(gzclient_thread) - process_name = 'ROS2Demo.GameLauncher' + process_name = "ROS2Demo.GameLauncher" wait_for_process_to_start(process_name, timeout=360) def terminate(self): diff --git a/robotics_application_manager/manager/launcher/launcher_robot.py b/robotics_application_manager/manager/launcher/launcher_robot.py index a00c21a..800cd97 100644 --- a/robotics_application_manager/manager/launcher/launcher_robot.py +++ b/robotics_application_manager/manager/launcher/launcher_robot.py @@ -3,9 +3,13 @@ from typing import Optional from pydantic import BaseModel -from manager.libs.process_utils import get_class, class_from_module, get_ros_version -from manager.ram_logging.log_manager import LogManager -from manager.manager.launcher.launcher_interface import ILauncher +from robotics_application_manager.libs import ( + get_class, + class_from_module, + get_ros_version, +) +from robotics_application_manager import LogManager +from .launcher_interface import ILauncher worlds = { "gazebo": { diff --git a/robotics_application_manager/manager/launcher/launcher_robot_ros2_api.py b/robotics_application_manager/manager/launcher/launcher_robot_ros2_api.py index 9ccffe5..eb25b48 100644 --- a/robotics_application_manager/manager/launcher/launcher_robot_ros2_api.py +++ b/robotics_application_manager/manager/launcher/launcher_robot_ros2_api.py @@ -3,8 +3,11 @@ import time import stat -from manager.manager.launcher.launcher_interface import ILauncher, LauncherException -from manager.manager.docker_thread.docker_thread import DockerThread +from .launcher_interface import ( + ILauncher, + LauncherException, +) +from robotics_application_manager.manager.docker_thread import DockerThread import subprocess import logging diff --git a/robotics_application_manager/manager/launcher/launcher_ros2_api.py b/robotics_application_manager/manager/launcher/launcher_ros2_api.py index 4263cce..4840dce 100644 --- a/robotics_application_manager/manager/launcher/launcher_ros2_api.py +++ b/robotics_application_manager/manager/launcher/launcher_ros2_api.py @@ -4,8 +4,11 @@ import time import stat -from manager.manager.launcher.launcher_interface import ILauncher, LauncherException -from manager.manager.docker_thread.docker_thread import DockerThread +from .launcher_interface import ( + ILauncher, + LauncherException, +) +from robotics_application_manager.manager.docker_thread import DockerThread import subprocess import logging diff --git a/robotics_application_manager/manager/launcher/launcher_rviz.py b/robotics_application_manager/manager/launcher/launcher_rviz.py index 7ced22c..63643b4 100644 --- a/robotics_application_manager/manager/launcher/launcher_rviz.py +++ b/robotics_application_manager/manager/launcher/launcher_rviz.py @@ -1,7 +1,7 @@ -from manager.manager.launcher.launcher_interface import ILauncher -from manager.manager.docker_thread.docker_thread import DockerThread -from manager.manager.vnc.vnc_server import Vnc_server -from manager.libs.process_utils import check_gpu_acceleration +from .launcher_interface import ILauncher +from robotics_application_manager.manager.docker_thread import DockerThread +from robotics_application_manager.manager.vnc import Vnc_server +from robotics_application_manager.libs import check_gpu_acceleration import os import stat from typing import List, Any @@ -23,8 +23,8 @@ def run(self, config_file, callback): config = "ros2 run rviz2 rviz2" if config_file != None: - config = f'ros2 launch {config_file}' - + config = f"ros2 launch {config_file}" + print(config) if ACCELERATION_ENABLED: self.console_vnc.start_vnc_gpu( @@ -69,7 +69,6 @@ def terminate(self): def died(self): pass - # rviz_node_full = Node( # package="rviz2", # executable="rviz2", @@ -80,7 +79,7 @@ def died(self): # robot_description, # robot_description_semantic, # kinematics_yaml, - + # pilz_planning_pipeline_config, # joint_limits, @@ -92,4 +91,4 @@ def died(self): # move_group_capabilities, # {"use_sim_time": True}, # ] - # ) \ No newline at end of file + # ) diff --git a/robotics_application_manager/manager/launcher/launcher_rviz_ros2.py b/robotics_application_manager/manager/launcher/launcher_rviz_ros2.py index 06fb629..97728ba 100755 --- a/robotics_application_manager/manager/launcher/launcher_rviz_ros2.py +++ b/robotics_application_manager/manager/launcher/launcher_rviz_ros2.py @@ -1,6 +1,6 @@ -from manager.manager.launcher.launcher_interface import ILauncher -from manager.manager.docker_thread.docker_thread import DockerThread -from manager.manager.vnc.vnc_server import Vnc_server +from .launcher_interface import ILauncher +from robotics_application_manager.manager.docker_thread import DockerThread +from robotics_application_manager.manager.vnc import Vnc_server import os import stat diff --git a/robotics_application_manager/manager/launcher/launcher_state_monitor.py b/robotics_application_manager/manager/launcher/launcher_state_monitor.py index c2b9446..2785fb8 100644 --- a/robotics_application_manager/manager/launcher/launcher_state_monitor.py +++ b/robotics_application_manager/manager/launcher/launcher_state_monitor.py @@ -1,8 +1,7 @@ -from manager.libs.server import Server -from manager.ram_logging.log_manager import LogManager -from manager.comms.new_consumer import ManagerConsumer +from robotics_application_manager.libs import Server, FileWatchdog +from robotics_application_manager import LogManager +from robotics_application_manager.comms import ManagerConsumer from typing import Optional -from manager.libs.file_watchdog import FileWatchdog class LauncherStateMonitor: diff --git a/robotics_application_manager/manager/launcher/launcher_tools.py b/robotics_application_manager/manager/launcher/launcher_tools.py index e7ea628..2151a16 100644 --- a/robotics_application_manager/manager/launcher/launcher_tools.py +++ b/robotics_application_manager/manager/launcher/launcher_tools.py @@ -1,11 +1,10 @@ -from manager.libs.process_utils import get_class, class_from_module +from robotics_application_manager.libs import get_class, class_from_module from typing import Optional from pydantic import BaseModel -from manager.libs.process_utils import get_class, class_from_module -from manager.ram_logging.log_manager import LogManager -from manager.manager.launcher.launcher_interface import ILauncher +from robotics_application_manager import LogManager +from .launcher_interface import ILauncher tools = { "console": { diff --git a/robotics_application_manager/manager/launcher/launcher_web_gui.py b/robotics_application_manager/manager/launcher/launcher_web_gui.py index 367e003..1e4f6db 100644 --- a/robotics_application_manager/manager/launcher/launcher_web_gui.py +++ b/robotics_application_manager/manager/launcher/launcher_web_gui.py @@ -1,6 +1,6 @@ -from manager.libs.server import Server -from manager.ram_logging.log_manager import LogManager -from manager.comms.new_consumer import ManagerConsumer +from robotics_application_manager.libs import Server +from robotics_application_manager import LogManager +from robotics_application_manager.comms import ManagerConsumer from typing import Optional diff --git a/robotics_application_manager/manager/launcher/launcher_world.py b/robotics_application_manager/manager/launcher/launcher_world.py index d9ba862..17e4abb 100644 --- a/robotics_application_manager/manager/launcher/launcher_world.py +++ b/robotics_application_manager/manager/launcher/launcher_world.py @@ -1,9 +1,13 @@ from typing import Optional from pydantic import BaseModel -from manager.libs.process_utils import get_class, class_from_module, get_ros_version -from manager.ram_logging.log_manager import LogManager -from manager.manager.launcher.launcher_interface import ILauncher +from robotics_application_manager.libs import ( + get_class, + class_from_module, + get_ros_version, +) +from robotics_application_manager import LogManager +from .launcher_interface import ILauncher worlds = { "gazebo": { diff --git a/robotics_application_manager/manager/lint/__init__.py b/robotics_application_manager/manager/lint/__init__.py index e69de29..6b131a5 100644 --- a/robotics_application_manager/manager/lint/__init__.py +++ b/robotics_application_manager/manager/lint/__init__.py @@ -0,0 +1 @@ +from .linter import Lint diff --git a/robotics_application_manager/manager/manager.py b/robotics_application_manager/manager/manager.py index 1fef216..ab41b84 100644 --- a/robotics_application_manager/manager/manager.py +++ b/robotics_application_manager/manager/manager.py @@ -33,20 +33,24 @@ from transitions import Machine -from manager.comms.consumer_message import ManagerConsumerMessageException -from manager.comms.new_consumer import ManagerConsumer -from manager.libs.process_utils import ( +from robotics_application_manager.comms import ( + ManagerConsumerMessageException, + ManagerConsumer, +) +from robotics_application_manager.libs import ( check_gpu_acceleration, get_class_from_file, stop_process_and_children, + ConfigurationManager, +) +from robotics_application_manager.ram_logging import LogManager +from robotics_application_manager.manager.launcher import ( + LauncherWorld, + LauncherRobot, + LauncherTools, ) -from manager.libs.launch_world_model import ConfigurationManager -from manager.ram_logging.log_manager import LogManager -from manager.manager.launcher.launcher_world import LauncherWorld -from manager.manager.launcher.launcher_robot import LauncherRobot -from manager.manager.launcher.launcher_tools import LauncherTools -from manager.manager.lint.linter import Lint -from manager.manager.editor.serializers import serialize_completions +from robotics_application_manager.manager.lint import Lint +from robotics_application_manager.manager.editor import serialize_completions class Manager: diff --git a/robotics_application_manager/manager/vnc/__init__.py b/robotics_application_manager/manager/vnc/__init__.py new file mode 100644 index 0000000..690fa0b --- /dev/null +++ b/robotics_application_manager/manager/vnc/__init__.py @@ -0,0 +1 @@ +from .vnc_server import Vnc_server diff --git a/robotics_application_manager/manager/vnc/vnc_server.py b/robotics_application_manager/manager/vnc/vnc_server.py index ced8642..24129cb 100755 --- a/robotics_application_manager/manager/vnc/vnc_server.py +++ b/robotics_application_manager/manager/vnc/vnc_server.py @@ -6,11 +6,11 @@ import time import socket -from manager.manager.docker_thread.docker_thread import DockerThread +from robotics_application_manager.manager.docker_thread import DockerThread import subprocess from typing import List, Any import os -from manager.libs.process_utils import wait_for_xserver +from robotics_application_manager.libs import wait_for_xserver class Vnc_server: @@ -40,9 +40,9 @@ def start_vnc(self, display, internal_port, external_port): wait_for_xserver(display) certs = "" - + if os.path.isfile("/etc/certs/cert.pem"): - certs = "--cert /etc/certs/cert.pem --key /etc/certs/privkey.pem" + certs = "--cert /etc/certs/cert.pem --key /etc/certs/privkey.pem" # Start noVNC with default port 6080 listening to VNC server on 5900 if self.get_ros_version() == "2": @@ -89,9 +89,9 @@ def start_vnc_gpu(self, display, internal_port, external_port, dri_path): wait_for_xserver(display) certs = "" - + if os.path.isfile("/etc/certs/cert.pem"): - certs = "--cert /etc/certs/cert.pem --key /etc/certs/privkey.pem" + certs = "--cert /etc/certs/cert.pem --key /etc/certs/privkey.pem" # Start noVNC with default port 6080 listening to VNC server on 5900 if self.get_ros_version() == "2": diff --git a/robotics_application_manager/ram_logging/__init__.py b/robotics_application_manager/ram_logging/__init__.py index e69de29..f85cd73 100644 --- a/robotics_application_manager/ram_logging/__init__.py +++ b/robotics_application_manager/ram_logging/__init__.py @@ -0,0 +1 @@ +from .log_manager import LogManager diff --git a/robotics_application_manager/ram_logging/log_manager.py b/robotics_application_manager/ram_logging/log_manager.py index ad7d149..30aec29 100644 --- a/robotics_application_manager/ram_logging/log_manager.py +++ b/robotics_application_manager/ram_logging/log_manager.py @@ -7,7 +7,16 @@ import logging import os -from manager.libs.singleton import singleton + +def singleton(cls): + instances = {} + + def get_instance(): + if cls not in instances: + instances[cls] = cls() + return instances[cls] + + return get_instance() # Clase para un Formatter personalizado que aƱade colores From 328ff1ad46825124a345436f5c8af0c7d9134052 Mon Sep 17 00:00:00 2001 From: Javier Izquierdo Hernandez Date: Wed, 18 Feb 2026 14:57:10 +0100 Subject: [PATCH 11/15] Add back manager --- launch.py | 14 -------------- .../manager/manager.py | 18 ++++++++++++++++++ 2 files changed, 18 insertions(+), 14 deletions(-) delete mode 100644 launch.py diff --git a/launch.py b/launch.py deleted file mode 100644 index c9e0f79..0000000 --- a/launch.py +++ /dev/null @@ -1,14 +0,0 @@ -from robotics_application_manager.manager.manager import Manager - -if __name__ == "__main__": - import argparse - - parser = argparse.ArgumentParser() - parser.add_argument( - "host", type=str, help="Host to listen to (0.0.0.0 or all hosts)" - ) - parser.add_argument("port", type=int, help="Port to listen to") - args = parser.parse_args() - - RAM = Manager(args.host, args.port) - RAM.start() diff --git a/robotics_application_manager/manager/manager.py b/robotics_application_manager/manager/manager.py index ab41b84..f3acb3d 100644 --- a/robotics_application_manager/manager/manager.py +++ b/robotics_application_manager/manager/manager.py @@ -777,6 +777,7 @@ def on_disconnect(self, event): This method stops all running processes, terminates launchers, and restarts the script. """ + LogManager.logger.exception("Disconected") try: self.consumer.stop() @@ -801,6 +802,7 @@ def on_disconnect(self, event): self.robot_launcher.terminate() except Exception as e: LogManager.logger.exception("Exception terminating robot launcher") + if self.world_launcher: try: self.world_launcher.terminate() @@ -912,6 +914,8 @@ def start(self): self.consumer.start() def signal_handler(sign, frame): + LogManager.logger.exception("why") + print("\nprogram exiting gracefully") self.running = False @@ -968,3 +972,17 @@ def signal_handler(sign, frame): ) self.consumer.send_message(ex) LogManager.logger.error(e, exc_info=True) + + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser() + parser.add_argument( + "host", type=str, help="Host to listen to (0.0.0.0 or all hosts)" + ) + parser.add_argument("port", type=int, help="Port to listen to") + args = parser.parse_args() + + RAM = Manager(args.host, args.port) + RAM.start() From eb4d62e76ff127e8fbd77836af54f076c07d6c12 Mon Sep 17 00:00:00 2001 From: Javier Izquierdo Hernandez Date: Wed, 18 Feb 2026 14:57:20 +0100 Subject: [PATCH 12/15] Fix typo --- robotics_application_manager/manager/manager.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/robotics_application_manager/manager/manager.py b/robotics_application_manager/manager/manager.py index f3acb3d..6eae1b3 100644 --- a/robotics_application_manager/manager/manager.py +++ b/robotics_application_manager/manager/manager.py @@ -777,7 +777,6 @@ def on_disconnect(self, event): This method stops all running processes, terminates launchers, and restarts the script. """ - LogManager.logger.exception("Disconected") try: self.consumer.stop() @@ -914,8 +913,6 @@ def start(self): self.consumer.start() def signal_handler(sign, frame): - LogManager.logger.exception("why") - print("\nprogram exiting gracefully") self.running = False From 52e10eb806d2f8c89a36c605fddb0a1843575c0c Mon Sep 17 00:00:00 2001 From: Javier Izquierdo Hernandez Date: Fri, 20 Feb 2026 12:11:06 +0100 Subject: [PATCH 13/15] Fix reset --- robotics_application_manager/manager/manager.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/robotics_application_manager/manager/manager.py b/robotics_application_manager/manager/manager.py index 6eae1b3..d4cb64e 100644 --- a/robotics_application_manager/manager/manager.py +++ b/robotics_application_manager/manager/manager.py @@ -754,6 +754,7 @@ def on_terminate_application(self, event): def on_terminate_tools(self, event): self.tools_launcher.terminate() + self.tools_launcher = None def on_terminate_universe(self, event): """ @@ -767,8 +768,11 @@ def on_terminate_universe(self, event): """ if self.world_launcher is not None: self.world_launcher.terminate() + self.world_launcher = None + self.world_type = None if self.robot_launcher is not None: self.robot_launcher.terminate() + self.robot_launcher = None def on_disconnect(self, event): """ @@ -778,11 +782,6 @@ def on_disconnect(self, event): terminates launchers, and restarts the script. """ - try: - self.consumer.stop() - except Exception as e: - LogManager.logger.exception("Exception stopping consumer") - if self.application_process: try: stop_process_and_children(self.application_process) @@ -808,10 +807,6 @@ def on_disconnect(self, event): except Exception as e: LogManager.logger.exception("Exception terminating world launcher") - # Reiniciar el script - python = sys.executable - os.execl(python, python, *sys.argv) - def process_message(self, message): if message.command == "gui": self.tools_launcher.pass_msg(message.data) From e10bc7f473236b59e5f9247feed0debe5c45cc82 Mon Sep 17 00:00:00 2001 From: Javier Izquierdo Hernandez Date: Fri, 20 Feb 2026 13:04:03 +0100 Subject: [PATCH 14/15] Fix imports --- robotics_application_manager/manager/manager.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/robotics_application_manager/manager/manager.py b/robotics_application_manager/manager/manager.py index d4cb64e..14cce10 100644 --- a/robotics_application_manager/manager/manager.py +++ b/robotics_application_manager/manager/manager.py @@ -5,20 +5,16 @@ and handling code analysis and formatting. """ -from __future__ import annotations -import json import sys -import tempfile - -import black - sys.path.insert(0, "/RoboticsApplicationManager") +import json +import tempfile +import black import os import signal import subprocess -import sys import re import psutil import shutil @@ -26,13 +22,11 @@ import base64 import zipfile import jedi - import traceback + from queue import Queue from uuid import uuid4 - from transitions import Machine - from robotics_application_manager.comms import ( ManagerConsumerMessageException, ManagerConsumer, From caca38b6bd45944c2bdda8120cd98c7136c2980a Mon Sep 17 00:00:00 2001 From: Javier Izquierdo Hernandez Date: Fri, 20 Feb 2026 19:23:50 +0100 Subject: [PATCH 15/15] Update version --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c245866..893a632 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,12 +1,12 @@ [project] name = "robotics_application_manager" -version = "0.0.3" +version = "5.6.7" authors = [ { name="Example Author", email="author@example.com" }, ] description = "Robotics Application Manager" readme = "README.md" -requires-python = ">=3.9" +requires-python = ">=3.10" classifiers = [ "Programming Language :: Python :: 3", "Operating System :: OS Independent",

u4DF^TTlHp`-3ku_jjwqJhE9e?3=l`V=+`!Yr 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%0Dn0CKb`hv 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 None: - try: - while True: - self.client.run_forever(ping_timeout=None, ping_interval=0) - if self._stop.isSet(): - return - except Exception as ex: - LogManager.logger.exception(ex) - - def stop(self) -> None: - self._stop.set() - self.client.close() - - def send(self, data): - self.client.send(data) - - def on_message(self, ws, message): - self.callback(self.name, message) - - def on_error(self, ws, error): - LogManager.logger.error(error) - - def on_close(self, ws, status, msg): - LogManager.logger.info( - f"Connection with {self.name} closed, status code: {status}, close message: {msg}" - ) - - def on_open(self, ws): - LogManager.logger.info(f"Connection with {self.name} opened") diff --git a/manager/libs/applications/compatibility/exercise_wrapper.py b/manager/libs/applications/compatibility/exercise_wrapper.py deleted file mode 100644 index 443d94f..0000000 --- a/manager/libs/applications/compatibility/exercise_wrapper.py +++ /dev/null @@ -1,75 +0,0 @@ -import json -import signal -import subprocess -import sys -import threading -import time -import importlib -from threading import Thread - -from manager.libs.applications.compatibility.client import Client -from manager.libs.process_utils import stop_process_and_children -from manager.ram_logging.log_manager import LogManager -from manager.manager.application.robotics_python_application_interface import ( - IRoboticsPythonApplication, -) -from manager.manager.lint.linter import Lint - - -class CompatibilityExerciseWrapper: - def __init__(self): - self.running = False - self.linter = Lint() - self.brain_ready_event = threading.Event() - self.pick = None - self.exercise = None - self.run() - - def save_pick(self, pick): - self.pick = pick - - def send_pick(self, pick): - self.gui_connection.send("#pick" + json.dumps(pick)) - print("#pick" + json.dumps(pick)) - - def handle_client_gui(self, msg): - if msg["msg"] == "#pick": - self.pick = msg["data"] - else: - self.gui_connection.send(msg["msg"]) - - def _run_server(self, cmd): - process = subprocess.Popen( - f"{cmd}", - shell=True, - stdout=sys.stdout, - stderr=subprocess.STDOUT, - bufsize=1024, - universal_newlines=True, - ) - return process - - def run(self): - self.exercise = self._run_server( - f"python3 $EXERCISE_FOLDER/entry_point/exercise.py" - ) - - def stop(self): - pass - - def resume(self): - pass - - def pause(self): - pass - - @property - def is_alive(self): - return self.running - - def terminate(self): - - if self.exercise is not None: - stop_process_and_children(self.exercise) - - self.running = False diff --git a/manager/libs/applications/compatibility/exercise_wrapper_ros2.py b/manager/libs/applications/compatibility/exercise_wrapper_ros2.py deleted file mode 100644 index 08129e3..0000000 --- a/manager/libs/applications/compatibility/exercise_wrapper_ros2.py +++ /dev/null @@ -1,180 +0,0 @@ -import json -import logging -import os.path -import subprocess -import sys -import threading -import time -from threading import Thread - -from manager.libs.applications.compatibility.client import Client -from manager.libs.process_utils import stop_process_and_children -from manager.ram_logging.log_manager import LogManager -from manager.manager.application.robotics_python_application_interface import ( - IRoboticsPythonApplication, -) -from manager.manager.lint.linter import Lint - - -class CompatibilityExerciseWrapperRos2(IRoboticsPythonApplication): - def __init__(self, exercise_command, gui_command, update_callback): - super().__init__(update_callback) - - home_dir = os.path.expanduser("~") - self.running = False - self.linter = Lint() - self.brain_ready_event = threading.Event() - # TODO: review hardcoded values - process_ready, self.exercise_server = self._run_exercise_server( - f"python3 {exercise_command}", - f"{home_dir}/ws_code.log", - "websocket_code=ready", - ) - if process_ready: - LogManager.logger.info(f"Exercise code {exercise_command} launched") - time.sleep(1) - self.exercise_connection = Client( - "ws://127.0.0.1:1905", "exercise", self.server_message - ) - self.exercise_connection.start() - else: - self.exercise_server.kill() - raise RuntimeError(f"Exercise {exercise_command} could not be run") - - process_ready, self.gui_server = self._run_exercise_server( - f"python3 {gui_command}", f"{home_dir}/ws_gui.log", "websocket_gui=ready" - ) - if process_ready: - LogManager.logger.info(f"Exercise gui {gui_command} launched") - time.sleep(1) - self.gui_connection = Client( - "ws://127.0.0.1:2303", "gui", self.server_message - ) - self.gui_connection.start() - else: - self.gui_server.kill() - raise RuntimeError(f"Exercise GUI {gui_command} could not be run") - - self.running = True - - self.start_send_freq_thread() - - def send_freq(self, exercise_connection, is_alive): - """Send the frequency of the brain and gui to the exercise server""" - while is_alive(): - exercise_connection.send("""#freq{"brain": 20, "gui": 10, "rtf": 100}""") - time.sleep(1) - - def start_send_freq_thread(self): - """Start a thread to send the frequency of the brain and gui to the exercise server""" - daemon = Thread( - target=lambda: self.send_freq( - self.exercise_connection, lambda: self.is_alive - ), - daemon=False, - name="Monitor frequencies", - ) - daemon.start() - - def _run_exercise_server(self, cmd, log_file, load_string, timeout: int = 5): - process = subprocess.Popen( - f"{cmd}", - shell=True, - stdout=sys.stdout, - stderr=subprocess.STDOUT, - bufsize=1024, - universal_newlines=True, - ) - - process_ready = False - while not process_ready: - try: - f = open(log_file, "r") - if f.readline() == load_string: - process_ready = True - f.close() - time.sleep(0.2) - except Exception as e: - LogManager.logger.debug(f"waiting for server string '{load_string}'...") - time.sleep(0.2) - - return process_ready, process - - def server_message(self, name, message): - if name == "gui": # message received from GUI server - LogManager.logger.debug(f"Message received from gui: {message[:30]}") - self._process_gui_message(message) - elif name == "exercise": # message received from EXERCISE server - if message.startswith("#exec"): - self.brain_ready_event.set() - LogManager.logger.info(f"Message received from exercise: {message[:30]}") - self._process_exercise_message(message) - - def _process_gui_message(self, message): - payload = json.loads(message[4:]) - self.update_callback(payload) - self.gui_connection.send("#ack") - - def _process_exercise_message(self, message): - comand = message[:5] - if message == comand: - payload = comand - else: - payload = json.loads(message[5:]) - self.update_callback(payload) - self.exercise_connection.send("#ack") - - def call_service(self, service, service_type): - command = f"ros2 service call {service} {service_type}" - subprocess.call( - f"{command}", - shell=True, - stdout=sys.stdout, - stderr=subprocess.STDOUT, - bufsize=1024, - universal_newlines=True, - ) - - def run(self): - self.call_service("/unpause_physics", "std_srvs/srv/Empty") - self.exercise_connection.send("#play") - - def stop(self): - self.call_service("/pause_physics", "std_srvs/srv/Empty") - self.call_service("/reset_world", "std_srvs/srv/Empty") - self.exercise_connection.send("#rest") - - def resume(self): - self.call_service("/unpause_physics", "std_srvs/srv/Empty") - self.exercise_connection.send("#play") - - def pause(self): - self.call_service("/pause_physics", "std_srvs/srv/Empty") - self.exercise_connection.send("#stop") - - def restart(self): - # pause_cmd = "ros2 service call /restart_simulation std_srvs/srv/Empty" - # subprocess.call(f"{pause_cmd}", shell=True, stdout=sys.stdout, stderr=subprocess.STDOUT, bufsize=1024, - # universal_newlines=True) - pass - - @property - def is_alive(self): - return self.running - - def load_code(self, code: str): - errors = self.linter.evaluate_code(code) - if errors == "": - self.brain_ready_event.clear() - self.exercise_connection.send(f"#code {code}") - self.brain_ready_event.wait() - else: - raise Exception(errors) - - def terminate(self): - self.running = False - self.exercise_connection.stop() - self.gui_connection.stop() - - stop_process_and_children(self.exercise_server) - stop_process_and_children(self.gui_server) diff --git a/manager/libs/applications/compatibility/physical_robot_exercise_wrapper_ros2.py b/manager/libs/applications/compatibility/physical_robot_exercise_wrapper_ros2.py deleted file mode 100644 index 97d7adc..0000000 --- a/manager/libs/applications/compatibility/physical_robot_exercise_wrapper_ros2.py +++ /dev/null @@ -1,164 +0,0 @@ -import json -import logging -import os.path -import subprocess -import sys -import threading -import time -from threading import Thread - -from manager.libs.applications.compatibility.client import Client -from manager.libs.process_utils import stop_process_and_children -from manager.ram_logging.log_manager import LogManager -from manager.manager.application.robotics_python_application_interface import ( - IRoboticsPythonApplication, -) -from manager.manager.lint.linter import Lint - - -class CompatibilityExerciseWrapperRos2(IRoboticsPythonApplication): - def __init__(self, exercise_command, gui_command, update_callback): - super().__init__(update_callback) - - home_dir = os.path.expanduser("~") - self.running = False - self.linter = Lint() - self.brain_ready_event = threading.Event() - # TODO: review hardcoded values - process_ready, self.exercise_server = self._run_exercise_server( - f"python3 {exercise_command}", - f"{home_dir}/ws_code.log", - "websocket_code=ready", - ) - if process_ready: - LogManager.logger.info(f"Exercise code {exercise_command} launched") - time.sleep(1) - self.exercise_connection = Client( - "ws://127.0.0.1:1905", "exercise", self.server_message - ) - self.exercise_connection.start() - else: - self.exercise_server.kill() - raise RuntimeError(f"Exercise {exercise_command} could not be run") - - process_ready, self.gui_server = self._run_exercise_server( - f"python3 {gui_command}", f"{home_dir}/ws_gui.log", "websocket_gui=ready" - ) - if process_ready: - LogManager.logger.info(f"Exercise gui {gui_command} launched") - time.sleep(1) - self.gui_connection = Client( - "ws://127.0.0.1:2303", "gui", self.server_message - ) - self.gui_connection.start() - else: - self.gui_server.kill() - raise RuntimeError(f"Exercise GUI {gui_command} could not be run") - - self.running = True - - self.start_send_freq_thread() - - def send_freq(self, exercise_connection, is_alive): - """Send the frequency of the brain and gui to the exercise server""" - while is_alive(): - exercise_connection.send("""#freq{"brain": 20, "gui": 10, "rtf": 100}""") - time.sleep(1) - - def start_send_freq_thread(self): - """Start a thread to send the frequency of the brain and gui to the exercise server""" - daemon = Thread( - target=lambda: self.send_freq( - self.exercise_connection, lambda: self.is_alive - ), - daemon=False, - name="Monitor frequencies", - ) - daemon.start() - - def _run_exercise_server(self, cmd, log_file, load_string, timeout: int = 5): - process = subprocess.Popen( - f"{cmd}", - shell=True, - stdout=sys.stdout, - stderr=subprocess.STDOUT, - bufsize=1024, - universal_newlines=True, - ) - - process_ready = False - while not process_ready: - try: - f = open(log_file, "r") - if f.readline() == load_string: - process_ready = True - f.close() - time.sleep(0.2) - except Exception as e: - LogManager.logger.debug(f"waiting for server string '{load_string}'...") - time.sleep(0.2) - - return process_ready, process - - def server_message(self, name, message): - if name == "gui": # message received from GUI server - LogManager.logger.debug(f"Message received from gui: {message[:30]}") - self._process_gui_message(message) - elif name == "exercise": # message received from EXERCISE server - if message.startswith("#exec"): - self.brain_ready_event.set() - LogManager.logger.info(f"Message received from exercise: {message[:40]}") - self._process_exercise_message(message) - - def _process_gui_message(self, message): - payload = json.loads(message[4:]) - self.update_callback(payload) - self.gui_connection.send("#ack") - - def _process_exercise_message(self, message): - comand = message[:5] - if message == comand: - payload = comand - else: - payload = json.loads(message[5:]) - self.update_callback(payload) - self.exercise_connection.send("#ack") - - def run(self): - self.exercise_connection.send("#play") - - def stop(self): - self.exercise_connection.send("#rest") - - def resume(self): - self.exercise_connection.send("#play") - - def pause(self): - self.exercise_connection.send("#stop") - - def restart(self): - # pause_cmd = "ros2 service call /restart_simulation std_srvs/srv/Empty" - # subprocess.call(f"{pause_cmd}", shell=True, stdout=sys.stdout, stderr=subprocess.STDOUT, bufsize=1024, - # universal_newlines=True) - pass - - @property - def is_alive(self): - return self.running - - def load_code(self, code: str): - errors = self.linter.evaluate_code(code) - if errors == "": - self.brain_ready_event.clear() - self.exercise_connection.send(f"#code {code}") - self.brain_ready_event.wait() - else: - raise Exception(errors) - - def terminate(self): - self.running = False - self.exercise_connection.stop() - self.gui_connection.stop() - - stop_process_and_children(self.exercise_server) - stop_process_and_children(self.gui_server) diff --git a/manager/libs/applications/compatibility/robotics_application_wrapper.py b/manager/libs/applications/compatibility/robotics_application_wrapper.py deleted file mode 100644 index 400e70a..0000000 --- a/manager/libs/applications/compatibility/robotics_application_wrapper.py +++ /dev/null @@ -1,111 +0,0 @@ -import os.path -import subprocess -import sys -import time - -import psutil - -from manager.libs.process_utils import stop_process_and_children -from manager.manager.application.robotics_python_application_interface import ( - IRoboticsPythonApplication, -) -from manager.manager.lint.linter import Lint -from manager.manager.docker_thread.docker_thread import DockerThread - -from manager.libs.applications.compatibility.client import Client -from manager.libs.process_utils import stop_process_and_children -from manager.ram_logging.log_manager import LogManager -from manager.manager.application.robotics_python_application_interface import ( - IRoboticsPythonApplication, -) -from manager.manager.lint.linter import Lint - -import os - - -class RoboticsApplicationWrapper(IRoboticsPythonApplication): - def __init__(self, update_callback): - super().__init__(update_callback) - self.running = False - self.linter = Lint() - time.sleep(5) - self.start_console() - self.user_process = None - self.entrypoint_path = None - - def _create_process(self, cmd): - # print("creando procesos") - process = subprocess.Popen( - f"{cmd}", - shell=True, - stdout=sys.stdout, - stderr=subprocess.STDOUT, - bufsize=1024, - universal_newlines=True, - cwd="/workspace/code", - ) - psProcess = psutil.Process(pid=process.pid) - return psProcess - - def terminate(self): - self.running = False - if self.user_process != None: - stop_process_and_children(self.user_process) - self.user_process = None - - def load_code(self, path: str): - self.entrypoint_path = path - - def run(self): - self.user_process = self._create_process( - f"DISPLAY=:2 python3 {self.entrypoint_path}" - ) - self.running = True - - def stop(self): - stop_process_and_children(self.user_process) - self.user_process = None - - def restart(self): - pass - - def resume(self): - self.suspend_resume("resume") - - def pause(self): - if self.user_process != None: - self.suspend_resume("pause") - - @property - def is_alive(self): - return self.running - - def start_console(self): - # 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(self): - sys.stderr.close() - sys.stdout.close() - sys.stdin.close() - - def suspend_resume(self, signal): - # collect processes to stop - children = self.user_process.children(recursive=True) - children.append(self.user_process) - - # send signal to processes - for p in children: - try: - if signal == "pause": - p.suspend() - if signal == "resume": - p.resume() - except psutil.NoSuchProcess: - pass diff --git a/manager/libs/applications/robotics_application.py b/manager/libs/applications/robotics_application.py deleted file mode 100644 index c62b627..0000000 --- a/manager/libs/applications/robotics_application.py +++ /dev/null @@ -1,24 +0,0 @@ -from manager.manager.application.robotics_python_application_interface import ( - IRoboticsPythonApplication, -) - - -class RoboticsApplication(IRoboticsPythonApplication): - def terminate(self): - pass - - def load_code(self, code: str): - pass - - def run(self): - pass - - def stop(self): - pass - - def restart(self): - pass - - @property - def is_alive(self): - pass diff --git a/manager/manager/application/__pycache__/__init__.cpython-38.pyc b/manager/manager/application/__pycache__/__init__.cpython-38.pyc deleted file mode 100644 index 76c82566d2ab1db40e46a075551d69f0a479f7d5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 151 zcmWIL<>g`k0#>o06cGIwL?8o3AjbiSi&=m~3PUi1CZpdOEUBG^yA|*^D;}~j2MBHX$)$M#gs4!3dWk9!2XZma+JzPS$JmQV)f3HQ z@B%&ZI(y|2xN%}8VYf@wY!t8T@p{JNZ|wE#yH2OYu$;9o)lY}9U-VKgIL2OL)!W#- zW|B#3iuKAKF=@YLeaFM`|4K3A0}d-TvLqYX(vtR+jT}`|b?N-DWlh$nwb7bflMNhi zNLMydy0RtLQ8r~;c2KtDhU}tTmz#16Wm|RRw%nPrD{HhNcX9oD+1}}!cpMww(`OU& zDUOblB=9|9{5bOL5*hWWzQU?0=u7L8af}GPSi`=Rb<;!&qqrx6U@W}PeLJgZ6`TXj zx@J&A@#p;@U+vfvQiYR&25%6GNPJXKNO8k3fPZv6nc?x0j{A{Oa4tM`luR<07%(^&O9NkfFPoQ2bRkjAoq)-ud8+{r&KI=Ok3kGfvFP@!zQ{eep^~NClNcX~2 zo1PygfeKY*dO<8CC->J@wlj>)X|7T71R#dK4RqwtpoaEn-qf*|HH*pgn#Xil5mPb2 zvv5XUs9&7jT|w+G2?Ejxn&{-(n#11WbWrc2IwyQkL5QZSu^lX7(SAq<-N`#Lp2t`fbj?q#~GWU=96djy90h#&H|~ z-B9RILze-%)NE2iUmnoUGjkJ&69r}hJ2ZBaf@VERkU)8ks-{oDwOnV@aZ8VF%`EU| THIsL_$lT|1;uqxSH&*r++qLHX diff --git a/manager/manager/application/robotics_python_application_interface.py b/manager/manager/application/robotics_python_application_interface.py deleted file mode 100644 index 26ac826..0000000 --- a/manager/manager/application/robotics_python_application_interface.py +++ /dev/null @@ -1,28 +0,0 @@ -class IRoboticsPythonApplication: - def __init__(self, update_callback): - self.update_callback = update_callback - - def load_code(self, code: str) -> bool: - raise NotImplementedError("Exercise brains must implement load_code") - - def run(self): - raise NotImplementedError("Exercise brains must implement run") - - def stop(self): - raise NotImplementedError("Exercise brains must implement stop") - - def pause(self): - raise NotImplementedError("Exercise brains must implement pause") - - def resume(self): - raise NotImplementedError("Exercise brains must implement resume") - - def restart(self): - raise NotImplementedError("Exercise brains must implement restart") - - def terminate(self): - raise NotImplementedError("Exercise brains must implement terminate") - - @property - def is_alive(self): - raise NotImplementedError("Exercise brains must implement is_alive") diff --git a/manager/manager/config.json b/manager/manager/config.json deleted file mode 100644 index f8c71e3..0000000 --- a/manager/manager/config.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "application": { - "type": "python", - "entry_point": "$EXERCISE_FOLDER/entry_point/exercise.py", - "class_name": "Exercise", - "params": "" - }, - "launch": { - "0": { - "type": "module", - "module": "ros_api", - "resource_folders": [ - "$EXERCISE_FOLDER/launch" - ], - "model_folders": [ - "$CUSTOM_ROBOTS_FOLDER/f1/models" - ], - "plugin_folders": [ - ], - "parameters": [], - "launch_file": "$EXERCISE_FOLDER/launch/simple_line_follower_ros_headless_${circuit}.launch" - }, - "1": { - "type": "module", - "module": "console", - "display": ":1", - "internal_port": 5901, - "external_port": 1108, - "height": 1080, - "width": 1920 - } - } -} \ No newline at end of file diff --git a/manager/manager/launcher/__init__.py b/manager/manager/launcher/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/manager/manager/launcher/launcher_drones.py b/manager/manager/launcher/launcher_drones.py deleted file mode 100644 index 7aff0be..0000000 --- a/manager/manager/launcher/launcher_drones.py +++ /dev/null @@ -1,51 +0,0 @@ -import os -import subprocess -from manager.manager.launcher.launcher_interface import ILauncher, LauncherException -from manager.manager.docker_thread.docker_thread import DockerThread -from manager.libs.process_utils import wait_for_xserver -from typing import List, Any -import psutil -import threading - - -class LauncherDrones(ILauncher): - exercise_id: str - type: str - module: str - parameters: List[str] - launch_file: str - process: Any = None - threads: List[Any] = [] - - # holder for roslaunch process - launch: Any = None - - def run(self, callback: callable = None): - # Start X server in display - xserver_cmd = f"/usr/bin/Xorg -quiet -noreset +extension GLX +extension RANDR +extension RENDER -logfile ./xdummy.log -config ./xorg.conf :0" - xserver_thread = DockerThread(xserver_cmd) - xserver_thread.start() - wait_for_xserver(":0") - self.threads.append(xserver_thread) - self.launch_file = os.path.expandvars(self.launch_file) - - # Inicia el proceso en un hilo separado - self.launch = DockerThread(f"python3 {self.launch_file}") - self.launch.start() - self.threads.append(self.launch) - - def is_running(self): - return True - - def terminate(self): - try: - for thread in self.threads: - if thread.is_alive(): - thread.terminate() - thread.join() - self.threads.remove(thread) - except Exception as e: - print("Exception shutting down ROS") - - def died(self): - pass diff --git a/manager/manager/launcher/launcher_drones_gzsim.py b/manager/manager/launcher/launcher_drones_gzsim.py deleted file mode 100644 index b3b9628..0000000 --- a/manager/manager/launcher/launcher_drones_gzsim.py +++ /dev/null @@ -1,39 +0,0 @@ -import os -from src.manager.manager.launcher.launcher_interface import ILauncher -from src.manager.manager.docker_thread.docker_thread import DockerThread -from src.manager.libs.process_utils import wait_for_xserver -from typing import List, Any - - -class LauncherDronesGzsim(ILauncher): - type: str - module: str - launch_file: str - threads: List[Any] = [] - - def run(self, callback): - # Start X server in display - xserver_cmd = f"/usr/bin/Xorg -quiet -noreset +extension GLX +extension RANDR +extension RENDER -logfile ./xdummy.log -config ./xorg.conf :0" - xserver_thread = DockerThread(xserver_cmd) - xserver_thread.start() - wait_for_xserver(":0") - self.threads.append(xserver_thread) - - # expand variables in configuration paths - world_file = os.path.expandvars(self.launch_file) - - # Launching gzserver and Aerostack2 nodes - as2_launch_cmd = f"ros2 launch jderobot_drones as2_default_gazebo_sim.launch.py world_file:={world_file}" - - as2_launch_thread = DockerThread(as2_launch_cmd) - as2_launch_thread.start() - self.threads.append(as2_launch_thread) - - def is_running(self): - return True - - def terminate(self): - if self.is_running(): - for thread in self.threads: - thread.terminate() - thread.join() diff --git a/manager/manager/launcher/launcher_drones_ros2.py b/manager/manager/launcher/launcher_drones_ros2.py deleted file mode 100644 index e4c5d6f..0000000 --- a/manager/manager/launcher/launcher_drones_ros2.py +++ /dev/null @@ -1,48 +0,0 @@ -import os -from manager.manager.launcher.launcher_interface import ILauncher -from manager.manager.docker_thread.docker_thread import DockerThread -from manager.libs.process_utils import wait_for_xserver -from typing import List, Any - - -class LauncherDronesRos2(ILauncher): - type: str - module: str - launch_file: str - threads: List[Any] = [] - - def run(self, callback): - # Start X server in display - xserver_cmd = f"/usr/bin/Xorg -quiet -noreset +extension GLX +extension RANDR +extension RENDER -logfile ./xdummy.log -config ./xorg.conf :0" - xserver_thread = DockerThread(xserver_cmd) - xserver_thread.start() - wait_for_xserver(":0") - self.threads.append(xserver_thread) - - # expand variables in configuration paths - world_file = os.path.expandvars(self.launch_file) - - # Launching MicroXRCE and Aerostack2 nodes - as2_launch_cmd = f"ros2 launch jderobot_drones as2_default_classic_gazebo.launch.py world_file:={world_file}" - - as2_launch_thread = DockerThread(as2_launch_cmd) - as2_launch_thread.start() - self.threads.append(as2_launch_thread) - - # Launching gzserver and PX4 - px4_launch_cmd = f"$AS2_GZ_ASSETS_SCRIPT_PATH/default_run.sh {world_file}" - - px4_launch_thread = DockerThread(px4_launch_cmd) - px4_launch_thread.start() - self.threads.append(px4_launch_thread) - - def is_running(self): - return True - - def terminate(self): - if self.is_running(): - for thread in self.threads: - if thread.is_alive(): - thread.terminate() - thread.join() - self.threads.remove(thread) diff --git a/manager/manager/launcher/launcher_robot_display_view.py b/manager/manager/launcher/launcher_robot_display_view.py deleted file mode 100644 index 09e153d..0000000 --- a/manager/manager/launcher/launcher_robot_display_view.py +++ /dev/null @@ -1,55 +0,0 @@ -from manager.manager.launcher.launcher_interface import ILauncher -from manager.manager.docker_thread.docker_thread import DockerThread -from manager.manager.vnc.vnc_server import Vnc_server -import time -import os -import stat - - -class LauncherRobotDisplayView(ILauncher): - display: str - internal_port: str - external_port: str - height: int - width: int - running = False - threads = [] - - def run(self, config_file, callback): - DRI_PATH = self.get_dri_path() - ACCELERATION_ENABLED = self.check_device(DRI_PATH) - - robot_display_vnc = Vnc_server() - - if ACCELERATION_ENABLED: - robot_display_vnc.start_vnc_gpu( - self.display, self.internal_port, self.external_port, DRI_PATH - ) - # Write display config and start the console - console_cmd = f"export VGL_DISPLAY={DRI_PATH}; export DISPLAY={self.display}; /usr/bin/Xorg -noreset +extension GLX +extension RANDR +extension RENDER -logfile ./xdummy.log -config ./xorg.conf {self.display}" - else: - robot_display_vnc.start_vnc( - self.display, self.internal_port, self.external_port - ) - # Write display config and start the console - console_cmd = f"export DISPLAY={self.display};/usr/bin/Xorg -noreset +extension GLX +extension RANDR +extension RENDER -logfile ./xdummy.log -config ./xorg.conf {self.display}" - - console_thread = DockerThread(console_cmd) - console_thread.start() - self.threads.append(console_thread) - - self.running = True - - def is_running(self): - return self.running - - def terminate(self): - for thread in self.threads: - if thread.is_alive(): - thread.terminate() - thread.join() - self.threads.remove(thread) - self.running = False - - def died(self): - pass diff --git a/manager/manager/launcher/launcher_ros.py b/manager/manager/launcher/launcher_ros.py deleted file mode 100644 index af91171..0000000 --- a/manager/manager/launcher/launcher_ros.py +++ /dev/null @@ -1,92 +0,0 @@ -import os -import shutil -import subprocess -import traceback -from typing import Any, List - -from manager.launcher.launcher_interface import ILauncher, LauncherException - - -class LauncherRos(ILauncher): - """ - Launcher for ROS/Gazebo - - It's configuration should follow this spec: - - { - "type": "module", - "module": "ros", - "resource_folders": [ - "$EXERCISE_FOLDER/launch" - ], - "model_folders": [ - "$CUSTOM_ROBOTS/f1/models" - ], - "plugin_folders": [ - ], - "parameters": [], - "launch_file": "$EXERCISE_FOLDER/launch/simple_line_follower_ros_headless_default.launch" - } - """ - - exercise_id: str - type: str - module: str - resource_folders: List[str] - model_folders: List[str] - plugin_folders: List[str] - parameters: List[str] - launch_file: str - - ros_command_line: str = shutil.which("roslaunch") - process: Any = None - - def run(self): - try: - # generate entry_point environment variable - os.environ["EXERCISE_FOLDER"] = ( - f"{os.environ.get('EXERCISES_STATIC_FOLDER')}/{self.exercise_id}" - ) - - # expand variables in configuration paths - resource_folders = [ - os.path.expandvars(path) for path in self.resource_folders - ] - model_folders = [os.path.expandvars(path) for path in self.model_folders] - plugin_folders = [os.path.expandvars(path) for path in self.plugin_folders] - launch_file = os.path.expandvars(self.launch_file) - - env = dict(os.environ) - env["GAZEBO_RESOURCE_PATH"] = ( - f"{env.get('GAZEBO_RESOURCE_PATH', '')}:{':'.join(resource_folders)}" - ) - env["GAZEBO_MODEL_PATH"] = ( - f"{env.get('GAZEBO_MODEL_PATH', '')}:{':'.join(model_folders)}" - ) - env["GAZEBO_PLUGIN_PATH"] = ( - f"{env.get('GAZEBO_PLUGIN_PATH', '')}:{':'.join(plugin_folders)}" - ) - - parameters = " ".join(self.parameters) - command = f"{self.ros_command_line} {parameters} {launch_file}" - self.process = subprocess.Popen( - command, - env=env, - shell=True, - # stdin=subprocess.PIPE, - # stdout=subprocess.PIPE, - # stderr=subprocess.STDOUT - ) - # print(self.process.communicate()) - except Exception as ex: - traceback.print_exc() - raise ex - - def is_running(self): - return True if self.process.poll() is None else False - - def terminate(self): - if self.is_running(): - self.process.terminate() - else: - raise LauncherException("The process is not running") diff --git a/manager/manager/launcher/launcher_ros_api.py b/manager/manager/launcher/launcher_ros_api.py deleted file mode 100644 index b133936..0000000 --- a/manager/manager/launcher/launcher_ros_api.py +++ /dev/null @@ -1,82 +0,0 @@ -import os -import time -from typing import List, Any -from manager.manager.docker_thread.docker_thread import DockerThread -from manager.libs.process_utils import wait_for_xserver -from manager.libs.process_utils import wait_for_process_to_start -import roslaunch -import rospy - - -from manager.manager.launcher.launcher_interface import ILauncher, LauncherException - -import logging - - -class RosProcessListener(roslaunch.pmon.ProcessListener): - def __init__(self, *args, **kwargs): - self.callback = kwargs.get("callback", None) - - def process_died(self, name, exit_code): - print(f"ROS process {name} terminated with code {exit_code}") - if self.callback is not None: - self.callback(name, exit_code) - - -class LauncherRosApi(ILauncher): - type: str - module: str - launch_file: str - threads: List[Any] = [] - - # holder for roslaunch process - launch: Any = None - listener: Any = None - - def run(self, callback: callable = None): - logging.getLogger("roslaunch").setLevel(logging.CRITICAL) - - # Start X server in display - xserver_cmd = f"/usr/bin/Xorg -quiet -noreset +extension GLX +extension RANDR +extension RENDER -logfile ./xdummy.log -config ./xorg.conf :0" - xserver_thread = DockerThread(xserver_cmd) - xserver_thread.start() - wait_for_xserver(":0") - self.threads.append(xserver_thread) - - self.listener = RosProcessListener(callback=callback) - uuid = roslaunch.rlutil.get_or_generate_uuid(None, False) - roslaunch.configure_logging(uuid) - self.launch = roslaunch.parent.ROSLaunchParent( - uuid, [self.launch_file], process_listeners=[self.listener] - ) - self.launch.start() - - wait_for_process_to_start("rosmaster", timeout=60) - wait_for_process_to_start("gzserver", timeout=60) - - if not self.launch.pm.is_alive(): - raise LauncherException("Exception launching ROS") - - def is_running(self): - return self.launch.pm.is_alive() - - def wait_for_shutdown(self, timeout=30): - print("Waiting for ROS and Gazebo to shutdown") - start_time = rospy.Time.now().to_sec() - while not rospy.is_shutdown() and self.is_running(): - if rospy.Time.now().to_sec() - start_time > timeout: - print("Timeout while waiting for ROS and Gazebo to shutdown") - break - rospy.sleep(0.5) - - def terminate(self): - try: - for thread in self.threads: - if thread.is_alive(): - thread.terminate() - thread.join() - self.threads.remove(thread) - self.launch.shutdown() - self.wait_for_shutdown() - except Exception as e: - print("Exception shutting down ROS") diff --git a/manager/manager/launcher/launcher_teleoperator_ros2.py b/manager/manager/launcher/launcher_teleoperator_ros2.py deleted file mode 100644 index 409ad21..0000000 --- a/manager/manager/launcher/launcher_teleoperator_ros2.py +++ /dev/null @@ -1,39 +0,0 @@ -from manager.manager.launcher.launcher_interface import ILauncher -from manager.manager.docker_thread.docker_thread import DockerThread -import time -import os -import stat - - -class LauncherTeleoperatorRos2(ILauncher): - running = False - threads = [] - - def run(self, callback): - DRI_PATH = self.get_dri_path() - ACCELERATION_ENABLED = self.check_device(DRI_PATH) - - if ACCELERATION_ENABLED: - teleop_cmd = f"export VGL_DISPLAY={DRI_PATH}; vglrun python3 /opt/jderobot/utils/model_teleoperator.py 0.0.0.0" - else: - teleop_cmd = f"python3 /opt/jderobot/utils/model_teleoperator.py 0.0.0.0" - - teleop_thread = DockerThread(teleop_cmd) - teleop_thread.start() - self.threads.append(teleop_thread) - - self.running = True - - def is_running(self): - return self.running - - def terminate(self): - for thread in self.threads: - if thread.is_alive(): - thread.terminate() - thread.join() - self.threads.remove(thread) - self.running = False - - def died(self): - pass diff --git a/manager/manager/lint/__init__.py b/manager/manager/lint/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/manager/ram_logging/__init__.py b/manager/ram_logging/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..c245866 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,36 @@ +[project] +name = "robotics_application_manager" +version = "0.0.3" +authors = [ + { name="Example Author", email="author@example.com" }, +] +description = "Robotics Application Manager" +readme = "README.md" +requires-python = ">=3.9" +classifiers = [ + "Programming Language :: Python :: 3", + "Operating System :: OS Independent", +] +license = "GPL-3.0-only" +license-files = ["LICENSE*"] +dependencies = [ + "pydantic==2.4.2", + "transitions==0.9.0", + "pylint==3.3.1", + "websocket-client==1.5.2", + "argparse==1.4.0", + "six==1.16.0", + "psutil==5.9.0", + "watchdog==2.1.5", + "jedi", + "black==24.10.0", + "websocket_server==0.6.4" +] + +[project.urls] +Homepage = "https://github.com/JdeRobot/RoboticsApplicationManager" +Issues = "https://github.com/JdeRobot/RoboticsApplicationManager/issues" + +[build-system] +requires = ["setuptools >= 82.0.0"] +build-backend = "setuptools.build_meta" \ No newline at end of file diff --git a/robotics_application_manager.egg-info/PKG-INFO b/robotics_application_manager.egg-info/PKG-INFO new file mode 100644 index 0000000..e820770 --- /dev/null +++ b/robotics_application_manager.egg-info/PKG-INFO @@ -0,0 +1,169 @@ +Metadata-Version: 2.4 +Name: robotics_application_manager +Version: 0.0.3 +Summary: Robotics Application Manager +Author-email: Example Author +License-Expression: GPL-3.0-only +Project-URL: Homepage, https://github.com/JdeRobot/RoboticsApplicationManager +Project-URL: Issues, https://github.com/JdeRobot/RoboticsApplicationManager/issues +Classifier: Programming Language :: Python :: 3 +Classifier: Operating System :: OS Independent +Requires-Python: >=3.9 +Description-Content-Type: text/markdown +License-File: LICENSE +Requires-Dist: pydantic==2.4.2 +Requires-Dist: transitions==0.9.0 +Requires-Dist: pylint==3.3.1 +Requires-Dist: websocket-client==1.5.2 +Requires-Dist: argparse==1.4.0 +Requires-Dist: six==1.16.0 +Requires-Dist: psutil==5.9.0 +Requires-Dist: watchdog==2.1.5 +Requires-Dist: jedi +Requires-Dist: black==24.10.0 +Requires-Dist: websocket_server==0.6.4 +Dynamic: license-file + +# Robotics Application Manager (RAM) Documentation + +## Table of Contents + +1. [Project Overview](#project-overview) +2. [Main Class: `Manager`](#main-class-manager) + - [Purpose and Functionality](#purpose-and-functionality) + - [States and Transitions](#states-and-transitions) + - [Key Methods](#key-methods) + - [Interactions with Other Components](#interactions-with-other-components) +3. [Usage Examples](#usage-examples) + +## Project Overview + +The Robotics Application Manager (RAM) is an advanced manager for executing robotic applications. It operates as a state machine, managing the lifecycle of robotic applications from initialization to termination and uses the following ports to communicate: + +- **7063**: Connexion with other applications (Robotics Academy, BT Studio, Unibotics) +- **6080-6090**: Tools VNC + +## Main Class: `Manager` + +### Purpose and Functionality + +The `Manager` class is the core of RAM, orchestrating operations and managing transitions between various application states. + +### States and Transitions + +- **States**: + - `idle`: The initial state, waiting for a connection. + - `connected`: Connected and ready to initiate processes. + - `world_ready`: The world environment is set up and ready. + - `tools_ready`: Tools are prepared and ready. + - `application_running`: A robotic application is actively running. + - `paused`: The application is paused. +- **Transitions**: + - `connect`: Moves from `idle` to `connected`. + - `launch_world`: Initiates the world setup from `connected`. + - `prepare_tools`: Prepares the tools in `world_ready`. + - `run_application`: Starts the application in `tools_ready` or `paused`. + - `pause`: Pauses the running application. + - `resume`: Resumes a paused application. + - `terminate`: Stops the application and goes back to `tools_ready`. + - `stop`: Completely stops the application. + - `disconnect`: Disconnects from the current session and returns to `idle`. +- **Stateless Transitions**: + - `gui`: Redirects content to the gui webserver. + - `style_check`: Triggers on_style_check. + - `code_analysis`: Triggers on_code_analysis. + - `code_format`: Triggers on_code_format. + - `code_autocomplete`: Triggers on_code_autocomplete. + +### Key Methods + +- `on_connect(self, event)`: Manages the transition to the 'connected' state. +- `on_launch_world(self, event)`: Prepares and launches the robotic world. +- `on_prepare_tools(self, event)`: Sets up tools. +- `on_run_application(self, event)`: Executes the robotic application. +- `on_pause(self, msg)`: Pauses the running application. +- `on_resume(self, msg)`: Resumes the paused application. +- `on_terminate(self, event)`: Terminates the running application. +- `on_disconnect(self, event)`: Handles disconnection and cleanup. +- `on_style_check(self, event)`: Check the style of the user code. +- `on_code_analysis(self, event)`: Analyzes the style and format of the user code using pylint. +- `on_code_format(self, event)`: Formats the user code using black. +- `on_code_autocomplete(self, event)`: Searches for all available code completions using Jedi. +- **Exception Handling**: Details how specific errors are managed in each method. + +### Interactions with Other Components + +#### Interaction Between `Manager` and `ManagerConsumer` + +1. **Message Queue Integration**: `ManagerConsumer` puts received messages into `manager_queue` for `Manager` to process. +2. **State Updates and Commands**: `Manager` sends state updates or commands to the client through `ManagerConsumer`. +3. **Client Connection Handling**: `Manager` relies on `ManagerConsumer` for client connection and disconnection handling. +4. **Error Handling**: `ManagerConsumer` communicates exceptions back to the client and `Manager`. +5. **Lifecycle Management**: `Manager` controls the start and stop of the `ManagerConsumer` WebSocket server. + +#### Interaction Between `Manager` and `LauncherWorld` + +1. **World Initialization and Launching**: `Manager` initializes `LauncherWorld` with specific configurations, such as world type (e.g., `gazebo`, `drones`) and the launch file path. +2. **Dynamic Module Management**: `LauncherWorld` dynamically launches modules based on the world configuration and ROS version, as dictated by `Manager`. +3. **State Management and Transition**: The state of `Manager` is updated in response to the actions performed by `LauncherWorld`. For example, once the world is ready, `Manager` may transition to the `world_ready` state. +4. **Termination and Cleanup**: `Manager` can instruct `LauncherWorld` to terminate the world environment through its `terminate` method. `LauncherWorld` ensures a clean and orderly shutdown of all modules and resources involved in the world setup. +5. **Error Handling and Logging**: `Manager` handles exceptions and errors that may arise during the world setup or termination processes, ensuring robust operation. + +#### Interaction Between `Manager` and `LauncherTools` + +1. **Visualization Setup**: `Manager` initializes `LauncherTools` with a specific tools configuration, which can include tools like `console`, `simulator`, `web_gui`, etc. +2. **Module Launching for Tools**: `LauncherTools` dynamically launches tools modules based on the configuration provided by `Manager`. +3. **State Management and Synchronization**: Upon successful setup of the tools, `Manager` can update its state (e.g., to `tools_ready`) to reflect the readiness of the tools. +4. **Termination of Tools**: `Manager` can instruct `LauncherTools` to terminate the current tools setup using its `terminate` method. +5. **Error Handling and Logging**: `Manager` is equipped to manage exceptions and errors that might occur during the setup or termination of the tools. + +#### Interaction Between `Manager` and `application_process` + +1. **Application Execution**: `Manager` initiates the `application_process` when transitioning to the `application_running` state. +2. **Application Configuration and Launching**: Before launching the `application_process`, `Manager` configures the necessary parameters. +3. **Process Management**: `Manager` monitors and controls the `application_process`. +4. **Error Handling and Logging**: `Manager` is responsible for handling any errors or exceptions that occur during the execution of the `application_process`. +5. **State Synchronization**: The state of the `application_process` is closely synchronized with the state machine in `Manager`. + +#### Interaction Between `Manager` and `Server` (Specific to RoboticsAcademy Applications) (Now inside tool web_gui) + +1. **Dedicated WebSocket Server for GUI Updates**: `Server` is used exclusively for RoboticsAcademy applications that require real-time interaction with a web-based GUI. +2. **Client Communication for GUI Module**: For RoboticsAcademy applications with a GUI module, `Server` handles incoming and outgoing messages. +3. **Real-time Interaction and Feedback**: `Server` allows for real-time feedback and interaction within the browser-based GUI. +4. **Conditional Operation Based on Application Type**: `Manager` initializes and controls `Server` based on the specific needs of the RoboticsAcademy application being executed. +5. **Error Handling and Logging**: `Manager` ensures robust error handling for `Server`. + +## Usage Example + +1. **Connecting to RAM**: + + - Initially, the RAM is in the `idle` state. + - A client (e.g., a user interface or another system) connects to RAM, triggering the `connect` transition and moving RAM to the `connected` state. + +2. **Launching the World**: + + - Once connected, the client can request RAM to launch a robotic world by sending a `launch_world` command. + - RAM transitions to the `world_ready` state after successfully setting up the world environment. + +3. **Setting Up Tools**: + + - After the world is ready, the client requests RAM to prepare the tools with a `prepare_tools` command. + - RAM transitions to the `tools_ready` state, indicating that the tools are set up and ready. + +4. **Running an Application**: + + - The client then requests RAM to run a specific robotic application, moving RAM into the `application_running` state. + - The application executes, and RAM handles its process management, including monitoring and error handling. + +5. **Pausing and Resuming Application**: + + - The client can send `pause` and `resume` commands to RAM to control the application's execution. + - RAM transitions to the `paused` state when paused and returns to `application_running` upon resumption. + +6. **Stopping the Application**: + + - Finally, the client can send a `stop` command to halt the application. + - RAM stops the application and transitions back to the `tools_ready` state, ready for new commands. + +7. **Disconnecting**: + - Once all tasks are completed, the client can disconnect from RAM, which then returns to the `idle` state, ready for a new session. diff --git a/robotics_application_manager.egg-info/SOURCES.txt b/robotics_application_manager.egg-info/SOURCES.txt new file mode 100644 index 0000000..7b77dff --- /dev/null +++ b/robotics_application_manager.egg-info/SOURCES.txt @@ -0,0 +1,55 @@ +LICENSE +README.md +pyproject.toml +robotics_application_manager/__init__.py +robotics_application_manager.egg-info/PKG-INFO +robotics_application_manager.egg-info/SOURCES.txt +robotics_application_manager.egg-info/dependency_links.txt +robotics_application_manager.egg-info/requires.txt +robotics_application_manager.egg-info/top_level.txt +robotics_application_manager/comms/__init__.py +robotics_application_manager/comms/consumer_message.py +robotics_application_manager/comms/new_consumer.py +robotics_application_manager/comms/thread.py +robotics_application_manager/comms/websocket_server.py +robotics_application_manager/libs/__init__.py +robotics_application_manager/libs/file_watchdog.py +robotics_application_manager/libs/launch_world_model.py +robotics_application_manager/libs/process_utils.py +robotics_application_manager/libs/server.py +robotics_application_manager/libs/singleton.py +robotics_application_manager/manager/__init__.py +robotics_application_manager/manager/manager.py +robotics_application_manager/manager/docker_thread/__init__.py +robotics_application_manager/manager/docker_thread/docker_thread.py +robotics_application_manager/manager/editor/serializers.py +robotics_application_manager/manager/launcher/__init__.py +robotics_application_manager/manager/launcher/launcher_console.py +robotics_application_manager/manager/launcher/launcher_gazebo.py +robotics_application_manager/manager/launcher/launcher_gzsim.py +robotics_application_manager/manager/launcher/launcher_interface.py +robotics_application_manager/manager/launcher/launcher_o3de.py +robotics_application_manager/manager/launcher/launcher_o3de_api.py +robotics_application_manager/manager/launcher/launcher_robot.py +robotics_application_manager/manager/launcher/launcher_robot_ros2_api.py +robotics_application_manager/manager/launcher/launcher_ros2_api.py +robotics_application_manager/manager/launcher/launcher_rviz.py +robotics_application_manager/manager/launcher/launcher_rviz_ros2.py +robotics_application_manager/manager/launcher/launcher_state_monitor.py +robotics_application_manager/manager/launcher/launcher_tools.py +robotics_application_manager/manager/launcher/launcher_web_gui.py +robotics_application_manager/manager/launcher/launcher_world.py +robotics_application_manager/manager/lint/__init__.py +robotics_application_manager/manager/lint/linter.py +robotics_application_manager/manager/lint/pylint_checker.py +robotics_application_manager/manager/lint/pylint_checker_style.py +robotics_application_manager/manager/vnc/vnc_server.py +robotics_application_manager/ram_logging/__init__.py +robotics_application_manager/ram_logging/log_manager.py +test/test_connect_disconnect_transitions.py +test/test_connected_to_world_ready.py +test/test_resume_and_pause_transitions.py +test/test_terminate_transitions.py +test/test_tools_ready_to_application_running.py +test/test_utils.py +test/test_world_ready_to_tools_ready.py \ No newline at end of file diff --git a/manager/__init__.py b/robotics_application_manager.egg-info/dependency_links.txt similarity index 100% rename from manager/__init__.py rename to robotics_application_manager.egg-info/dependency_links.txt diff --git a/robotics_application_manager.egg-info/requires.txt b/robotics_application_manager.egg-info/requires.txt new file mode 100644 index 0000000..6a7cf61 --- /dev/null +++ b/robotics_application_manager.egg-info/requires.txt @@ -0,0 +1,11 @@ +pydantic==2.4.2 +transitions==0.9.0 +pylint==3.3.1 +websocket-client==1.5.2 +argparse==1.4.0 +six==1.16.0 +psutil==5.9.0 +watchdog==2.1.5 +jedi +black==24.10.0 +websocket_server==0.6.4 diff --git a/robotics_application_manager.egg-info/top_level.txt b/robotics_application_manager.egg-info/top_level.txt new file mode 100644 index 0000000..4835f1a --- /dev/null +++ b/robotics_application_manager.egg-info/top_level.txt @@ -0,0 +1 @@ +robotics_application_manager diff --git a/robotics_application_manager/__init__.py b/robotics_application_manager/__init__.py new file mode 100644 index 0000000..20532d0 --- /dev/null +++ b/robotics_application_manager/__init__.py @@ -0,0 +1,2 @@ +from .manager.manager import Manager +from .ram_logging.log_manager import LogManager diff --git a/manager/comms/__init__.py b/robotics_application_manager/comms/__init__.py similarity index 100% rename from manager/comms/__init__.py rename to robotics_application_manager/comms/__init__.py diff --git a/manager/comms/consumer_message.py b/robotics_application_manager/comms/consumer_message.py similarity index 100% rename from manager/comms/consumer_message.py rename to robotics_application_manager/comms/consumer_message.py diff --git a/manager/comms/new_consumer.py b/robotics_application_manager/comms/new_consumer.py similarity index 94% rename from manager/comms/new_consumer.py rename to robotics_application_manager/comms/new_consumer.py index 7c04b84..5bdf3be 100644 --- a/manager/comms/new_consumer.py +++ b/robotics_application_manager/comms/new_consumer.py @@ -18,16 +18,6 @@ from manager.ram_logging.log_manager import LogManager -class Client: - """Represents a client connected to the WebSocket server.""" - - def __init__(self, **kwargs): - """Initialize a Client instance with id, handler, and address.""" - self.id = kwargs["id"] - self.handler = kwargs["handler"] - self.address = kwargs["address"] - - class ManagerConsumer: """ Websocket server consumer for new Robotics Application Manager aka: RAM. diff --git a/manager/comms/thread.py b/robotics_application_manager/comms/thread.py similarity index 100% rename from manager/comms/thread.py rename to robotics_application_manager/comms/thread.py diff --git a/manager/comms/websocket_server.py b/robotics_application_manager/comms/websocket_server.py similarity index 100% rename from manager/comms/websocket_server.py rename to robotics_application_manager/comms/websocket_server.py diff --git a/manager/libs/__init__.py b/robotics_application_manager/libs/__init__.py similarity index 100% rename from manager/libs/__init__.py rename to robotics_application_manager/libs/__init__.py diff --git a/manager/libs/applications/compatibility/file_watchdog.py b/robotics_application_manager/libs/file_watchdog.py similarity index 100% rename from manager/libs/applications/compatibility/file_watchdog.py rename to robotics_application_manager/libs/file_watchdog.py diff --git a/manager/libs/launch_world_model.py b/robotics_application_manager/libs/launch_world_model.py similarity index 100% rename from manager/libs/launch_world_model.py rename to robotics_application_manager/libs/launch_world_model.py diff --git a/manager/libs/process_utils.py b/robotics_application_manager/libs/process_utils.py similarity index 100% rename from manager/libs/process_utils.py rename to robotics_application_manager/libs/process_utils.py diff --git a/manager/libs/applications/compatibility/server.py b/robotics_application_manager/libs/server.py similarity index 100% rename from manager/libs/applications/compatibility/server.py rename to robotics_application_manager/libs/server.py diff --git a/manager/libs/singleton.py b/robotics_application_manager/libs/singleton.py similarity index 100% rename from manager/libs/singleton.py rename to robotics_application_manager/libs/singleton.py diff --git a/manager/libs/applications/__init__.py b/robotics_application_manager/manager/__init__.py similarity index 100% rename from manager/libs/applications/__init__.py rename to robotics_application_manager/manager/__init__.py diff --git a/manager/libs/applications/compatibility/__init__.py b/robotics_application_manager/manager/docker_thread/__init__.py similarity index 100% rename from manager/libs/applications/compatibility/__init__.py rename to robotics_application_manager/manager/docker_thread/__init__.py diff --git a/manager/manager/docker_thread/docker_thread.py b/robotics_application_manager/manager/docker_thread/docker_thread.py similarity index 100% rename from manager/manager/docker_thread/docker_thread.py rename to robotics_application_manager/manager/docker_thread/docker_thread.py diff --git a/manager/manager/editor/serializers.py b/robotics_application_manager/manager/editor/serializers.py similarity index 100% rename from manager/manager/editor/serializers.py rename to robotics_application_manager/manager/editor/serializers.py diff --git a/manager/manager/__init__.py b/robotics_application_manager/manager/launcher/__init__.py similarity index 100% rename from manager/manager/__init__.py rename to robotics_application_manager/manager/launcher/__init__.py diff --git a/manager/manager/launcher/launcher_console.py b/robotics_application_manager/manager/launcher/launcher_console.py similarity index 100% rename from manager/manager/launcher/launcher_console.py rename to robotics_application_manager/manager/launcher/launcher_console.py diff --git a/manager/manager/launcher/launcher_gazebo.py b/robotics_application_manager/manager/launcher/launcher_gazebo.py similarity index 100% rename from manager/manager/launcher/launcher_gazebo.py rename to robotics_application_manager/manager/launcher/launcher_gazebo.py diff --git a/manager/manager/launcher/launcher_gzsim.py b/robotics_application_manager/manager/launcher/launcher_gzsim.py similarity index 100% rename from manager/manager/launcher/launcher_gzsim.py rename to robotics_application_manager/manager/launcher/launcher_gzsim.py diff --git a/manager/manager/launcher/launcher_interface.py b/robotics_application_manager/manager/launcher/launcher_interface.py similarity index 100% rename from manager/manager/launcher/launcher_interface.py rename to robotics_application_manager/manager/launcher/launcher_interface.py diff --git a/manager/manager/launcher/launcher_o3de.py b/robotics_application_manager/manager/launcher/launcher_o3de.py similarity index 100% rename from manager/manager/launcher/launcher_o3de.py rename to robotics_application_manager/manager/launcher/launcher_o3de.py diff --git a/manager/manager/launcher/launcher_o3de_api.py b/robotics_application_manager/manager/launcher/launcher_o3de_api.py similarity index 100% rename from manager/manager/launcher/launcher_o3de_api.py rename to robotics_application_manager/manager/launcher/launcher_o3de_api.py diff --git a/manager/manager/launcher/launcher_robot.py b/robotics_application_manager/manager/launcher/launcher_robot.py similarity index 100% rename from manager/manager/launcher/launcher_robot.py rename to robotics_application_manager/manager/launcher/launcher_robot.py diff --git a/manager/manager/launcher/launcher_robot_ros2_api.py b/robotics_application_manager/manager/launcher/launcher_robot_ros2_api.py similarity index 100% rename from manager/manager/launcher/launcher_robot_ros2_api.py rename to robotics_application_manager/manager/launcher/launcher_robot_ros2_api.py diff --git a/manager/manager/launcher/launcher_ros2_api.py b/robotics_application_manager/manager/launcher/launcher_ros2_api.py similarity index 100% rename from manager/manager/launcher/launcher_ros2_api.py rename to robotics_application_manager/manager/launcher/launcher_ros2_api.py diff --git a/manager/manager/launcher/launcher_rviz.py b/robotics_application_manager/manager/launcher/launcher_rviz.py similarity index 100% rename from manager/manager/launcher/launcher_rviz.py rename to robotics_application_manager/manager/launcher/launcher_rviz.py diff --git a/manager/manager/launcher/launcher_rviz_ros2.py b/robotics_application_manager/manager/launcher/launcher_rviz_ros2.py similarity index 100% rename from manager/manager/launcher/launcher_rviz_ros2.py rename to robotics_application_manager/manager/launcher/launcher_rviz_ros2.py diff --git a/manager/manager/launcher/launcher_state_monitor.py b/robotics_application_manager/manager/launcher/launcher_state_monitor.py similarity index 88% rename from manager/manager/launcher/launcher_state_monitor.py rename to robotics_application_manager/manager/launcher/launcher_state_monitor.py index 8e645b5..c2b9446 100644 --- a/manager/manager/launcher/launcher_state_monitor.py +++ b/robotics_application_manager/manager/launcher/launcher_state_monitor.py @@ -1,8 +1,8 @@ -from manager.libs.applications.compatibility.server import Server +from manager.libs.server import Server from manager.ram_logging.log_manager import LogManager from manager.comms.new_consumer import ManagerConsumer from typing import Optional -from manager.libs.applications.compatibility.file_watchdog import FileWatchdog +from manager.libs.file_watchdog import FileWatchdog class LauncherStateMonitor: diff --git a/manager/manager/launcher/launcher_tools.py b/robotics_application_manager/manager/launcher/launcher_tools.py similarity index 100% rename from manager/manager/launcher/launcher_tools.py rename to robotics_application_manager/manager/launcher/launcher_tools.py diff --git a/manager/manager/launcher/launcher_web_gui.py b/robotics_application_manager/manager/launcher/launcher_web_gui.py similarity index 94% rename from manager/manager/launcher/launcher_web_gui.py rename to robotics_application_manager/manager/launcher/launcher_web_gui.py index 6d5c9e3..367e003 100644 --- a/manager/manager/launcher/launcher_web_gui.py +++ b/robotics_application_manager/manager/launcher/launcher_web_gui.py @@ -1,4 +1,4 @@ -from manager.libs.applications.compatibility.server import Server +from manager.libs.server import Server from manager.ram_logging.log_manager import LogManager from manager.comms.new_consumer import ManagerConsumer from typing import Optional diff --git a/manager/manager/launcher/launcher_world.py b/robotics_application_manager/manager/launcher/launcher_world.py similarity index 100% rename from manager/manager/launcher/launcher_world.py rename to robotics_application_manager/manager/launcher/launcher_world.py diff --git a/manager/manager/application/__init__.py b/robotics_application_manager/manager/lint/__init__.py similarity index 100% rename from manager/manager/application/__init__.py rename to robotics_application_manager/manager/lint/__init__.py diff --git a/manager/manager/lint/linter.py b/robotics_application_manager/manager/lint/linter.py similarity index 100% rename from manager/manager/lint/linter.py rename to robotics_application_manager/manager/lint/linter.py diff --git a/manager/manager/lint/pylint_checker.py b/robotics_application_manager/manager/lint/pylint_checker.py similarity index 100% rename from manager/manager/lint/pylint_checker.py rename to robotics_application_manager/manager/lint/pylint_checker.py diff --git a/manager/manager/lint/pylint_checker_style.py b/robotics_application_manager/manager/lint/pylint_checker_style.py similarity index 100% rename from manager/manager/lint/pylint_checker_style.py rename to robotics_application_manager/manager/lint/pylint_checker_style.py diff --git a/manager/manager/lint/pylintrc b/robotics_application_manager/manager/lint/pylintrc similarity index 100% rename from manager/manager/lint/pylintrc rename to robotics_application_manager/manager/lint/pylintrc diff --git a/manager/manager/manager.py b/robotics_application_manager/manager/manager.py similarity index 98% rename from manager/manager/manager.py rename to robotics_application_manager/manager/manager.py index be85314..1fef216 100644 --- a/manager/manager/manager.py +++ b/robotics_application_manager/manager/manager.py @@ -35,16 +35,16 @@ from manager.comms.consumer_message import ManagerConsumerMessageException from manager.comms.new_consumer import ManagerConsumer -from manager.libs.process_utils import check_gpu_acceleration, get_class_from_file +from manager.libs.process_utils import ( + check_gpu_acceleration, + get_class_from_file, + stop_process_and_children, +) from manager.libs.launch_world_model import ConfigurationManager +from manager.ram_logging.log_manager import LogManager from manager.manager.launcher.launcher_world import LauncherWorld from manager.manager.launcher.launcher_robot import LauncherRobot from manager.manager.launcher.launcher_tools import LauncherTools -from manager.ram_logging.log_manager import LogManager -from manager.manager.application.robotics_python_application_interface import ( - IRoboticsPythonApplication, -) -from manager.libs.process_utils import stop_process_and_children from manager.manager.lint.linter import Lint from manager.manager.editor.serializers import serialize_completions @@ -442,6 +442,8 @@ def on_style_check_application(self, event): Raises: Exception: with the errors found in the linter """ + # TODO: redo + # Extract app config app_cfg = event.kwargs.get("data", {}) try: @@ -962,17 +964,3 @@ def signal_handler(sign, frame): ) self.consumer.send_message(ex) LogManager.logger.error(e, exc_info=True) - - -if __name__ == "__main__": - import argparse - - parser = argparse.ArgumentParser() - parser.add_argument( - "host", type=str, help="Host to listen to (0.0.0.0 or all hosts)" - ) - parser.add_argument("port", type=int, help="Port to listen to") - args = parser.parse_args() - - RAM = Manager(args.host, args.port) - RAM.start() diff --git a/manager/manager/vnc/vnc_server.py b/robotics_application_manager/manager/vnc/vnc_server.py similarity index 100% rename from manager/manager/vnc/vnc_server.py rename to robotics_application_manager/manager/vnc/vnc_server.py diff --git a/manager/manager/docker_thread/__init__.py b/robotics_application_manager/ram_logging/__init__.py similarity index 100% rename from manager/manager/docker_thread/__init__.py rename to robotics_application_manager/ram_logging/__init__.py diff --git a/manager/ram_logging/log_manager.py b/robotics_application_manager/ram_logging/log_manager.py similarity index 100% rename from manager/ram_logging/log_manager.py rename to robotics_application_manager/ram_logging/log_manager.py From 3c8ed3e4cda6d584db5817217b67c78068767121 Mon Sep 17 00:00:00 2001 From: Javier Izquierdo Hernandez Date: Wed, 18 Feb 2026 12:31:23 +0100 Subject: [PATCH 02/15] Fix links --- launch.py | 2 +- robotics_application_manager/__init__.py | 3 +-- .../comms/__init__.py | 4 ++++ .../comms/new_consumer.py | 6 ++--- .../comms/websocket_server.py | 2 +- robotics_application_manager/libs/__init__.py | 14 +++++++++++ .../libs/file_watchdog.py | 2 +- .../libs/process_utils.py | 2 +- robotics_application_manager/libs/server.py | 2 +- .../libs/singleton.py | 9 ------- .../manager/docker_thread/__init__.py | 1 + .../manager/editor/__init__.py | 1 + .../manager/launcher/__init__.py | 3 +++ .../manager/launcher/launcher_console.py | 8 +++---- .../manager/launcher/launcher_gazebo.py | 10 ++++---- .../manager/launcher/launcher_gzsim.py | 10 ++++---- .../manager/launcher/launcher_o3de.py | 13 +++++----- .../manager/launcher/launcher_o3de_api.py | 24 +++++++++++-------- .../manager/launcher/launcher_robot.py | 10 +++++--- .../launcher/launcher_robot_ros2_api.py | 7 ++++-- .../manager/launcher/launcher_ros2_api.py | 7 ++++-- .../manager/launcher/launcher_rviz.py | 17 +++++++------ .../manager/launcher/launcher_rviz_ros2.py | 6 ++--- .../launcher/launcher_state_monitor.py | 7 +++--- .../manager/launcher/launcher_tools.py | 7 +++--- .../manager/launcher/launcher_web_gui.py | 6 ++--- .../manager/launcher/launcher_world.py | 10 +++++--- .../manager/lint/__init__.py | 1 + .../manager/manager.py | 24 +++++++++++-------- .../manager/vnc/__init__.py | 1 + .../manager/vnc/vnc_server.py | 12 +++++----- .../ram_logging/__init__.py | 1 + .../ram_logging/log_manager.py | 11 ++++++++- 33 files changed, 144 insertions(+), 99 deletions(-) delete mode 100644 robotics_application_manager/libs/singleton.py create mode 100644 robotics_application_manager/manager/editor/__init__.py create mode 100644 robotics_application_manager/manager/vnc/__init__.py diff --git a/launch.py b/launch.py index d5c5604..c9e0f79 100644 --- a/launch.py +++ b/launch.py @@ -1,4 +1,4 @@ -from .robotics_application_manager import Manager +from robotics_application_manager.manager.manager import Manager if __name__ == "__main__": import argparse diff --git a/robotics_application_manager/__init__.py b/robotics_application_manager/__init__.py index 20532d0..bbb5a12 100644 --- a/robotics_application_manager/__init__.py +++ b/robotics_application_manager/__init__.py @@ -1,2 +1 @@ -from .manager.manager import Manager -from .ram_logging.log_manager import LogManager +from robotics_application_manager.ram_logging.log_manager import LogManager diff --git a/robotics_application_manager/comms/__init__.py b/robotics_application_manager/comms/__init__.py index e69de29..6671462 100644 --- a/robotics_application_manager/comms/__init__.py +++ b/robotics_application_manager/comms/__init__.py @@ -0,0 +1,4 @@ +from .new_consumer import ManagerConsumer +from .consumer_message import ManagerConsumerMessageException, ManagerConsumerMessage +from .thread import ThreadWithLoggedException, WebsocketServerThread +from .websocket_server import WebsocketServer diff --git a/robotics_application_manager/comms/new_consumer.py b/robotics_application_manager/comms/new_consumer.py index 5bdf3be..0f2568c 100644 --- a/robotics_application_manager/comms/new_consumer.py +++ b/robotics_application_manager/comms/new_consumer.py @@ -10,12 +10,12 @@ from uuid import uuid4 from datetime import datetime -from manager.comms.consumer_message import ( +from .consumer_message import ( ManagerConsumerMessageException, ManagerConsumerMessage, ) -from manager.comms.websocket_server import WebsocketServer -from manager.ram_logging.log_manager import LogManager +from .websocket_server import WebsocketServer +from robotics_application_manager import LogManager class ManagerConsumer: diff --git a/robotics_application_manager/comms/websocket_server.py b/robotics_application_manager/comms/websocket_server.py index 70479bd..0a67118 100644 --- a/robotics_application_manager/comms/websocket_server.py +++ b/robotics_application_manager/comms/websocket_server.py @@ -14,7 +14,7 @@ import threading from socketserver import ThreadingMixIn, TCPServer, StreamRequestHandler -from manager.comms.thread import WebsocketServerThread +from .thread import WebsocketServerThread logger = logging.getLogger(__name__) logging.basicConfig() diff --git a/robotics_application_manager/libs/__init__.py b/robotics_application_manager/libs/__init__.py index e69de29..9dd88f8 100644 --- a/robotics_application_manager/libs/__init__.py +++ b/robotics_application_manager/libs/__init__.py @@ -0,0 +1,14 @@ +from .file_watchdog import FileWatchdog +from .launch_world_model import ConfigurationModel, ConfigurationManager +from .process_utils import ( + get_ros_version, + check_gpu_acceleration, + get_user_world, + wait_for_xserver, + wait_for_process_to_start, + stop_process_and_children, + class_from_module, + get_class_from_file, + get_class, +) +from .server import Server diff --git a/robotics_application_manager/libs/file_watchdog.py b/robotics_application_manager/libs/file_watchdog.py index 7cc9231..6545f49 100644 --- a/robotics_application_manager/libs/file_watchdog.py +++ b/robotics_application_manager/libs/file_watchdog.py @@ -4,7 +4,7 @@ from watchdog.events import FileSystemEventHandler import watchdog.observers -from manager.ram_logging.log_manager import LogManager +from robotics_application_manager import LogManager class Handler(FileSystemEventHandler): diff --git a/robotics_application_manager/libs/process_utils.py b/robotics_application_manager/libs/process_utils.py index b43a704..ecc61c7 100644 --- a/robotics_application_manager/libs/process_utils.py +++ b/robotics_application_manager/libs/process_utils.py @@ -11,7 +11,7 @@ import psutil -from manager.ram_logging.log_manager import LogManager +from robotics_application_manager import LogManager def get_class(kls): diff --git a/robotics_application_manager/libs/server.py b/robotics_application_manager/libs/server.py index d7b1f7c..7bb488c 100644 --- a/robotics_application_manager/libs/server.py +++ b/robotics_application_manager/libs/server.py @@ -3,7 +3,7 @@ from websocket_server import WebsocketServer -from manager.ram_logging.log_manager import LogManager +from robotics_application_manager import LogManager class Server(threading.Thread): diff --git a/robotics_application_manager/libs/singleton.py b/robotics_application_manager/libs/singleton.py deleted file mode 100644 index aceb996..0000000 --- a/robotics_application_manager/libs/singleton.py +++ /dev/null @@ -1,9 +0,0 @@ -def singleton(cls): - instances = {} - - def get_instance(): - if cls not in instances: - instances[cls] = cls() - return instances[cls] - - return get_instance() diff --git a/robotics_application_manager/manager/docker_thread/__init__.py b/robotics_application_manager/manager/docker_thread/__init__.py index e69de29..dd99779 100644 --- a/robotics_application_manager/manager/docker_thread/__init__.py +++ b/robotics_application_manager/manager/docker_thread/__init__.py @@ -0,0 +1 @@ +from .docker_thread import DockerThread diff --git a/robotics_application_manager/manager/editor/__init__.py b/robotics_application_manager/manager/editor/__init__.py new file mode 100644 index 0000000..2fcbebd --- /dev/null +++ b/robotics_application_manager/manager/editor/__init__.py @@ -0,0 +1 @@ +from .serializers import serialize_completions diff --git a/robotics_application_manager/manager/launcher/__init__.py b/robotics_application_manager/manager/launcher/__init__.py index e69de29..39ed308 100644 --- a/robotics_application_manager/manager/launcher/__init__.py +++ b/robotics_application_manager/manager/launcher/__init__.py @@ -0,0 +1,3 @@ +from .launcher_tools import LauncherTools +from .launcher_world import LauncherWorld +from .launcher_robot import LauncherRobot diff --git a/robotics_application_manager/manager/launcher/launcher_console.py b/robotics_application_manager/manager/launcher/launcher_console.py index 0611fef..af94190 100644 --- a/robotics_application_manager/manager/launcher/launcher_console.py +++ b/robotics_application_manager/manager/launcher/launcher_console.py @@ -1,7 +1,7 @@ -from manager.manager.launcher.launcher_interface import ILauncher -from manager.manager.docker_thread.docker_thread import DockerThread -from manager.manager.vnc.vnc_server import Vnc_server -from manager.libs.process_utils import check_gpu_acceleration +from .launcher_interface import ILauncher +from robotics_application_manager.manager.docker_thread import DockerThread +from robotics_application_manager.manager.vnc import Vnc_server +from robotics_application_manager.libs import check_gpu_acceleration import os import stat from typing import List, Any diff --git a/robotics_application_manager/manager/launcher/launcher_gazebo.py b/robotics_application_manager/manager/launcher/launcher_gazebo.py index f7f6333..5002cef 100644 --- a/robotics_application_manager/manager/launcher/launcher_gazebo.py +++ b/robotics_application_manager/manager/launcher/launcher_gazebo.py @@ -1,13 +1,13 @@ import sys -from manager.manager.launcher.launcher_interface import ILauncher -from manager.manager.docker_thread.docker_thread import DockerThread -from manager.manager.vnc.vnc_server import Vnc_server -from manager.libs.process_utils import ( +from .launcher_interface import ILauncher +from robotics_application_manager.manager.docker_thread import DockerThread +from robotics_application_manager.manager.vnc import Vnc_server +from robotics_application_manager.libs import ( wait_for_process_to_start, ) import subprocess from typing import List, Any -from manager.ram_logging.log_manager import LogManager +from robotics_application_manager import LogManager def call_service(service, service_type, request_data="{}"): diff --git a/robotics_application_manager/manager/launcher/launcher_gzsim.py b/robotics_application_manager/manager/launcher/launcher_gzsim.py index 053cafa..261b19a 100644 --- a/robotics_application_manager/manager/launcher/launcher_gzsim.py +++ b/robotics_application_manager/manager/launcher/launcher_gzsim.py @@ -1,8 +1,8 @@ import sys -from manager.manager.launcher.launcher_interface import ILauncher -from manager.manager.docker_thread.docker_thread import DockerThread -from manager.manager.vnc.vnc_server import Vnc_server -from manager.libs.process_utils import ( +from .launcher_interface import ILauncher +from robotics_application_manager.manager.docker_thread import DockerThread +from robotics_application_manager.manager.vnc import Vnc_server +from robotics_application_manager.libs import ( wait_for_process_to_start, check_gpu_acceleration, ) @@ -11,7 +11,7 @@ import os import stat from typing import List, Any -from manager.ram_logging.log_manager import LogManager +from robotics_application_manager import LogManager def call_gzservice(service, reqtype, reptype, timeout, req): diff --git a/robotics_application_manager/manager/launcher/launcher_o3de.py b/robotics_application_manager/manager/launcher/launcher_o3de.py index f633dc0..0bb9af1 100644 --- a/robotics_application_manager/manager/launcher/launcher_o3de.py +++ b/robotics_application_manager/manager/launcher/launcher_o3de.py @@ -1,8 +1,8 @@ import sys -from manager.manager.launcher.launcher_interface import ILauncher -from manager.manager.docker_thread.docker_thread import DockerThread -from manager.manager.vnc.vnc_server import Vnc_server -from manager.libs.process_utils import ( +from .launcher_interface import ILauncher +from robotics_application_manager.manager.docker_thread import DockerThread +from robotics_application_manager.manager.vnc import Vnc_server +from robotics_application_manager.libs import ( wait_for_process_to_start, check_gpu_acceleration, ) @@ -11,7 +11,8 @@ import os import stat from typing import List, Any -from manager.ram_logging.log_manager import LogManager +from robotics_application_manager import LogManager + class LauncherO3de(ILauncher): running: bool = False @@ -50,7 +51,7 @@ def unpause(self): pass def reset(self): - #TODO: add reset + # TODO: add reset pass def get_dri_path(self): diff --git a/robotics_application_manager/manager/launcher/launcher_o3de_api.py b/robotics_application_manager/manager/launcher/launcher_o3de_api.py index b5c8697..5824c6b 100644 --- a/robotics_application_manager/manager/launcher/launcher_o3de_api.py +++ b/robotics_application_manager/manager/launcher/launcher_o3de_api.py @@ -4,10 +4,13 @@ import time import stat -from manager.manager.launcher.launcher_interface import ILauncher, LauncherException -from manager.manager.docker_thread.docker_thread import DockerThread -from manager.manager.vnc.vnc_server import Vnc_server -from manager.libs.process_utils import ( +from .launcher_interface import ( + ILauncher, + LauncherException, +) +from robotics_application_manager.manager.docker_thread import DockerThread +from robotics_application_manager.manager.vnc import Vnc_server +from robotics_application_manager.libs import ( wait_for_process_to_start, check_gpu_acceleration, ) @@ -15,6 +18,7 @@ import logging + class LauncherO3deApi(ILauncher): display: str internal_port: int @@ -31,19 +35,19 @@ def run(self, callback): DRI_PATH = self.get_dri_path() ACCELERATION_ENABLED = self.check_device(DRI_PATH) - #TODO: add run here + # TODO: add run here xserver_cmd = f"/usr/bin/Xorg -quiet -noreset +extension GLX +extension RANDR +extension RENDER -logfile ./xdummy.log -config ./xorg.conf :0" xserver_thread = DockerThread(xserver_cmd) xserver_thread.start() self.threads.append(xserver_thread) - - LevelSelect=f'echo "LoadLevel Levels/{self.launch_file}" > data/workspace/ROS2Demo/autoexec.cfg' - + + LevelSelect = f'echo "LoadLevel Levels/{self.launch_file}" > data/workspace/ROS2Demo/autoexec.cfg' + LevelSelect_thread = DockerThread(LevelSelect) LevelSelect_thread.start() self.threads.append(LevelSelect_thread) - + if ACCELERATION_ENABLED: # Starts xserver, x11vnc and novnc self.gz_vnc.start_vnc_gpu( @@ -61,7 +65,7 @@ def run(self, callback): gzclient_thread.start() self.threads.append(gzclient_thread) - process_name = 'ROS2Demo.GameLauncher' + process_name = "ROS2Demo.GameLauncher" wait_for_process_to_start(process_name, timeout=360) def terminate(self): diff --git a/robotics_application_manager/manager/launcher/launcher_robot.py b/robotics_application_manager/manager/launcher/launcher_robot.py index a00c21a..800cd97 100644 --- a/robotics_application_manager/manager/launcher/launcher_robot.py +++ b/robotics_application_manager/manager/launcher/launcher_robot.py @@ -3,9 +3,13 @@ from typing import Optional from pydantic import BaseModel -from manager.libs.process_utils import get_class, class_from_module, get_ros_version -from manager.ram_logging.log_manager import LogManager -from manager.manager.launcher.launcher_interface import ILauncher +from robotics_application_manager.libs import ( + get_class, + class_from_module, + get_ros_version, +) +from robotics_application_manager import LogManager +from .launcher_interface import ILauncher worlds = { "gazebo": { diff --git a/robotics_application_manager/manager/launcher/launcher_robot_ros2_api.py b/robotics_application_manager/manager/launcher/launcher_robot_ros2_api.py index 9ccffe5..eb25b48 100644 --- a/robotics_application_manager/manager/launcher/launcher_robot_ros2_api.py +++ b/robotics_application_manager/manager/launcher/launcher_robot_ros2_api.py @@ -3,8 +3,11 @@ import time import stat -from manager.manager.launcher.launcher_interface import ILauncher, LauncherException -from manager.manager.docker_thread.docker_thread import DockerThread +from .launcher_interface import ( + ILauncher, + LauncherException, +) +from robotics_application_manager.manager.docker_thread import DockerThread import subprocess import logging diff --git a/robotics_application_manager/manager/launcher/launcher_ros2_api.py b/robotics_application_manager/manager/launcher/launcher_ros2_api.py index 4263cce..4840dce 100644 --- a/robotics_application_manager/manager/launcher/launcher_ros2_api.py +++ b/robotics_application_manager/manager/launcher/launcher_ros2_api.py @@ -4,8 +4,11 @@ import time import stat -from manager.manager.launcher.launcher_interface import ILauncher, LauncherException -from manager.manager.docker_thread.docker_thread import DockerThread +from .launcher_interface import ( + ILauncher, + LauncherException, +) +from robotics_application_manager.manager.docker_thread import DockerThread import subprocess import logging diff --git a/robotics_application_manager/manager/launcher/launcher_rviz.py b/robotics_application_manager/manager/launcher/launcher_rviz.py index 7ced22c..63643b4 100644 --- a/robotics_application_manager/manager/launcher/launcher_rviz.py +++ b/robotics_application_manager/manager/launcher/launcher_rviz.py @@ -1,7 +1,7 @@ -from manager.manager.launcher.launcher_interface import ILauncher -from manager.manager.docker_thread.docker_thread import DockerThread -from manager.manager.vnc.vnc_server import Vnc_server -from manager.libs.process_utils import check_gpu_acceleration +from .launcher_interface import ILauncher +from robotics_application_manager.manager.docker_thread import DockerThread +from robotics_application_manager.manager.vnc import Vnc_server +from robotics_application_manager.libs import check_gpu_acceleration import os import stat from typing import List, Any @@ -23,8 +23,8 @@ def run(self, config_file, callback): config = "ros2 run rviz2 rviz2" if config_file != None: - config = f'ros2 launch {config_file}' - + config = f"ros2 launch {config_file}" + print(config) if ACCELERATION_ENABLED: self.console_vnc.start_vnc_gpu( @@ -69,7 +69,6 @@ def terminate(self): def died(self): pass - # rviz_node_full = Node( # package="rviz2", # executable="rviz2", @@ -80,7 +79,7 @@ def died(self): # robot_description, # robot_description_semantic, # kinematics_yaml, - + # pilz_planning_pipeline_config, # joint_limits, @@ -92,4 +91,4 @@ def died(self): # move_group_capabilities, # {"use_sim_time": True}, # ] - # ) \ No newline at end of file + # ) diff --git a/robotics_application_manager/manager/launcher/launcher_rviz_ros2.py b/robotics_application_manager/manager/launcher/launcher_rviz_ros2.py index 06fb629..97728ba 100755 --- a/robotics_application_manager/manager/launcher/launcher_rviz_ros2.py +++ b/robotics_application_manager/manager/launcher/launcher_rviz_ros2.py @@ -1,6 +1,6 @@ -from manager.manager.launcher.launcher_interface import ILauncher -from manager.manager.docker_thread.docker_thread import DockerThread -from manager.manager.vnc.vnc_server import Vnc_server +from .launcher_interface import ILauncher +from robotics_application_manager.manager.docker_thread import DockerThread +from robotics_application_manager.manager.vnc import Vnc_server import os import stat diff --git a/robotics_application_manager/manager/launcher/launcher_state_monitor.py b/robotics_application_manager/manager/launcher/launcher_state_monitor.py index c2b9446..2785fb8 100644 --- a/robotics_application_manager/manager/launcher/launcher_state_monitor.py +++ b/robotics_application_manager/manager/launcher/launcher_state_monitor.py @@ -1,8 +1,7 @@ -from manager.libs.server import Server -from manager.ram_logging.log_manager import LogManager -from manager.comms.new_consumer import ManagerConsumer +from robotics_application_manager.libs import Server, FileWatchdog +from robotics_application_manager import LogManager +from robotics_application_manager.comms import ManagerConsumer from typing import Optional -from manager.libs.file_watchdog import FileWatchdog class LauncherStateMonitor: diff --git a/robotics_application_manager/manager/launcher/launcher_tools.py b/robotics_application_manager/manager/launcher/launcher_tools.py index e7ea628..2151a16 100644 --- a/robotics_application_manager/manager/launcher/launcher_tools.py +++ b/robotics_application_manager/manager/launcher/launcher_tools.py @@ -1,11 +1,10 @@ -from manager.libs.process_utils import get_class, class_from_module +from robotics_application_manager.libs import get_class, class_from_module from typing import Optional from pydantic import BaseModel -from manager.libs.process_utils import get_class, class_from_module -from manager.ram_logging.log_manager import LogManager -from manager.manager.launcher.launcher_interface import ILauncher +from robotics_application_manager import LogManager +from .launcher_interface import ILauncher tools = { "console": { diff --git a/robotics_application_manager/manager/launcher/launcher_web_gui.py b/robotics_application_manager/manager/launcher/launcher_web_gui.py index 367e003..1e4f6db 100644 --- a/robotics_application_manager/manager/launcher/launcher_web_gui.py +++ b/robotics_application_manager/manager/launcher/launcher_web_gui.py @@ -1,6 +1,6 @@ -from manager.libs.server import Server -from manager.ram_logging.log_manager import LogManager -from manager.comms.new_consumer import ManagerConsumer +from robotics_application_manager.libs import Server +from robotics_application_manager import LogManager +from robotics_application_manager.comms import ManagerConsumer from typing import Optional diff --git a/robotics_application_manager/manager/launcher/launcher_world.py b/robotics_application_manager/manager/launcher/launcher_world.py index d9ba862..17e4abb 100644 --- a/robotics_application_manager/manager/launcher/launcher_world.py +++ b/robotics_application_manager/manager/launcher/launcher_world.py @@ -1,9 +1,13 @@ from typing import Optional from pydantic import BaseModel -from manager.libs.process_utils import get_class, class_from_module, get_ros_version -from manager.ram_logging.log_manager import LogManager -from manager.manager.launcher.launcher_interface import ILauncher +from robotics_application_manager.libs import ( + get_class, + class_from_module, + get_ros_version, +) +from robotics_application_manager import LogManager +from .launcher_interface import ILauncher worlds = { "gazebo": { diff --git a/robotics_application_manager/manager/lint/__init__.py b/robotics_application_manager/manager/lint/__init__.py index e69de29..6b131a5 100644 --- a/robotics_application_manager/manager/lint/__init__.py +++ b/robotics_application_manager/manager/lint/__init__.py @@ -0,0 +1 @@ +from .linter import Lint diff --git a/robotics_application_manager/manager/manager.py b/robotics_application_manager/manager/manager.py index 1fef216..ab41b84 100644 --- a/robotics_application_manager/manager/manager.py +++ b/robotics_application_manager/manager/manager.py @@ -33,20 +33,24 @@ from transitions import Machine -from manager.comms.consumer_message import ManagerConsumerMessageException -from manager.comms.new_consumer import ManagerConsumer -from manager.libs.process_utils import ( +from robotics_application_manager.comms import ( + ManagerConsumerMessageException, + ManagerConsumer, +) +from robotics_application_manager.libs import ( check_gpu_acceleration, get_class_from_file, stop_process_and_children, + ConfigurationManager, +) +from robotics_application_manager.ram_logging import LogManager +from robotics_application_manager.manager.launcher import ( + LauncherWorld, + LauncherRobot, + LauncherTools, ) -from manager.libs.launch_world_model import ConfigurationManager -from manager.ram_logging.log_manager import LogManager -from manager.manager.launcher.launcher_world import LauncherWorld -from manager.manager.launcher.launcher_robot import LauncherRobot -from manager.manager.launcher.launcher_tools import LauncherTools -from manager.manager.lint.linter import Lint -from manager.manager.editor.serializers import serialize_completions +from robotics_application_manager.manager.lint import Lint +from robotics_application_manager.manager.editor import serialize_completions class Manager: diff --git a/robotics_application_manager/manager/vnc/__init__.py b/robotics_application_manager/manager/vnc/__init__.py new file mode 100644 index 0000000..690fa0b --- /dev/null +++ b/robotics_application_manager/manager/vnc/__init__.py @@ -0,0 +1 @@ +from .vnc_server import Vnc_server diff --git a/robotics_application_manager/manager/vnc/vnc_server.py b/robotics_application_manager/manager/vnc/vnc_server.py index 16c6372..7085233 100755 --- a/robotics_application_manager/manager/vnc/vnc_server.py +++ b/robotics_application_manager/manager/vnc/vnc_server.py @@ -6,11 +6,11 @@ import time import socket -from manager.manager.docker_thread.docker_thread import DockerThread +from robotics_application_manager.manager.docker_thread import DockerThread import subprocess from typing import List, Any import os -from manager.libs.process_utils import wait_for_xserver +from robotics_application_manager.libs import wait_for_xserver class Vnc_server: @@ -40,9 +40,9 @@ def start_vnc(self, display, internal_port, external_port): wait_for_xserver(display) certs = "" - + if os.path.isfile("/etc/certs/cert.pem"): - certs = "--cert /etc/certs/cert.pem --key /etc/certs/privkey.pem" + certs = "--cert /etc/certs/cert.pem --key /etc/certs/privkey.pem" # Start noVNC with default port 6080 listening to VNC server on 5900 if self.get_ros_version() == "2": @@ -89,9 +89,9 @@ def start_vnc_gpu(self, display, internal_port, external_port, dri_path): wait_for_xserver(display) certs = "" - + if os.path.isfile("/etc/certs/cert.pem"): - certs = "--cert /etc/certs/cert.pem --key /etc/certs/privkey.pem" + certs = "--cert /etc/certs/cert.pem --key /etc/certs/privkey.pem" # Start noVNC with default port 6080 listening to VNC server on 5900 if self.get_ros_version() == "2": diff --git a/robotics_application_manager/ram_logging/__init__.py b/robotics_application_manager/ram_logging/__init__.py index e69de29..f85cd73 100644 --- a/robotics_application_manager/ram_logging/__init__.py +++ b/robotics_application_manager/ram_logging/__init__.py @@ -0,0 +1 @@ +from .log_manager import LogManager diff --git a/robotics_application_manager/ram_logging/log_manager.py b/robotics_application_manager/ram_logging/log_manager.py index ad7d149..30aec29 100644 --- a/robotics_application_manager/ram_logging/log_manager.py +++ b/robotics_application_manager/ram_logging/log_manager.py @@ -7,7 +7,16 @@ import logging import os -from manager.libs.singleton import singleton + +def singleton(cls): + instances = {} + + def get_instance(): + if cls not in instances: + instances[cls] = cls() + return instances[cls] + + return get_instance() # Clase para un Formatter personalizado que aƱade colores From 15d365ea28dc4f0ecf021f196d5c9081f9a480ef Mon Sep 17 00:00:00 2001 From: Javier Izquierdo Hernandez Date: Wed, 18 Feb 2026 14:57:10 +0100 Subject: [PATCH 03/15] Add back manager --- launch.py | 14 -------------- .../manager/manager.py | 18 ++++++++++++++++++ 2 files changed, 18 insertions(+), 14 deletions(-) delete mode 100644 launch.py diff --git a/launch.py b/launch.py deleted file mode 100644 index c9e0f79..0000000 --- a/launch.py +++ /dev/null @@ -1,14 +0,0 @@ -from robotics_application_manager.manager.manager import Manager - -if __name__ == "__main__": - import argparse - - parser = argparse.ArgumentParser() - parser.add_argument( - "host", type=str, help="Host to listen to (0.0.0.0 or all hosts)" - ) - parser.add_argument("port", type=int, help="Port to listen to") - args = parser.parse_args() - - RAM = Manager(args.host, args.port) - RAM.start() diff --git a/robotics_application_manager/manager/manager.py b/robotics_application_manager/manager/manager.py index ab41b84..f3acb3d 100644 --- a/robotics_application_manager/manager/manager.py +++ b/robotics_application_manager/manager/manager.py @@ -777,6 +777,7 @@ def on_disconnect(self, event): This method stops all running processes, terminates launchers, and restarts the script. """ + LogManager.logger.exception("Disconected") try: self.consumer.stop() @@ -801,6 +802,7 @@ def on_disconnect(self, event): self.robot_launcher.terminate() except Exception as e: LogManager.logger.exception("Exception terminating robot launcher") + if self.world_launcher: try: self.world_launcher.terminate() @@ -912,6 +914,8 @@ def start(self): self.consumer.start() def signal_handler(sign, frame): + LogManager.logger.exception("why") + print("\nprogram exiting gracefully") self.running = False @@ -968,3 +972,17 @@ def signal_handler(sign, frame): ) self.consumer.send_message(ex) LogManager.logger.error(e, exc_info=True) + + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser() + parser.add_argument( + "host", type=str, help="Host to listen to (0.0.0.0 or all hosts)" + ) + parser.add_argument("port", type=int, help="Port to listen to") + args = parser.parse_args() + + RAM = Manager(args.host, args.port) + RAM.start() From e6fc028c2c6fbcbdf03217a685e2c1b338e21fd4 Mon Sep 17 00:00:00 2001 From: Javier Izquierdo Hernandez Date: Wed, 18 Feb 2026 14:57:20 +0100 Subject: [PATCH 04/15] Fix typo --- robotics_application_manager/manager/manager.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/robotics_application_manager/manager/manager.py b/robotics_application_manager/manager/manager.py index f3acb3d..6eae1b3 100644 --- a/robotics_application_manager/manager/manager.py +++ b/robotics_application_manager/manager/manager.py @@ -777,7 +777,6 @@ def on_disconnect(self, event): This method stops all running processes, terminates launchers, and restarts the script. """ - LogManager.logger.exception("Disconected") try: self.consumer.stop() @@ -914,8 +913,6 @@ def start(self): self.consumer.start() def signal_handler(sign, frame): - LogManager.logger.exception("why") - print("\nprogram exiting gracefully") self.running = False From b677e742ccbdaa2234d956e823e0b4aee211f0ac Mon Sep 17 00:00:00 2001 From: Javier Izquierdo Hernandez Date: Fri, 20 Feb 2026 12:11:06 +0100 Subject: [PATCH 05/15] Fix reset --- robotics_application_manager/manager/manager.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/robotics_application_manager/manager/manager.py b/robotics_application_manager/manager/manager.py index 6eae1b3..d4cb64e 100644 --- a/robotics_application_manager/manager/manager.py +++ b/robotics_application_manager/manager/manager.py @@ -754,6 +754,7 @@ def on_terminate_application(self, event): def on_terminate_tools(self, event): self.tools_launcher.terminate() + self.tools_launcher = None def on_terminate_universe(self, event): """ @@ -767,8 +768,11 @@ def on_terminate_universe(self, event): """ if self.world_launcher is not None: self.world_launcher.terminate() + self.world_launcher = None + self.world_type = None if self.robot_launcher is not None: self.robot_launcher.terminate() + self.robot_launcher = None def on_disconnect(self, event): """ @@ -778,11 +782,6 @@ def on_disconnect(self, event): terminates launchers, and restarts the script. """ - try: - self.consumer.stop() - except Exception as e: - LogManager.logger.exception("Exception stopping consumer") - if self.application_process: try: stop_process_and_children(self.application_process) @@ -808,10 +807,6 @@ def on_disconnect(self, event): except Exception as e: LogManager.logger.exception("Exception terminating world launcher") - # Reiniciar el script - python = sys.executable - os.execl(python, python, *sys.argv) - def process_message(self, message): if message.command == "gui": self.tools_launcher.pass_msg(message.data) From 224a718472f5850a812a2b00709b37c3164d1568 Mon Sep 17 00:00:00 2001 From: Javier Izquierdo Hernandez Date: Fri, 20 Feb 2026 13:04:03 +0100 Subject: [PATCH 06/15] Fix imports --- robotics_application_manager/manager/manager.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/robotics_application_manager/manager/manager.py b/robotics_application_manager/manager/manager.py index d4cb64e..14cce10 100644 --- a/robotics_application_manager/manager/manager.py +++ b/robotics_application_manager/manager/manager.py @@ -5,20 +5,16 @@ and handling code analysis and formatting. """ -from __future__ import annotations -import json import sys -import tempfile - -import black - sys.path.insert(0, "/RoboticsApplicationManager") +import json +import tempfile +import black import os import signal import subprocess -import sys import re import psutil import shutil @@ -26,13 +22,11 @@ import base64 import zipfile import jedi - import traceback + from queue import Queue from uuid import uuid4 - from transitions import Machine - from robotics_application_manager.comms import ( ManagerConsumerMessageException, ManagerConsumer, From 85cb8b205682bf23fd1b3f66e75d413bb8c11190 Mon Sep 17 00:00:00 2001 From: Javier Izquierdo Date: Fri, 20 Feb 2026 19:15:54 +0100 Subject: [PATCH 07/15] Create python-publish.yml --- .github/workflows/python-publish.yml | 73 ++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 .github/workflows/python-publish.yml diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml new file mode 100644 index 0000000..618cb38 --- /dev/null +++ b/.github/workflows/python-publish.yml @@ -0,0 +1,73 @@ +# This workflow will upload a Python Package to PyPI when a release is created +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries + +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +name: Upload Python Package + +# on: +# release: +# types: [published] + +on: + workflow_dispatch: + +permissions: + contents: read + +jobs: + release-build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.10" + + - name: Build release distributions + run: | + # NOTE: put your own distribution build steps here. + python -m pip install build + python -m build + + - name: Upload distributions + uses: actions/upload-artifact@v4 + with: + name: release-dists + path: dist/ + + pypi-publish: + runs-on: ubuntu-latest + needs: + - release-build + permissions: + # IMPORTANT: this permission is mandatory for trusted publishing + id-token: write + + # Dedicated environments with protections for publishing are strongly recommended. + # For more information, see: https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment#deployment-protection-rules + environment: + name: pypi + # OPTIONAL: uncomment and update to include your PyPI project URL in the deployment status: + url: https://pypi.org/p/robotics_application_manager + # + # ALTERNATIVE: if your GitHub Release name is the PyPI project version string + # ALTERNATIVE: exactly, uncomment the following line instead: + # url: https://pypi.org/project/YOURPROJECT/${{ github.event.release.name }} + + steps: + - name: Retrieve release distributions + uses: actions/download-artifact@v4 + with: + name: release-dists + path: dist/ + + - name: Publish release distributions to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + packages-dir: dist/ From 887e602efea20517745da7aa571fddecaddaf905 Mon Sep 17 00:00:00 2001 From: Javier Izquierdo Hernandez Date: Fri, 20 Feb 2026 19:23:50 +0100 Subject: [PATCH 08/15] Update version --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c245866..893a632 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,12 +1,12 @@ [project] name = "robotics_application_manager" -version = "0.0.3" +version = "5.6.7" authors = [ { name="Example Author", email="author@example.com" }, ] description = "Robotics Application Manager" readme = "README.md" -requires-python = ">=3.9" +requires-python = ">=3.10" classifiers = [ "Programming Language :: Python :: 3", "Operating System :: OS Independent", From f6f415912ae2b09908d2fc34d6babd4c4de97c42 Mon Sep 17 00:00:00 2001 From: Javier Izquierdo Hernandez Date: Wed, 18 Feb 2026 11:25:38 +0100 Subject: [PATCH 09/15] Upload package changes --- .gitignore | 2 +- .idea/.gitignore | 8 - client_libraries/comms_manager.js | 170 ----------------- ...application_manager-0.0.3-py3-none-any.whl | Bin 0 -> 63551 bytes .../robotics_application_manager-0.0.3.tar.gz | Bin 0 -> 54318 bytes launch.py | 14 ++ manager/comms/consumer.py | 106 ----------- manager/libs/applications/brain_exercise.py | 21 -- .../libs/applications/compatibility/client.py | 49 ----- .../compatibility/exercise_wrapper.py | 75 -------- .../compatibility/exercise_wrapper_ros2.py | 180 ------------------ .../physical_robot_exercise_wrapper_ros2.py | 164 ---------------- .../robotics_application_wrapper.py | 111 ----------- .../libs/applications/robotics_application.py | 24 --- .../__pycache__/__init__.cpython-38.pyc | Bin 151 -> 0 bytes ...ython_application_interface.cpython-38.pyc | Bin 2020 -> 0 bytes .../robotics_python_application_interface.py | 28 --- manager/manager/config.json | 33 ---- manager/manager/launcher/__init__.py | 0 manager/manager/launcher/launcher_drones.py | 51 ----- .../manager/launcher/launcher_drones_gzsim.py | 39 ---- .../manager/launcher/launcher_drones_ros2.py | 48 ----- .../launcher/launcher_robot_display_view.py | 55 ------ manager/manager/launcher/launcher_ros.py | 92 --------- manager/manager/launcher/launcher_ros_api.py | 82 -------- .../launcher/launcher_teleoperator_ros2.py | 39 ---- manager/manager/lint/__init__.py | 0 manager/ram_logging/__init__.py | 0 pyproject.toml | 36 ++++ .../PKG-INFO | 169 ++++++++++++++++ .../SOURCES.txt | 55 ++++++ .../dependency_links.txt | 0 .../requires.txt | 11 ++ .../top_level.txt | 1 + robotics_application_manager/__init__.py | 2 + .../comms/__init__.py | 0 .../comms/consumer_message.py | 0 .../comms/new_consumer.py | 10 - .../comms/thread.py | 0 .../comms/websocket_server.py | 0 .../libs/__init__.py | 0 .../libs}/file_watchdog.py | 0 .../libs/launch_world_model.py | 0 .../libs/process_utils.py | 0 .../libs}/server.py | 0 .../libs/singleton.py | 0 .../manager}/__init__.py | 0 .../manager/docker_thread}/__init__.py | 0 .../manager/docker_thread/docker_thread.py | 0 .../manager/editor/serializers.py | 0 .../manager/launcher}/__init__.py | 0 .../manager/launcher/launcher_console.py | 0 .../manager/launcher/launcher_gazebo.py | 0 .../manager/launcher/launcher_gzsim.py | 0 .../manager/launcher/launcher_interface.py | 0 .../manager/launcher/launcher_o3de.py | 0 .../manager/launcher/launcher_o3de_api.py | 0 .../manager/launcher/launcher_robot.py | 0 .../launcher/launcher_robot_ros2_api.py | 0 .../manager/launcher/launcher_ros2_api.py | 0 .../manager/launcher/launcher_rviz.py | 0 .../manager/launcher/launcher_rviz_ros2.py | 0 .../launcher/launcher_state_monitor.py | 4 +- .../manager/launcher/launcher_tools.py | 0 .../manager/launcher/launcher_web_gui.py | 2 +- .../manager/launcher/launcher_world.py | 0 .../manager/lint}/__init__.py | 0 .../manager/lint/linter.py | 0 .../manager/lint/pylint_checker.py | 0 .../manager/lint/pylint_checker_style.py | 0 .../manager/lint/pylintrc | 0 .../manager/manager.py | 28 +-- .../manager/vnc/vnc_server.py | 0 .../ram_logging}/__init__.py | 0 .../ram_logging/log_manager.py | 0 75 files changed, 300 insertions(+), 1409 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 client_libraries/comms_manager.js create mode 100644 dist/robotics_application_manager-0.0.3-py3-none-any.whl create mode 100644 dist/robotics_application_manager-0.0.3.tar.gz create mode 100644 launch.py delete mode 100644 manager/comms/consumer.py delete mode 100644 manager/libs/applications/brain_exercise.py delete mode 100644 manager/libs/applications/compatibility/client.py delete mode 100644 manager/libs/applications/compatibility/exercise_wrapper.py delete mode 100644 manager/libs/applications/compatibility/exercise_wrapper_ros2.py delete mode 100644 manager/libs/applications/compatibility/physical_robot_exercise_wrapper_ros2.py delete mode 100644 manager/libs/applications/compatibility/robotics_application_wrapper.py delete mode 100644 manager/libs/applications/robotics_application.py delete mode 100644 manager/manager/application/__pycache__/__init__.cpython-38.pyc delete mode 100644 manager/manager/application/__pycache__/robotics_python_application_interface.cpython-38.pyc delete mode 100644 manager/manager/application/robotics_python_application_interface.py delete mode 100644 manager/manager/config.json delete mode 100644 manager/manager/launcher/__init__.py delete mode 100644 manager/manager/launcher/launcher_drones.py delete mode 100644 manager/manager/launcher/launcher_drones_gzsim.py delete mode 100644 manager/manager/launcher/launcher_drones_ros2.py delete mode 100644 manager/manager/launcher/launcher_robot_display_view.py delete mode 100644 manager/manager/launcher/launcher_ros.py delete mode 100644 manager/manager/launcher/launcher_ros_api.py delete mode 100644 manager/manager/launcher/launcher_teleoperator_ros2.py delete mode 100644 manager/manager/lint/__init__.py delete mode 100644 manager/ram_logging/__init__.py create mode 100644 pyproject.toml create mode 100644 robotics_application_manager.egg-info/PKG-INFO create mode 100644 robotics_application_manager.egg-info/SOURCES.txt rename manager/__init__.py => robotics_application_manager.egg-info/dependency_links.txt (100%) create mode 100644 robotics_application_manager.egg-info/requires.txt create mode 100644 robotics_application_manager.egg-info/top_level.txt create mode 100644 robotics_application_manager/__init__.py rename {manager => robotics_application_manager}/comms/__init__.py (100%) rename {manager => robotics_application_manager}/comms/consumer_message.py (100%) rename {manager => robotics_application_manager}/comms/new_consumer.py (94%) rename {manager => robotics_application_manager}/comms/thread.py (100%) rename {manager => robotics_application_manager}/comms/websocket_server.py (100%) rename {manager => robotics_application_manager}/libs/__init__.py (100%) rename {manager/libs/applications/compatibility => robotics_application_manager/libs}/file_watchdog.py (100%) rename {manager => robotics_application_manager}/libs/launch_world_model.py (100%) rename {manager => robotics_application_manager}/libs/process_utils.py (100%) rename {manager/libs/applications/compatibility => robotics_application_manager/libs}/server.py (100%) rename {manager => robotics_application_manager}/libs/singleton.py (100%) rename {manager/libs/applications => robotics_application_manager/manager}/__init__.py (100%) rename {manager/libs/applications/compatibility => robotics_application_manager/manager/docker_thread}/__init__.py (100%) rename {manager => robotics_application_manager}/manager/docker_thread/docker_thread.py (100%) rename {manager => robotics_application_manager}/manager/editor/serializers.py (100%) rename {manager/manager => robotics_application_manager/manager/launcher}/__init__.py (100%) rename {manager => robotics_application_manager}/manager/launcher/launcher_console.py (100%) rename {manager => robotics_application_manager}/manager/launcher/launcher_gazebo.py (100%) rename {manager => robotics_application_manager}/manager/launcher/launcher_gzsim.py (100%) rename {manager => robotics_application_manager}/manager/launcher/launcher_interface.py (100%) rename {manager => robotics_application_manager}/manager/launcher/launcher_o3de.py (100%) rename {manager => robotics_application_manager}/manager/launcher/launcher_o3de_api.py (100%) rename {manager => robotics_application_manager}/manager/launcher/launcher_robot.py (100%) rename {manager => robotics_application_manager}/manager/launcher/launcher_robot_ros2_api.py (100%) rename {manager => robotics_application_manager}/manager/launcher/launcher_ros2_api.py (100%) rename {manager => robotics_application_manager}/manager/launcher/launcher_rviz.py (100%) rename {manager => robotics_application_manager}/manager/launcher/launcher_rviz_ros2.py (100%) rename {manager => robotics_application_manager}/manager/launcher/launcher_state_monitor.py (88%) rename {manager => robotics_application_manager}/manager/launcher/launcher_tools.py (100%) rename {manager => robotics_application_manager}/manager/launcher/launcher_web_gui.py (94%) rename {manager => robotics_application_manager}/manager/launcher/launcher_world.py (100%) rename {manager/manager/application => robotics_application_manager/manager/lint}/__init__.py (100%) rename {manager => robotics_application_manager}/manager/lint/linter.py (100%) rename {manager => robotics_application_manager}/manager/lint/pylint_checker.py (100%) rename {manager => robotics_application_manager}/manager/lint/pylint_checker_style.py (100%) rename {manager => robotics_application_manager}/manager/lint/pylintrc (100%) rename {manager => robotics_application_manager}/manager/manager.py (98%) rename {manager => robotics_application_manager}/manager/vnc/vnc_server.py (100%) rename {manager/manager/docker_thread => robotics_application_manager/ram_logging}/__init__.py (100%) rename {manager => robotics_application_manager}/ram_logging/log_manager.py (100%) diff --git a/.gitignore b/.gitignore index f95d89c..eb9bdfe 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ # Byte-compiled file __pycache__/ -/.idea +.idea/ # IDEs .vscode diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 13566b8..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/client_libraries/comms_manager.js b/client_libraries/comms_manager.js deleted file mode 100644 index 1daf55e..0000000 --- a/client_libraries/comms_manager.js +++ /dev/null @@ -1,170 +0,0 @@ -import * as log from "loglevel"; -import { v4 as uuidv4 } from "uuid"; - -const CommsManager = (address) => { - let websocket = null; - - log.enableAll(); - - const events = { - RESPONSES: ["ack", "error"], - UPDATE: "update", - STATE_CHANGED: "state-changed", - VERSION: "version", - }; - - //region Observer pattern methods - const observers = {}; - let currentState = null; - - const subscribe = (suscribingEvents, callback) => { - if (typeof suscribingEvents === "string") { - suscribingEvents = [suscribingEvents]; - } - for (let i = 0, length = suscribingEvents.length; i < length; i++) { - let event = suscribingEvents[i]; - observers[event] = observers[event] || []; - observers[event].push(callback); - if (event === events.STATE_CHANGED && currentState !== null) { - callback({ command: events.STATE_CHANGED, data: currentState }); - } - } - }; - - const unsubscribe = (events, callback) => { - if (typeof events === "string") { - events = [events]; - } - for (let i = 0, length = events.length; i < length; i++) { - observers[events[i]] = observers[events[i]] || []; - observers[events[i]].splice(observers[events[i]].indexOf(callback)); - } - }; - - const unsuscribeAll = () => { - for (const event in observers) { - observers[event].length = 0; - } - }; - - const subscribeOnce = (event, callback) => { - subscribe(event, (response) => { - callback(response); - unsubscribe(event, callback); - }); - }; - - const dispatch = (message) => { - if (message.command === events.STATE_CHANGED) { - currentState = message.data; - } - - const subscriptions = observers[message.command] || []; - let length = subscriptions.length; - while (length--) { - subscriptions[length](message); - } - }; - //endregion - - // Send and receive method - const connect = () => { - return new Promise((resolve, reject) => { - websocket = new WebSocket(address); - - websocket.onopen = () => { - log.debug(`Connection with ${address} opened`); - send("connect") - .then(() => { - resolve(); - }) - .catch(() => { - reject(); - }); - }; - - websocket.onclose = (e) => { - // TODO: Rethink what to do when connection is interrupted, - // maybe try to reconnect and not clear the suscribers? - unsuscribeAll(); - if (e.wasClean) { - log.debug( - `Connection with ${address} closed, all suscribers cleared` - ); - } else { - log.debug(`Connection with ${address} interrupted`); - } - }; - - websocket.onerror = (e) => { - log.debug(`Error received from websocket: ${e.type}`); - reject(); - }; - - websocket.onmessage = (e) => { - const message = JSON.parse(e.data); - dispatch(message); - }; - }); - }; - - const send = (message, data) => { - // Sending messages to remote manager - return new Promise((resolve, reject) => { - const id = uuidv4(); - - if (!websocket || websocket.readyState !== WebSocket.OPEN) { - reject({ - id: "", - command: "error", - data: { - message: "Websocket not connected", - }, - }); - } - - subscribeOnce(["ack", "error"], (response) => { - if (id === response.id) { - if (response.command === "ack") { - resolve(response); - } else { - reject(response); - } - } - }); - - const msg = JSON.stringify({ - id: id, - command: message, - data: data, - }); - websocket.send(msg); - }); - }; - - // Messages and events - const commands = { - connect: connect, - launch: (configuration) => send("launch", configuration), - run: () => send("run"), - stop: () => send("stop"), - pause: () => send("pause"), - resume: () => send("resume"), - reset: () => send("reset"), - terminate: () => send("terminate"), - disconnect: () => send("disconnect"), - }; - - return { - ...commands, - - send: send, - - events: events, - subscribe: subscribe, - unsubscribe: unsubscribe, - suscribreOnce: subscribeOnce, - }; -}; - -export default CommsManager; diff --git a/dist/robotics_application_manager-0.0.3-py3-none-any.whl b/dist/robotics_application_manager-0.0.3-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..ae3d58a64f4915992a8b3efc97c34fed782ac03a GIT binary patch literal 63551 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Ŷw2QB5}KNUtU* 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 literal 0 HcmV?d00001 diff --git a/dist/robotics_application_manager-0.0.3.tar.gz b/dist/robotics_application_manager-0.0.3.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..2e40c98b9ae7b80a57bc8b8ba69ba2b6983bd93a GIT binary patch literal 54318 zcmcFqV~;Kjj2+vyZQHhS$F^=f*16t}ij<#u$fcy@YO9BBof97aM=8`c|AU&i)x4Njoa z6SCXO{BV}#^gxl-D%A>wJRkTq$m@U}sJ;I9V5TX-&%Zym5A)3doE+`#v-cmqolV?3 zKO+HvNy(2aOtZw{hk_rQUt59q3NyYqgm539;oCUOZJnHuytusGx^vO{l(+MMAI1D# zQ!_pWi9y@+k>kSH7FRa-r2~$sbq)yJkh<4>Gr;}r?Lq%=Zyx-&LGO74@9=G29q~gj z@eN?Nw>M7lCoiE306^IpufaMnxd%uGKmeeA0$e)g03F)D0Ob!r`6!^Hy}d)<7Lh}H zZvQD@7@(*aA3t9oAF%zqzq@OH1{jXJ%{xSR2E^^=)%DIQa&QEZ2mW+CGQR`*sQ~Y| z{(S;I*8zUM?!I20R|Y-*VAl3AaUOoo3LJ`ifQBABzJAYO0fOgNC&M^*Uiks3vlw2! zKAK;y=_v~TO{l%uAJrybV@-oKB2 zsf;Wa>OFn&pL&cXg<$|Dzy=3PZC zpa36{1P#3RYXiMlWkHH>zE4S7;8mEVhW99RJQE=v+*k4xYvLj&Cdfv}3@@(1> z$gQsxmQV-l&m5xusprdvU~V0{gF`@O-H_JB`0WYI2YPV;eujE<-U>ExU?QeZmc zcHQUG$*}f?d_C1uIuh~r801fh6c<@jID!8YGJikieBL4VyD+?xK)F!{K*IFs?IQp= zV~RiW`SHw8XqskvGQvkv?ogJ_Nf&q_2<$shyjHa}Nopb>5Ex&D*cgMucTfJZdO1MO zh1iFxxVzKK=D+YChWjSV2L74)oq1Sh!O4(29eIb*j$<>%n|rz-;6hY|Kw&tbi(LY* zM^*^_K0!pDEA*snu=JRF#_Ld1cs|qsJ%Vjq!e-#(7!iLQQ`R5a1A0Mc-H>{e&mAFW z2ruk%O0%VVb^7oOMuQR~9783W{O272gds32y&W2;6M80<=>_g5ejjp47v!%gtTZ$3 zVLlFebw_eD8qzsYIqNQI~p|BmG#NVvAwz5xJ+_d<6IQP+)U35BZknF>V?aV@O-0#$}o<-qpa!iBcCL22qY+X zY*JR(v(scTvXr9G&>>#D$q2V^*bG@XgHos%g0dLUFX%G5w7+ZXyz0nREu5hMgNTEf}dZa?bo+pg2nck`*0__pG50wUSXNYE~9SF%jg#!6rPNmG)S$SrR#r zM<+EDjxQkyFeGs@x-Ax32Nuo6jetf@TDY2lv1ST)eLV%j`~X6$(t=i*09^TLwsfvm&g?U`0^TKRyODllr=nO2`q1HUY`g^;kEP9?h0_EX zG$qc#9%7P|i+wGSv2p^_h)Qq%2Uf+wA~i@1A+SSOTEC=72nLahc%cmg1POH`S>UzV z1~SuK*O5d%4x)PzQ<~S{u(nDd4$tfod#Jb#jL3=eUE)aEU^!J3ArUnRYDAsYqZ1j{ zN!pCy1{RRK6MOo#$ohDOL;cTiOUCcw=jL?Eb;@%Kt!)o;;IS=A1s{puepmZkCPGX_ zYo9fy-KAfv8q9fotM;Z*kPi+x6RqD~9XT_dFbW@(fkhGygtHrM0$WaBBL>l+B@St_ zq0NyI#85^!FNj+Ll3hp~OCJF%5kh2h--V@6UbBqTA@zkHfGcpY6xt@-2ufCe0r}YD zVB%J&Cf*kb9bY>+9@&})XcMGr@XQH_@nT9C2SCsC0l{+pkOY^$rdk2fC)ptsoUB&d=~F1cuk5Mv_Z# z#;B&lP^kwx`guKTdGP7#19UbI%}Mzzu`wh zB!d`YVWHz?Q3x!FOk_KB4HQhttb})~;L1C&78^myIv7cfpr64RGza4NM=;OvL5VXj z+vIhS|BjaO(uNe_pp2Ao51PK$4>Z&C$LmDDdNE*zi&=ccmQn# z_AZ=@R7uz4!@6}#R|s;5P6Z3BBVS~phHTmNz}IW>3fev+no8zP4kY=moVMdsaFmZp zEvy*iDVlopu+$bY!e|X&2O^_Z9Mqf+^2bw@MHJ=&M#}Tl4gNxRBm~innsEUV5wDUQ zWSBM{#TNR>CdipHi}Oz=#xm+KVy02H{4CcyGNKTokVs6TLr827P`MM=@iTbFRqDwr zYwH!r^Me>A$z>pQ!m6HDPoSiniCeLVMW+Nd2)r&T*HoKI=vNp))-1ZWt*AY9 zT*E6|L#Vj1n_Oe7b|t357J4xHRwE@b;Rp#y$_S`&3=2LLgbq@RX{`$3N`ukN)gL1% zX|s@Hs(1oZqt?d-riT=0?EV>^0co8^L!jYiuSg1LFrX#Zi5V{m+s$0|AV^;KJqxo8 zW(3#}cO-M>m1VYQsVMQW%;9akFQOqV1R+_X)n_PmMYl5#I=@!Z>gL(_KXgJRNDo|| z{B9q7n;o7yTRCa$u_%aT&pG{KBZEyoEged42TA;^RtHRbv}o!fd@qVU%(!1l8@7;}6dI_yAc^stzgy%)4<@u2A_uR`E49wV37 z(yh1S&twa0?bOA@z?TjLJH^aoW)FO=d%#8Huoz>(RuH$b;j+sVb2K;NZboYt(JQz_ zl{tJuKb0$DiafjhJXwbUx5WU z{V{h~QUXwaz!M$8ZfIoNv~+1@C(>f2fIyX8=B*Wce##*!)%+7;+03+JuxlbOAPkjJ zfYaKXoUTFtqid1mzWoYZt%h)TQfS?ExzggdLXV$9hb<$SNXUqmO^-b2R6HUaou@2V zFZ=`Kr7$_5ImNXS(b!I&UlZ4Tp!DoAiK7+7OvoBW*z834Ls1p#j^#Z~2`4BE#4~Zd z_OYgo7l%exC8aO5V9U*$A?ezB?h9G612;XWPxWx-4 zbQZh@kq68rP8-rUZ_;AqQZgtF##k+AGHbIkY#_~}B9RtDj@ex(Id{m08#(+~o2IO0 z)-0-K!IZur4Y^527fJmjvSO#K&Y*Pvu5We{QTXSCw{A4Zb$JOxwDGBI1JM^@V|QBy*T$`myN5q}cCI#d z>aAfC@Ab_JIReCi<@U{pZTC1>& z8B>D_ZU(8qhv@EDHMCRd#$ChKiWXXIF*kg1(x%9&#w0_SCt#>LAc)ut7ei)Fp4p-* znBNY!8qpo9Z2XFIgO@;_cpG5Y6fPTIZ1I!PG(Nx04~s(-1~V}eeXhPch(<^&=pz{y zlO)HQI&(8fVshqcoyO^kYwK{o- z>6tzSh2-*+t?-aZvw>*a5dNe?f~)DgkT*Nly=raGfJqtzKPeO06#4Ff9fN9DjD*E- zKoH?PNz+)0SvJ7sTkU5p{7E97WFnMMJ=1fX&q2uKsB)~QgT)bNVcejK&;!yPSp*lo zkrB&>%vJvEq_EXggxA3sP7<%0I)}1g*-9&dRJ;u=t`<8eUx_Bj(o1wWWuCTh7GVnK zSB8mlyG$e9G9*3gS>V9HotP^2Vo`UOPmU!t3|e`EsQWg+4$A51bs3%0vgJcG_XgI$|?%p$WAaJ!7`eJA16g_dP}v?cZ+#ariy=b3MOF7g$7?tbv9iT zE=Pk*w$@ectus&3pJ3tUi&r_RFBJokl|0f{IR_M(0f&-Ri5s+#y#CC*su9DYpcg)dnc|v`5Uc8LrF1K3S)wjg32gzGg3T51S&?lbsYhb1++mLmgn0;2dfHAFv}HsSeItb) zMroo1ebL<@oGkyTk5}2QjX3?@p!@!AAh$&f!swk{7#A0&G9if%P+JBpT%^} zzbl!Fn`7!Pzv;3RKh;1LQci_JKjxeyX_NC!EzVfDWJ(wCVsBDET+LVgo2hG`qtE0WOFB*l3DLyI@WP&8gegP~J;ZdrX%>7Ze)qL8^nJ~uGz z!A#^&UKwQ3=|axX7}%E9un^UeR~$yul%aZ3_$77R%plCnIFCxgizm7X4>UyK+klm5 zGY;{X1|3M`NOc++#BmEug3%> zmSer`)FP-@i)jh_VPGIS!UU`2cffTnsJS)w)jhRrkY7QkfUJ)rLdb=ZO0L!kV=*iQ=uk(nzY$LET_c zX9OA~6U1f53Y%1PYWkc9#Y>Fm#T%6lInIa?70`rw<<7WuAhU2j3YVOdsUQ@l22?NV zi-C14j}rBr`B2GoJ&&DE2p#URoTMmdD83nV!ZQhx`bArDZRUz4 z{g9rK%r!S;iGp;~Pq%pjw1_3qsm<$RKwjhAu#Us`GqejE&9%T*Xd|>sgfLsC9jV=c z!!qKtg_D!yo)EC)nx3BfZs0>TusOA)K=&$#Gh;EVxveiZ79_(TbugtSZ|oTG?3xqx zQ0aHsU7BI%im41}TiAaxhWlM73Wk_oLlVcSw_!Jsv6PjdkLw3f@4g_KV2hN-F`x+d z#b)d%T@5E#qB^ukr84zM&u|yp=sA$1X$EmX@)(39(jON=kY2%DRW{5M{_rtz8QC#f z?HPA+=rreqo0AP<(u3-l@D`6 zleYnPavAa)>bS}nT@XtM8*dQNKtJHibUD#{$A?g~y&%bM6tHggVVXbZII}2H=~px9 zL;lI8>$2ce4E~NC!jOJ~vR7}9!xt`V#+`W-Ytq(;M6c%e>pv!%imG{=8jUp_jV-wd z>~Y0HM!MvJjvkWvywIFMnR4J;?}ASo^vegcOAK71l^c*RtBUAif|1cLzTn4syfh_s z&Tpaw0;*s`jhd1iU>cH&Ns6;<5UR(Rz6ybZ(BO%*I04~X8RnK*j-_cOxdz7C@siO? zp$k+rTQz1CE6ewPrwr4j6LcttBczswm!9_e|^^t%Vw-mZOD@ z5m*}If>~r<$lzm&`t!ao4lBXP+PCpihh=YB#*>5`bR0&~&%9q%AB1wt1w;b*6ukAG zi>ghRaX|vf*9zk`iYJO*a>YzKH^!oHJ^^<|{iuxW8z+q2!Np9k=p-#CHOu9ytRkKX z-&hrSK3c?mvegqMHS6(DSc;ny>x>1SOzOq-h-+~Pr>0fDDf%*IEMmAxXFh%^ONxWO zCWymb-Zqi^*5wT2!h9=ga@?peyxF~2ELP6;x>EX!7<)iV9?m9d!umRPxj1h`bGF!- zAOn^@!@E{kwNj>IvFB3c&%z;8KoHpvCp1@l&lqX!`R!H*y2@m#C+enm!+3XJziomM z%&#t`-#v;cd03pJ4{hyN8`Ij7zO!dHn{`jF1IZRl`MpT+%#N7zsUV4tUY{& z8#NGfQFk&Ww6n;WtyAbDY%h>tSV-U%*oz~4;vG=jdy7KUgNVge$n9?szeSJJx{_0c zTTS^mkdWmer%CwUA#4nfOYG0(GQ6VMX`XmK8iKipR7rKt_TupTi>Cd6ccCJg{ zag9E`=N`eoV;=zgRfRq|Y%%CF= zY!OI7QLR&v)iDoti@)ybOwsH!QF>YC5;9N{#kNXe`h@@(Up-Xv6dEF-?b20_=4O>u z5Zq|LjuDxcjvX+=gi2#-3X!sT_A=J8{tXF?DMKb%1)Hbk!}=TSQx{9<*p{wk2$>(l zx|>r0aG)p`PqOATMu-@a7&^`$8;!z}+oADfts9_9P{XcWjXF|NQbfaXzE>y5h=|P41{uzbe@c*nd3Xc6bjc&@ zaO^}ZII<~)`b7KIke7QZM1`e$2D_#e#J+=`kS`~6Obj(6YH)$DRBn=~_^(_OxL5Mq z@`bR(RR1Kq2{Fcb!T!-w1F<;ZlpA5P<+ktwCYj<9-7M9JQy4zKMDVK-O{U{Q0%{XP zEt#pJSCDI^|I#`SSROQvT*~V@P3?HLs@w9ax5yvb!6}sJjWgbe;kNDZf!@%9=(j5& za@3lc#fjyfb_LCD7&j|Y;#m;(FiA9AyidI(RPmv%#Q7MVZF zq?)SX=EW1%n*NAr3#79Fn6BclYJ#MN*2b7@l9RX9o-qho5UQt8jlHq)4aB&mwamO=dC-HX>J(hxa3=ms)jN2f7{ zg~e$H@#OsLvayyZPuNgb$b_=T{F@|8ko>hvMUqYeeV$*BJ?XDGt)_<#`-?@^9mP+9iB-jMzyjijszaXVv-AnZ=CHNb z8ANf^a?%cguf}SLv&jOtnbs6eObK^y|3NIxo}!`={KU)_)d#%GL0ZczEu|<@Y(c!3 zt!gK81)D96hrXuUGk(5wj-H^UkU7FF(LS;~vGTkek@J9a;Y~eS>8qWt=p&KvTz!yT z8uF-%7hA3ww@;7TpD*SM#Kl_2#j0ex!}he>_>Y$3!t4)EX^?Q7Lmf??*!L& z`xN$@;#r)gnkAakhnx7~ELkC6VH3kbe?PRDv#cqVt=y2_<{oOBw(c@_RC4Z&20_E3 zF#F_2mZtcAWWopChvQ1s(Uh%z^SAEN=2;M#_fq zElQ~5N;~6|b*|HGh<53w5m5gFVz1Y~G9_k!;`FVpZ5Y#13xnxh#2mx@mqhB<8R_~C zyh%8{$#1*uUvHHVtm91e6^*NP3reHA z8#0=%i870M^sL6V5svQzDO9+*Rd^G7w`W^q zvLov0S|pyiJ(fn#gmefdmGFFKjhq4ecq4PUhI~{yx%e`6g^-iiib%DuoLqrS6&^`d z=d(+n^X$C(3^lQh@} zkiTIxHdQinWwvfJPw_B4Rgtls6|LeK&LB7|A5+A3=;bceqq>VN_6=jktD|ZlC;lqDQT4qmH(%F&QwQInNs*A(Sw6)_75NLu>f&@(U_Muks;H zP~43?o{7zs4f$`Y(m|uo6ZDpKT+lm2Gi6kFSE-H-1T6ejD3DpO+>+Wm#_FXn{H;@w zLX~{We5++dNykwzf$Cu7ZSOfM-ne6D__v$kn%RleXG5ueFMs0;EE*}BHNSLn^@?4}%BgK`*-oB}(r+_qFD$22Bi z-E;AM$~Rv&ji`ZZR}=> zy#rQV%=(ayo&aSN)*LSaOdVXDLj@jQYFC3EygXX? z_9?rmi^ZgXXFFB*vcvfKvItXye}!zqLy5h4a;-E6|l#MDdK7HZkk_gR~=5RRzoFXrqV}12jK^^sK>t$<@%*7Er zgdhVAg)M~{^QWN`LrU)v!eB5V_Ryqioh)wTfcGD9IJnipFH$c#Nttv;D%SQYF@o>Q z&JDtuxoCo;iHE-W4;8gl%=9`~)s^WL9mB~1DxS0lGMD=(1Yzf4lH)3OgJxV%0hG{>OLVwUwKX{VHu<D0XXcHY#(HbpCGj!!jg z0d>kc7jRD0c_S;HNR42RZahIuRjYlaoQ%#DL4mu+!ypByzz`KY?h#h_obWXrwSB_D z;s}{#1~f&od)r|M3`v+FjHEcClWYW$!tgq;ESIFfu}RUww)4KGZU=$)eK77?4E^l# zqnJBaE3N693Rd>YOkII>0RiQTIwgFSL}nnC@~zlnZJJGBHAuU1r>kgLLzo8+dJ>b1 zW_<3d*}L`%Re3cS9e7U2KRZ`41}0$FehRBQ`HQoqN=pOQc$yfc*_lZBss z6e9H453!26X6ui;>QvXk5((sLsRWWFi3)=&C<$#AhqQ)Fs@D_mG)0-oWhCpXP4r47 zarK```7h#_2O_SY zR5j!(OCCc>jbakBf<+3X=V#DSigrC3CgdM|r72CZXIb5Nu}0WZ8U4$40nP??tY5Ad+k&NmUyRl?H1_I>)II_O^oU#pb@3yT{3yn^f1X?&yG8Yco4l`l@T(yDHp{>uBvn#w=b9hsx|}7^?9r9Vh+f!2yQ-(pgbJw!v1IVOAK`Xa zK#yBz<~&8L)<-K*Uei+&p$T#K&8B3Fc!v0;wZ2b@r{6C& zcPW_0Nmdc0$(xn~l~+628dKV}-eUZp&ZDT+nXeHv47w9^OFB6ak4>Z_@nm3Erv{~^ z_SKpaiEW}jUa_fp2kS!7p`6UVj3ZY|=YKYl@!!d`0Qb89A5SkYPxpU$fU6%AgAdG% za{~i{zRU0R^OyI>f`)|o`hPLU)0~v+>*I|78v33Q7#S24;}QCK_}>%}`$uz9etlhi zoo)lZzdmLJ$M5?2yLjFj>JcA9Qdj}o|7$>LX{Z`5d5*bfQ0(*fyX_Sa^z-tS=2WX2 zzIZ7LaPsu>bM_5$^mTc9+6bS#$I}Pizz1xnT(6%yG_XJHebQc>@B$PmEf~LlAEDh` zf6im3k85awfW8F0K#>TCr&C%=mOB`M)B6DEFOKg1w*~nFGliS4Ui-s~rFB>MK|#O! zcm@Q$;ev?$Jly=izK3nv4GQ)1hEPMh#G?K$Z!Zr^e?5J?oc!JR9j%N=y7n$SDW;a93hAzMM^ZG|gLC*ZXmV{wQt~LU^ey%<)zD{zE z1=>T6>i7D%F?sXTFsvgasG_&c;2pzJUwq(}CUjU-L`F>?1H55f#AzP&Xa;`~`}_DP#B|sW{d_&;_1J$EQhzT$(NzlHpB}yzC?gN|8{(T@Pam;~qTeHyk!V`hhI#_l6MoxQ|x{K9?{AyL>OJ&b$tOR=x}Z?+Uc_%?N1)RHh-cU;)^y9ryUe5d8fF5PNELL%q97u2(rH3AbdNZ z^rMQXAF9(!jS2OOhF@GI;5g*)L^uxLyC)zex3Y2n{dtgPp>rSj?`Hr+$piF!pu#l{JTj?o|enq)XiYe=T(j@F@#hl zyxAipy^g4IFu95u?Zu}6vm)N1^GUdsgbLwXuMaR2L!EN_Jtx>#>kc3`%NC} zPy5x;*bZym`T6a4woEu(>UYw4iOv#?rl~k9aeal6{m^9`e#^vFWnswSfmaZ^?Yb`O z>$PCBkq*qL?(4Zl+<~W+if(bqzBT6RreVA)lh+?Y7mKwEUi4_!77mWGkA7HXqF3mt zlQRn4u?$Xg2Cu85U80Svdv`h;HD=dVOB4wXmD}KN-bk#`-~YLq(rTN4W6;6}GajFa zm;fBr(^-uEetiuO4y7%<20$a>8{ZI^?{2ioH%3FTDzXGV?;fkY5WxF+ zr5kTj_0W-J8ePE1%?VIjw3E;ntwq_{UD9$?*y}(e5qn)uQk41V{%2=Kh)pw+rP-NS zRk)4r{nI7ktkIw7)bIPGQHxBnZcb79A1K}Yzuvw+UU+rVK0)cLYby*G0!0E!Y4-{9 z;pIG5;qB%SjL(#Vj~mUFHuqOR9W3(D>*S#W8Y6H8TY!O2N~|vJnMHj_KVfItLtzAh zg#+lpD(h=HRP%PD8)f10`vL@rrNw;O;g{7!eN?qU=*)`Hwgo31e_yvzRe%57IS~b( znc6<^5-|ho?U>&4@;Ispl^d$Ywv{TAZftCVh~kVG+SLmyX?}@&B?NaX4T8@NzSkvf z!^#plK{JUnNK9N~3HH@r$CGQklo2+xs_yP``L?yVHIT(veh95t_x^pnvfC)zJN}z$ z7eVV8$MNM@o1@17w-MSaq{}?Mc@>C+z)^Qn|TxpIu!g||Jn4tQ(R}}$JFk<;PH$mTa2t0BMoc#i19SIwZ0~2}yl>fXy z$C>srx+w0Z`~qAWZ}WmYd+r;G;T1b5NgnIZh%QfK`~WHfBqlM@#sQ>+_{p8U;iHMk zet=9;IA8y(fWe^;Lh=VgprPZ8DW4wq8R2O*5-C|5osc+oI9MT8G>Y)L-d?`lcY_c% z{*nz=H~0NuDdzsczBK@AEL5i(crkbp$in4qDb-o#G2r@3JK@sh9x5iSIDvLwlY*kU z)P)99^!xk47noJ?v4g(iUsoULADBatm}gqRNOzJbi+BwCoyk7n$)Wry(I5PM*moSU`hgSKcz8E#L2pQ@$A6jrj&==puC50F_tt*hP(Y8}7yf%Q>|9(Z z%ASi9Z9yjg+gPFhwA=ALh1eNxPCx(cdK&-uHzUHq`&YKAdoW97ucF{<9nAo|xt7lU z&}%pd(7VtO;Gf`TUmQW$T`(6~%IdZ9%u#fRt41BQ^7fC=tdX#YJ)K&oaAj1@#?m{#scEutf8sit2 z0Uy?G|G#rmlhxqlVyOmOUZW|R*FTTl)A@)tg~zQ?VOhuSzqieV4P0dpJ z-N}Lzl0A~)FpkOL%l~sygQ_?nul}kZal0(UU}(`+s~y&5$&VGTMj+eLUHh3A+z%oB zex83%B3QoYeM58HNMZF|C$foc%MyzCx!UTdIX8lX+1+M4>)^j~_c;C>+qxTzfxr3A zw%`0cyNT2&P6~a5r(0stK3!d^D@$&T=Q<)Xah9|A{6I)y%KCRTlqEL|aTsp?nz!^W ztYB5OR_Zmlz!S2-?f@Y{+8;9MEwwK=W`y#ulMy|_YOs^G)^zH$^TZzfwh;Rv;)w_* zAe9`u<_RKKNfJ3fIaHcGi&;^lugtX;xhBYl5$f#{1qx(v_l<^kmJy09)q{q>p$Di)W0+H%nJ|X$ z55;hg*%nc(k63D$95x+vVC9`eD8|Q?7?W0IDN$>MI5|;-PqiOoCl+$OzgSn`@7et) zgIA%Q1@Vb&W-ERCV27qH5`E3G!fu(+aE3^7919-*f{xz4i=DR$sN^3FJZ*y|MpKQF zJ)EtpU>q=gjv7(PE^N!n?x2!XN84eVU9<=4B*tcgYHKw%H;TGN!;1CCvfq-%V0Y>} ztQz1P?WG%Qvivbv0jBN5oox9RXWj&8WL@SuCFr0pjW91c6(D?oH$w~=4zMhCzbMY^ z@wQI&Qf7QDr}usMA{3WgGu2V?(w3J+hsCsss*V5g3;%d>U+n7GZ{7P)OKl1k_G-dP zD$>rVZG!;!pD6-+;-k7S>RkM5P>6iKWuHTAm*$`sbB8MH3~!&rm^>UBZ*&6Tf~xx5 zUG>{*m>T+lxzdcQn5p_|f0r4zvt43*nA9LA(TVZpqxSdEUh?DReCK|(@7NNPYu>p2 z6`eYm=eDYamx%-OW3_Kj_z#87vodsGA%-(Dn59oSH={+zwgggNs=LFmF4JUtqrz`z zHg>I0z1tI{0)Tw^(7#6XJQ_jGziQ+bQkRZj=ZMw4{lyjHCX{5?f0?v%N)&W~qu{?t z7*k08le|6f78L&hD4CGWxyP$1a7*y@izl8{@Z>Ip7qV!S^Hs^m_)wUW`O17n$#0c6 zTN7l*SLUoP0?3VHk&`F%$O7;uUr_{;`yM~f9TuQLRGtH7OT5JX?KBsL4y+42S-4c| zQ`yf^2he#G{!)=VUNmg1CxnWEg89%WDPR|ZkkV}hOtbHtGT^;&Y>EP)b_o0o=$K_k zJ<@dL^qJj)ST6H=XpccrvSIpc#J4mmf6qg8!T`l3jah$KUjV^Q}5S1;NkVC?ib4EFwcTMFh zTRqb)CvCUk!rMdoA%O(Y+QIV4xc=Jt{nS<=BT>fvojZ#uGz@`Ps<1a;}NPO6qD1f#2p{N1OYM zRA`1ZgZ4F$jOXB1*BDieb2zq3lyX6y{nbWkJep`@_)N!=nGtd91OYd1D$4cYWL)H{ z5P4eZZW)>XvM`ih>%PWuN5iJ)$lb3V0Z zSi_@r_Xnv}W%E2M+6`0|(bDWo^8t8?5}ojag5fM2SeF<}apPkrY)(Q`r(l)A0=MeB zKa-8?HY-P27v{?C&j*4RP}#lF80|0c5CF;zEYq7ieU>pt--U_?916|i9o8}KZ$X6p z3Gm(BI=Fg4_i_UU69~lfk9@(dgN0>#rYbtL>+v*+Vr=wQgDrtMs2E^|GE`Ng?Qg#6 zk#ac^Wzyj&*4nNfNzVEkLk|a*kLfWCi4CqMbx^EaK6;F?EXQ5MZE2>1TJ9_tZ-IDr z+IKV~_mE8V+nn`|DuZ`^kB2R2jIC^2h^-5`4LADYt)v~r5ov!DYgd+piz#Z`j_t1U zj8_ZHkn$~(i^5C8$J)2peNh?Gt6owVPuz+;J4&Z$m}j}Csy?yLad5vr&5xXsby;=L zos>wN5Zja;t@L=cfz;38oNud=ny5t?1*O7por+>rLtwRIlc`$b~&7)Hw&^s(#c23elH80PR{OVqOHz;O#VJUXc={RCD|5h-@|D> z5@0&=+B!TvaB3LoiJ3)y1y-91Tv+$Pjj1~@Z6{a*e2m$9nI~)8Jj66^iRE@gHEjZS zx0cxf638^cvTnG~PkL~I5;pkbikZq3>L)rOsV^%%8JJ1mwi=2(C!+&Z0CcEqZo`a!Cb-wKGyFjLrFjVahIaA)iNOFj2TTw^WMfN~+Zh z#+yGw7JN=&?SaRlCLQVFN!d@LZlGnSjiv+D{{p)BL)_@Y{Y};uA&sB1!gHWJ8INg74RLc$1rep& z{Cc@20UqNa5p0S1=9H3+?Bj{%WaSqENZA~s39qK%V>*``0&sHGEZMwjpHpoA&9*fo z|-MY?*&0Y`%skh21I3waA=aLuLAuXs75X&+|wZ-yr0pawZJwEICqzH}vY4 zbZ4drp+&Bp;TjoKJ1ak)vtO4x*tN#_;pWgaCSWLr$^tV74cYU!-TS%-dA=x7%0PugLC)%=3=hj|a%^e-FaS4{G1|s*9P7i(&^Ctqx9pC|xzqb!Id4lxcV3 zJJiZv(J=Y-KZk1f;P>g3KYL>DAt(szJwSnOlSlLB>pi9sT}Tz}&hv6P-I?G?!gMQ? z9WV?oR%L9So+~)%`L~vqGW6G?EuBVaeCl4VGGx>Cc9%JS5@Sr9?(Z~rq6_U9cj6DE zS4cT9&8HkeDz4f~y!`8qp2kn0(TRci~%0-`KMEp6= z0nNQbBnSvv;*JIYg;;SGdX!kHLF8%t+S~_xKqdol?gKWw`#QR|WrFH{BA)iEDXtZF z0X{yw(C6a?2g@j0xmui_D+rey{`e>4?{EnHn08vHyg3Fe>d;E z4$yH5Ae;hd+5{zB21ElKf=D_+yAQ@NDnoJWe%GJ=+q2`R|KI2MIlyGHjX*j-+y{Yg z2*CLsJ`4jOyt8Y24+uylWVwx#Nb%yHFIkkrehy}v{T~33KySae+*{#!^6W1~ma-|3 zYOHaYAby2W;z<<7#SBA4ICGJkmJ14@mUf5fqVJ#2ICIA17An?7wZSZ!H(40Ldk)<3 zD+&El^P#I7_VzhUr_-T-E6r8zkdN^!ak=24Th#K8l!E8S;l4jlt5x7-3ISdHx2MFznjUVdm2>w1&1!moPO6`s|VbVLf7|zydV4n|13)* z{y2;#I^$hRfv7*RX30Vt_x!alm*9{QXCn`PE+u>$Olu))9I@L4-^DFgUZn;k7oI3 zM5yfTSI43raSwEzZ(y;W_Q^_O*40;ts`X#eC6B}+wk)&|j-C+xr54-(Qp{&j-hE`u zJ^|Vr=pm%fp6Q0M3H`^R`%on}9QYl|A?diU)%qT2ve6T&o*7GUxMYcSGu}n@^|qnw z6Dc12)RCb&`wn!!=+2(LvSsdq@DwnAS5O&T^II$bxAOmQl>djD+rt;TyRH1+%KslI z|EJNzNb&Q!4QRFeKLn+7m*oF#H2cHvApZ|r|NqDNL7~c&9~f(ynajnn(D#)3!UNA! z<@##1#R@H@sH9X-3Hp-zZkH6G3}6*jKxNnl9*N4f1|q*pBm(OY)K)1FR11!u&*vbt z$YcC{n;&CQ1+IUU+JW@v0&7YP@gNb56f^ zKYe;^#u7QyRiX4)QZdaChRP~kNfE(2&|82i1}sip&1YP>nbpLG;&Yq~OpUM4U!G&B zl@fExb=?6_#+H2y)P?mwRGxA!Wl@nf99649h`!5;vQJf|`@U2CZ=Z>%*Q_7u%5R*# zUez~=itVm-$!68P;01pU^V?$I5$fv2+pwU&amE) zO9JG;$C4<>yG|3XM_M}2zf#X!-KFzcHz>x)BzuMb{b$|s4s)>10{DAvxH%YZ7C+!) z!g@+icLqD~tmF35{j5uGi=OX)ruEE160OW&ulGNDv;WzZ_1pnmDxZyHau)bkCBQZ1 zqo^|5%14MoGg%uOO=yG-s5-58Wl@$ztvJAFl8#6?BCgeC>Zgz75x(uM`dXSL(WLQn zlZwBx7s2#M)joB)s=loBBpg>PPp*3Ju!#egZ5Xy&o3Zd;e$W>Vw1nF-+ND@P{pg_L zw<{Scf3t7bsK{>U5(TvPLq{@S?*&+>HrpRD&M^*MoCE)OCg=V&A4&Z?hRGdz- z?xf>RC<^!epZL))on_0wzcj9P7^RDm2;=H_w9Qq^Xy;e0hXCXjjs0Dh!*u=ouSh&m zqsIkNwhJZOd7iv}Hl&S9m}U>KbV|@1z@HX#!bfOs!g(KH(*gW3!KYor3QhAFG%UN; zN9L8Ww>`xm_4(&Nb>gr?w!<#T$K779?{~x&1ur^gVx3_F4ruOe%zJyb}&AT`8qqGLLms&lht#^!mL zDlQ+yq?}wL{iba#Pa-Sf>hZ_i^RDU zyrnKKC4cB6E)-wwy35`SORrAZHQ(0Aad`5U`0$ac&cDZpteL2~PuZ9sCMwweD`%&w zUE=z$mSV*6vOT(=t!3s-t+=5j5YQa}RVN*g3tdapXz0LVlc3RKjX^HTjEd}_W}^^* z@M;#U`ir&(ZW@IL*ZDkhV8~{TAY`*<2yXLW98vYfrQ@m+>?6~*+xt8CWm^AV>;G%x z|62dwZ}a~N6J3%6{8;?o&hDNY|F^lfyR+T;|31dgr~WI{lp4wX$T{PT;W^Owh?qw}-tle4#n z*C*#^pv!_{e@~5&#ujwBUpc-TfPf7$B^KR>L%U{7pDna!kO>fdKFYs!O;5gsNsuu< zG|GlzjW?q7Xn~;m<0;9qOP(?;L7;?YTsv7+=KQugdY7{=agEA@b3X5QTM|XSO$ASL^H~w;9`q4T+UaG6) z9M>oI^5mP7m&cbFz4dv=|J;ARvD8=gc>vRbe?EWrfWKLyPF+_h zc53SEpP~iTC*N;S+04Rq-o)fO_MdS-&k$AIy=^R|kT)xutqQp-Zy9{mLM!KKRjR8^ zRVgDQU?R6sxmw}2TXsL^zZ7izR~5oWZD0N24#4XRlQ85hca4nrNw-0ZwkNNDU$&N} zbzL=>bbg|iLQuc_uk?5sJ^MfLzrXDJKj|Lf1Gj_Dl0df!mt1z|9!80S+`exk*G8>o z4;4Qm@AU3creS!*%lUM=@E>wuR8h`%th@tVJ-V5jTMB@G-NYp`F61#?w>ROBTK>;&oB#Qd>7&gW)O0b>0;>$Qd)MKk5NyeUemu7XX$Lht68oX9b-o)=u(!PIvba5r#Q4W>4 z>hIM* z|Ka%?4fg6`!?VqXXIuFB5MFKh+y0Kf>+ku`{TCnd?01;K&#!*Ir2qVEhD7x9o1guQ zV1X`2^s@RtvnE(#z332VRlm7~O#(kZdq1E2d`18Hes{YEPY&rDzq{9?e}8r+-5u_( z?{4?3C(y&$&)?90>dBS+4gS>ywZ|I$;9$79yFT2cF`x(k=i$$rKX2(C{#5T_)z8n5 zW71z+`5$1?Uxg~XaVAdZc(aK+6aV1Ahj|bTkB#y8YBtVJt>;U0fN4Pd!C=sYhu`+| z9~?dX>f{Xe<;Hu^?wRw_UmLHyH-}e$I)16%T^xRQdVctFbb8Fq_g>kLM#q1?9`5ps z+HIOU8ur(9qX(o|N31+e>3?pYv^>u2`BE2MybR?p-Rf^q^@UP%kw= z8VBSNRBsk|&7oTr``p1bYq)mXJtr$-D~)jE=z#{PA(f;d%;ZRlIgDmb!!T4^kIVYiJ;_w;&w(rMbzoCId_CJFL z@@rEoNz1`jUHqMsPXG|f^m+p9O2w1VmwUmoti zi=vsYS_BCkVg4Ls$pj7Lad8)g4UU`&U!l~QjwZ~GWYBChq|bbNczFhbS09F5P&^zQ zhp?sxI%cw0b$B@mD%6?5ir&?OijS#l5_x)k6E^c&Wm$Ys5)nt!PzR`s;%~TXyS?v+ zKUDHHU{>g;iWU`VBchJ*AFoLcq1#}7s1`|pLv5aNPR)kqTAam&^pv8m!d;Q(>TR=(?REh0-}}s5|61aM-=z*yAgIT!F&#Ii((MH zkIU}Rt_M=p7<3Y;91l&x{HnC89903)nN%KG-#2=JH8hq`dg{0)yA70%S(e^*tDR*} z@66TpEG@6{o%M$ zg{fIeFM55NQiPWB|Nl7fhZTtjX5dOAl78cUSxJVJ!8=PNk&Z|Z=Dkvr<%MbfOXDdv zKv#Aeit^f9rnQ^!dX)Dqs<4#u=3)*nkm7SJP>?Jd`}{BpSc=HnI@y!`c=nZsWEpBss4DI-bOs%M zNmAGRK%GPBLSqgk6>%gkEiABtJbsP&<0;Y#zY~OZ8?n61QeuODtrk+7G2#+bd`cV) zskTKGalbDXEaS{?oArN(X{#BLijK_i2B`o!5fp+?j^7oO_0%1?(T@7E#egXYm_Shfg4qiKJ&l3bCcM0DI&y|`~hOx%~gZK=qB_5oJx zW`*4tHXYkVx5?SQyt*T6ynUZ-N;OZ8TN0(RDj1QBv~yBN-ND^Qyv zFtUe&O&$@BvQ9>$xJaLsz&Pe7N8e&Hrrct-_=>`&8FuIvf#MvC=d9bLL zG^oxRXf27VQ>sbhNFojMT4FNNjcQgVrL@~qU(yL~e_)XBODoybsz!`+C6_uccsidh zafs)*AE~!4HmdaD4DeVG{>`xO-)t(~(t=^LW+t*Cs@PD38ywv|7^po|0*s88VG@z&~)P(4l!7;`yTpnK?U+=H9!F7(L`U{E(q2=^hYAbZedt-^d z)r_2tzV(5iDI@WJQ>G_93IjIq++dA}b8qf@lTrT8zX&lozn5P)`+3kG!y3?<5r_EmP z`we4UBxcz(*4_t>^byk9hw0h}rKtA_mbtGU76S#Zv}t*j~sI+~uNm zQIZa>3>RalbFjL&#J{X)>B>#=*GdpS^*ab6_uL_q8)~fR4TYnvQ$IE=3hOMJsOhmj zSyf>4(FqJ93PaWV3Rdfv{)77y5+lZ)H9b@4?+Lt7W)eT>+@BYeUNt*3lGXa(f|1+lO`0x4B2PKQS8p79-gzD=gc5NDeZV z`R4jWJo-MUVdS)fy#zqUDOU4Ibz%mVc`0)^*HGY!%E=(hZ`Z?Yyna_sll6Qu-re5W z`LtkJYi)0^U5SI@Y3%#gfFG8>#p8z|K~mh;s;p!usa8t^F_>TYO`*VW7RviC{o%%N zXLY*qq{C>EPe@|B!6>8hA&OFpDyDskJ@x%tU==@cQ>^=EybqLESvj8Thqp)vEF1hA z$syEN54H~DV!6C*AI4QPPJ9CgQH4E!mazF$}7* z6KTsjJG(EO`M_eM%PK`v%1nTD0bdFKio))Jg#A+{UC+uIu1elm^LlI*rMrvb+@JHc zLqQ>5B=I!Hwrl>wU4&H<{4hi76Uy6SFtU>$+@f06%6&sMj?1Wa!B1let3F50Hw%ks zC(IWk=Jcbt*RP&;mzAG#)4WL=r?jlXFOuv576ywiMB-;tIbzV`+Vdt`$h3N`(NZle zKosEun^mn8%_Ca~57lr?i7CT14$*0a-!|ckf=9g%u>`J!@-8UEa1r=YIuzI&Ey}?E z8esUW=+t`r4DaKLx{Wmpo8%HZ4DAD$8K=OZ2UA^dtVGCu3f#J6U zv<{-n0rndlYwzfSJ9G}BKy^1y-xW(tCV#JkigbCD#YWty=CP8;=ra4oTH;BZgG`fv zG^gUpft|d*zP|9kKE5^|W5>6V4&E{L3jDIGCHfv#Eskg1nhc^k7|dr9JBWH>fk^0f zV5E+1|H&f^KqP0s21&rL7{}4N-$Yy=4ySd3a2NFhgciCsSRYRbs(daaSj>{R>~{8n z=!Q)+T6~ZH4(MnzfZ(-$)KLvGVZ#amj?;im`Ab*YSa}@Eu!z5ANLTA6pmyf7+Z=xF z{Gb3vt;<|$(5ae}59xc@H4Vjl9*az9+9ovsIZ#`}2MUFi_zf?uAi$NCg_am)0r_Gc zW<7%|KfysemBK>-Lhz`O_VJ17`wtIP;ooE%yFC)aQJ+***MO)2Q?aWG$H4 zU>qdld4drUYQiI!tGlqPu(ss;2Bdxc_5@RTJDWStj}KoR?yMcZ7;dj^@4Y-+d$DtL zxOTL2_|?YCqvtzc4Tl{oIIbY$^j$DSpfp9mIfETGP>2l&N`k85K^MCkgfVC`CA440 z%IwdzE$Hy-=;WkhooEuo*A#f6Xs;G_ifLu|ZAJP-+l2Jt@DqWD3h2rVI<~Yg4CHMZ zMsFZ*2~CHnz9+b~oGY z+1CGhwEkBT-xTXl*#CQbJIm_-(w@iqUprev;041^up;m#?Ejs%{#RT7vE~0CtN!EW z&i2;x&5h@6{l}L7e;EH)QNBwGz&}#|o%#Rv?(Xi!aC;l~|K{Ev90P6r$Cm%M{Qu+e z|DDa9y%)RNE&p%%{|EAabhL~f@WzHPyR9DrkMRF3koQ6U-`E2AV{eH2AN7E?{_jWm zsa29y4}d4A-?M_N!s->KOI{0A5cUAHkSqS18z}KefT2eSOmn&gHl6lCK8E_%6aSa9wBcKII!8&=i+_SSeg2E=s)p z&dwL5J=%TrwpUf{=QNn72$Ag-(l@gBYhPCGSgE;+IYvMy$Pqk)xVVY{hWaAVrOZVV zMYHbEU-!2*dXME+HDAr*_FSK$xk4p+-*|&yeP$N;MN}?dKI~%I%fDo?lci5yU}i^^ zo&Aq>J4yS~>VK{N_p$W9t>?SXx3{)i{jb&kK1}~h=&T++WO)*zxEm#o68PBtkDJ?@ zsQ+zmZtZSwZf=79x4E(1-v9Vfemb4b8?OFKnWDI=6zGOQSH zM$Ap(+k);hX8jHY3zF?4ttsk9+yYb`3Z3Pb=W@$9znd+>AcY~-+pmHmdV`eJSBVNL z%+T{dVuw!g5;g&2o(JRj|NMWcKQH4X{u@jt^Dk6~c<-~!jT&hccRm?Mkxh8=> zR`UjYWO%vV9}S9|CdHdp&qe-TK8dmYwI+y+^Akbh%<-0XJrAe#egi%w&JA;d;&CO= zO*)Udm0qymvzuL`6IC#|5$(@)>R26`!K$=3x5q9+;a}fH)v+YJOM!27 zdPxdHrQbRj+^hONtn@#vKzfP6Q#`Tt}5e2OD%RxcMNG+E3(~dduk#qr@hC0V%4`kr z6|9jAx(4DUR+q>7A2GMrgX3Dn-NK!9+pr#zJ_7I_;|AB`Cd~MuOWPNY)RN-}v zd==vSMu<`=5Ep2T*X{Ic_2h5yj4b`?9!I*>QC|Z&D+qvJ@3=w<_cki|K8EhQL?uqo zFcA*#o`8KLxnnAKP%dRT@Zkv?aH;Y4`)k7=qy@Yjjd-}xsLLPlE53?mI>RCj{Mm5- z2P22!mjDhGhn)7QC4VP^-?R$y2xm?@N!L|O`aZwM1>U!EoQqjBhC$pF9N<&a6=pC6 zoMdUSmJU1WXow0XD!uV@?hkwJ;{Wg`Si+xQ7vE1hKN%Q*!HU(=qXu+<`IKax2k`iv zgH6GT#u&Y_XRcCDEL=I=*qBj*_fas5OAveiM)!rw9O?EBOh9876zg!bdF)RGo{rGP z_n2m+I_vgm@loQJP8DH|I=vqhpy(|aDYhM8!4bTnJ;yX`ez?yYrtcT=Ehy#tnB5A9 zd(o%;md#7`1f3O?nO#BWI45ImFlnV93 zekQlDzS&B2b?J|t$?U0)`L?T~m{0M5#Jh_drZp%O7GzSRbUwWy2e3Rqu1^i#OEq2M zs#t(aJlMoPI_u?hwm$gtaJUA5zF$WU-1+lQZl}L=9Ga=Pz@7fpA`&6O{!mM)me5e+ z)<$JHzlBe9C0jjZ+WkS!rGcP?19-Jb5atr3c16p3Yn<_445%Z)YK7nylx