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/ 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 0000000..ae3d58a Binary files /dev/null and b/dist/robotics_application_manager-0.0.3-py3-none-any.whl differ 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 0000000..2e40c98 Binary files /dev/null and b/dist/robotics_application_manager-0.0.3.tar.gz differ diff --git a/manager/comms/consumer.py b/manager/comms/consumer.py deleted file mode 100644 index e8e0a0d..0000000 --- a/manager/comms/consumer.py +++ /dev/null @@ -1,106 +0,0 @@ -from __future__ import annotations -import asyncio -import json -from uuid import uuid4 - -import websockets -from websockets.server import WebSocketServerProtocol - -from manager.comms.consumer_message import ( - ManagerConsumerMessage, - ManagerConsumerMessageException, -) -from manager.ram_logging.log_manager import LogManager - -logger = LogManager.logger - - -class ManagerConsumer: - """ - Robotics Academy websocket consumer - """ - - def __init__(self, host, port): - from manager.manager import Manager - - """ - Initializes a new ManagerConsumer - @param host: host for connections, '0.0.0.0' to bind all interfaces - @param port: port for connections - """ - self.server = None - self.client = None - self.host = host - self.port = port - self.manager = Manager(consumer=self) - - async def reject_connection(self, websocket: WebSocketServerProtocol): - """ - Rejects a connection - @param websocket: websocket - """ - await websocket.close( - 1008, "This RADI server can't accept more than one connection" - ) - - async def handler(self, websocket: WebSocketServerProtocol): - """ - Handles connection - @param websocket: websocket - """ - if self.client is not None and websocket != self.client: - LogManager.logger.debug("Client already connected, rejecting connection") - await self.reject_connection(websocket) - else: - # self.client gets reassigned every time, but code is more clear - # TODO: Just review this block of code - self.client = websocket - - if self.client and self.client.closed: - LogManager.logger.debug("Client disconnected, machine state reset") - self.manager.reset() - self.client = None - return - - async for websocket_message in websocket: - try: - s = json.loads(websocket_message) - message = ManagerConsumerMessage(**s) - await self.manager.trigger(message.command, data=message.data or None) - response = { - "message": f"Exercise state changed to {self.manager.state}" - } - await websocket.send(str(message.response(response))) - except ManagerConsumerMessageException as e: - await websocket.send(str(e)) - except Exception as e: - if message is None: - ex = ManagerConsumerMessageException(message, str(e)) - else: - ex = ManagerConsumerMessageException( - id=str(uuid4()), message=str(e) - ) - await websocket.send(str(ex)) - - async def send_message(self, message_data): - if self.client is not None and self.server is not None: - message = ManagerConsumerMessage( - id=str(uuid4()), command="state-changed", data=message_data - ) - await self.client.send(str(message)) - - def start(self): - """ - Starts the consumer and listens for connections - """ - self.server = websockets.serve(self.handler, self.host, self.port) - LogManager.logger.debug( - f"Websocket server listening in {self.host}:{self.port}" - ) - asyncio.get_event_loop().run_until_complete(self.server) - asyncio.get_event_loop().run_forever() - - -if __name__ == "__main__": - consumer = ManagerConsumer("0.0.0.0", 7163) - consumer.start() diff --git a/manager/libs/__init__.py b/manager/libs/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/manager/libs/applications/__init__.py b/manager/libs/applications/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/manager/libs/applications/brain_exercise.py b/manager/libs/applications/brain_exercise.py deleted file mode 100644 index fd967c2..0000000 --- a/manager/libs/applications/brain_exercise.py +++ /dev/null @@ -1,21 +0,0 @@ -from manager.manager.application.robotics_python_application_interface import ( - IRoboticsPythonApplication, -) - - -class BrainExercise(IRoboticsPythonApplication): - 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/libs/applications/compatibility/__init__.py b/manager/libs/applications/compatibility/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/manager/libs/applications/compatibility/client.py b/manager/libs/applications/compatibility/client.py deleted file mode 100644 index 1dcc044..0000000 --- a/manager/libs/applications/compatibility/client.py +++ /dev/null @@ -1,49 +0,0 @@ -import threading -import websocket - -from manager.ram_logging.log_manager import LogManager - - -class Client(threading.Thread): - def __init__(self, url, name, callback): - super().__init__() - self.name = name - self.callback = callback - self._stop = threading.Event() - self.client = websocket.WebSocketApp( - url, - on_message=self.on_message, - on_close=self.on_close, - on_error=self.on_error, - on_open=self.on_open, - ) - - def run(self) -> 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/libs/singleton.py b/manager/libs/singleton.py deleted file mode 100644 index aceb996..0000000 --- a/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/manager/manager/__init__.py b/manager/manager/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/manager/manager/application/__init__.py b/manager/manager/application/__init__.py deleted file mode 100644 index e69de29..0000000 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 76c8256..0000000 Binary files a/manager/manager/application/__pycache__/__init__.cpython-38.pyc and /dev/null differ diff --git a/manager/manager/application/__pycache__/robotics_python_application_interface.cpython-38.pyc b/manager/manager/application/__pycache__/robotics_python_application_interface.cpython-38.pyc deleted file mode 100644 index b45dc2a..0000000 Binary files a/manager/manager/application/__pycache__/robotics_python_application_interface.cpython-38.pyc and /dev/null differ 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/docker_thread/__init__.py b/manager/manager/docker_thread/__init__.py deleted file mode 100644 index e69de29..0000000 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..893a632 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,36 @@ +[project] +name = "robotics_application_manager" +version = "5.6.7" +authors = [ + { name="Example Author", email="author@example.com" }, +] +description = "Robotics Application Manager" +readme = "README.md" +requires-python = ">=3.10" +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..bbb5a12 --- /dev/null +++ b/robotics_application_manager/__init__.py @@ -0,0 +1 @@ +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 new file mode 100644 index 0000000..6671462 --- /dev/null +++ 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/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 92% rename from manager/comms/new_consumer.py rename to robotics_application_manager/comms/new_consumer.py index 7c04b84..0f2568c 100644 --- a/manager/comms/new_consumer.py +++ b/robotics_application_manager/comms/new_consumer.py @@ -10,22 +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 - - -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"] +from .websocket_server import WebsocketServer +from robotics_application_manager import LogManager class ManagerConsumer: 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 99% rename from manager/comms/websocket_server.py rename to robotics_application_manager/comms/websocket_server.py index 70479bd..0a67118 100644 --- a/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 new file mode 100644 index 0000000..9dd88f8 --- /dev/null +++ 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/manager/libs/applications/compatibility/file_watchdog.py b/robotics_application_manager/libs/file_watchdog.py similarity index 96% rename from manager/libs/applications/compatibility/file_watchdog.py rename to robotics_application_manager/libs/file_watchdog.py index 7cc9231..6545f49 100644 --- a/manager/libs/applications/compatibility/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/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 99% rename from manager/libs/process_utils.py rename to robotics_application_manager/libs/process_utils.py index b83a28e..14c28c9 100644 --- a/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/manager/libs/applications/compatibility/server.py b/robotics_application_manager/libs/server.py similarity index 96% rename from manager/libs/applications/compatibility/server.py rename to robotics_application_manager/libs/server.py index d7b1f7c..7bb488c 100644 --- a/manager/libs/applications/compatibility/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/manager/comms/__init__.py b/robotics_application_manager/manager/__init__.py similarity index 100% rename from manager/comms/__init__.py rename to robotics_application_manager/manager/__init__.py diff --git a/robotics_application_manager/manager/docker_thread/__init__.py b/robotics_application_manager/manager/docker_thread/__init__.py new file mode 100644 index 0000000..dd99779 --- /dev/null +++ b/robotics_application_manager/manager/docker_thread/__init__.py @@ -0,0 +1 @@ +from .docker_thread import DockerThread 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/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/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/robotics_application_manager/manager/launcher/__init__.py b/robotics_application_manager/manager/launcher/__init__.py new file mode 100644 index 0000000..39ed308 --- /dev/null +++ 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/manager/manager/launcher/launcher_console.py b/robotics_application_manager/manager/launcher/launcher_console.py similarity index 87% rename from manager/manager/launcher/launcher_console.py rename to robotics_application_manager/manager/launcher/launcher_console.py index 0611fef..af94190 100644 --- a/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/manager/manager/launcher/launcher_gazebo.py b/robotics_application_manager/manager/launcher/launcher_gazebo.py similarity index 91% rename from manager/manager/launcher/launcher_gazebo.py rename to robotics_application_manager/manager/launcher/launcher_gazebo.py index f7f6333..5002cef 100644 --- a/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/manager/manager/launcher/launcher_gzsim.py b/robotics_application_manager/manager/launcher/launcher_gzsim.py similarity index 95% rename from manager/manager/launcher/launcher_gzsim.py rename to robotics_application_manager/manager/launcher/launcher_gzsim.py index 053cafa..261b19a 100644 --- a/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/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 81% rename from manager/manager/launcher/launcher_o3de.py rename to robotics_application_manager/manager/launcher/launcher_o3de.py index f633dc0..0bb9af1 100644 --- a/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/manager/manager/launcher/launcher_o3de_api.py b/robotics_application_manager/manager/launcher/launcher_o3de_api.py similarity index 84% rename from manager/manager/launcher/launcher_o3de_api.py rename to robotics_application_manager/manager/launcher/launcher_o3de_api.py index b5c8697..5824c6b 100644 --- a/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/manager/manager/launcher/launcher_robot.py b/robotics_application_manager/manager/launcher/launcher_robot.py similarity index 94% rename from manager/manager/launcher/launcher_robot.py rename to robotics_application_manager/manager/launcher/launcher_robot.py index a00c21a..800cd97 100644 --- a/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/manager/manager/launcher/launcher_robot_ros2_api.py b/robotics_application_manager/manager/launcher/launcher_robot_ros2_api.py similarity index 93% rename from manager/manager/launcher/launcher_robot_ros2_api.py rename to robotics_application_manager/manager/launcher/launcher_robot_ros2_api.py index 9ccffe5..eb25b48 100644 --- a/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/manager/manager/launcher/launcher_ros2_api.py b/robotics_application_manager/manager/launcher/launcher_ros2_api.py similarity index 92% rename from manager/manager/launcher/launcher_ros2_api.py rename to robotics_application_manager/manager/launcher/launcher_ros2_api.py index 4263cce..4840dce 100644 --- a/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/manager/manager/launcher/launcher_rviz.py b/robotics_application_manager/manager/launcher/launcher_rviz.py similarity index 87% rename from manager/manager/launcher/launcher_rviz.py rename to robotics_application_manager/manager/launcher/launcher_rviz.py index 7ced22c..63643b4 100644 --- a/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/manager/manager/launcher/launcher_rviz_ros2.py b/robotics_application_manager/manager/launcher/launcher_rviz_ros2.py similarity index 86% rename from manager/manager/launcher/launcher_rviz_ros2.py rename to robotics_application_manager/manager/launcher/launcher_rviz_ros2.py index 06fb629..97728ba 100755 --- a/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/manager/manager/launcher/launcher_state_monitor.py b/robotics_application_manager/manager/launcher/launcher_state_monitor.py similarity index 80% rename from manager/manager/launcher/launcher_state_monitor.py rename to robotics_application_manager/manager/launcher/launcher_state_monitor.py index 8e645b5..2785fb8 100644 --- a/manager/manager/launcher/launcher_state_monitor.py +++ b/robotics_application_manager/manager/launcher/launcher_state_monitor.py @@ -1,8 +1,7 @@ -from manager.libs.applications.compatibility.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.applications.compatibility.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 93% rename from manager/manager/launcher/launcher_tools.py rename to robotics_application_manager/manager/launcher/launcher_tools.py index e7ea628..2151a16 100644 --- a/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/manager/manager/launcher/launcher_web_gui.py b/robotics_application_manager/manager/launcher/launcher_web_gui.py similarity index 86% rename from manager/manager/launcher/launcher_web_gui.py rename to robotics_application_manager/manager/launcher/launcher_web_gui.py index 6d5c9e3..1e4f6db 100644 --- a/manager/manager/launcher/launcher_web_gui.py +++ b/robotics_application_manager/manager/launcher/launcher_web_gui.py @@ -1,6 +1,6 @@ -from manager.libs.applications.compatibility.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/manager/manager/launcher/launcher_world.py b/robotics_application_manager/manager/launcher/launcher_world.py similarity index 92% rename from manager/manager/launcher/launcher_world.py rename to robotics_application_manager/manager/launcher/launcher_world.py index d9ba862..17e4abb 100644 --- a/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 new file mode 100644 index 0000000..6b131a5 --- /dev/null +++ b/robotics_application_manager/manager/lint/__init__.py @@ -0,0 +1 @@ +from .linter import Lint 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 96% rename from manager/manager/manager.py rename to robotics_application_manager/manager/manager.py index be85314..14cce10 100644 --- a/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,27 +22,29 @@ import base64 import zipfile import jedi - import traceback + from queue import Queue from uuid import uuid4 - from transitions import Machine - -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.launch_world_model import ConfigurationManager -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 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 manager.libs.process_utils import stop_process_and_children -from manager.manager.lint.linter import Lint -from manager.manager.editor.serializers import serialize_completions +from robotics_application_manager.ram_logging import LogManager +from robotics_application_manager.manager.launcher import ( + LauncherWorld, + LauncherRobot, + LauncherTools, +) +from robotics_application_manager.manager.lint import Lint +from robotics_application_manager.manager.editor import serialize_completions class Manager: @@ -442,6 +440,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: @@ -748,6 +748,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): """ @@ -761,8 +762,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): """ @@ -772,11 +776,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) @@ -795,16 +794,13 @@ 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() 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) 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/manager/manager/vnc/vnc_server.py b/robotics_application_manager/manager/vnc/vnc_server.py similarity index 96% rename from manager/manager/vnc/vnc_server.py rename to robotics_application_manager/manager/vnc/vnc_server.py index ced8642..24129cb 100755 --- a/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 new file mode 100644 index 0000000..f85cd73 --- /dev/null +++ b/robotics_application_manager/ram_logging/__init__.py @@ -0,0 +1 @@ +from .log_manager import LogManager diff --git a/manager/ram_logging/log_manager.py b/robotics_application_manager/ram_logging/log_manager.py similarity index 94% rename from manager/ram_logging/log_manager.py rename to robotics_application_manager/ram_logging/log_manager.py index ad7d149..30aec29 100644 --- a/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