From 20fde5c0ce9b0565380f34ba9ca6859a86c37adf Mon Sep 17 00:00:00 2001 From: Apolo151 Date: Tue, 17 Jun 2025 19:21:46 +0300 Subject: [PATCH 1/2] setup python linting workflow --- .github/workflows/python_lint.yml | 32 +++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .github/workflows/python_lint.yml diff --git a/.github/workflows/python_lint.yml b/.github/workflows/python_lint.yml new file mode 100644 index 0000000..b6cb967 --- /dev/null +++ b/.github/workflows/python_lint.yml @@ -0,0 +1,32 @@ +# Ensure Python code is linted and formatted, matching the style guide +name: Python Linting + +on: [push, pull_request] + +jobs: + black-lint: + runs-on: ubuntu-latest + name: Python black Lint + steps: + - uses: actions/checkout@v4 + - uses: psf/black@stable + flake8-lint: + runs-on: ubuntu-latest + name: Python flake8 Lint + steps: + - name: Check out source repository + uses: actions/checkout@v3 + + - name: Set up Python environment + uses: actions/setup-python@v4 + with: + python-version: "3.11" + + - name: flake8 Lint + uses: py-actions/flake8@v2 + with: + ignore: "E701,W503,E203" + exclude: "docs,.idea" + max-line-length: "88" + path: "." + plugins: "flake8-docstrings flake8-black" \ No newline at end of file From 886be41458e13639c1441c498264061bcce7ccb9 Mon Sep 17 00:00:00 2001 From: Apolo151 Date: Tue, 17 Jun 2025 19:26:09 +0300 Subject: [PATCH 2/2] chore: reformat python code to match black style guide --- manager/comms/consumer.py | 31 ++- manager/comms/consumer_message.py | 15 +- manager/comms/new_consumer.py | 38 +-- manager/comms/thread.py | 7 +- manager/comms/websocker_server.py | 152 ++++++---- manager/libs/applications/brain_exercise.py | 6 +- .../libs/applications/compatibility/client.py | 7 +- .../compatibility/exercise_wrapper.py | 30 +- .../compatibility/exercise_wrapper_ros2.py | 88 +++--- .../compatibility/file_watchdog.py | 20 +- .../physical_robot_exercise_wrapper_ros2.py | 68 +++-- .../robotics_application_wrapper.py | 39 ++- .../libs/applications/compatibility/server.py | 3 +- .../libs/applications/robotics_application.py | 6 +- manager/libs/launch_world_model.py | 1 + manager/libs/process_utils.py | 41 ++- manager/libs/singleton.py | 1 + .../manager/docker_thread/docker_thread.py | 19 +- manager/manager/editor/serializers.py | 4 +- manager/manager/launcher/launcher_drones.py | 3 +- .../manager/launcher/launcher_drones_gzsim.py | 1 - .../manager/launcher/launcher_drones_ros2.py | 3 - .../manager/launcher/launcher_gzsim_view.py | 2 +- .../manager/launcher/launcher_interface.py | 2 +- manager/manager/launcher/launcher_robot.py | 4 +- .../launcher/launcher_robot_display_view.py | 10 +- .../launcher/launcher_robot_ros2_api.py | 5 +- manager/manager/launcher/launcher_ros.py | 43 +-- manager/manager/launcher/launcher_ros2_api.py | 4 +- manager/manager/launcher/launcher_ros_api.py | 5 +- .../manager/launcher/launcher_rviz_ros2.py | 5 +- .../launcher/launcher_teleoperator_ros2.py | 4 +- manager/manager/lint/linter.py | 67 +++-- manager/manager/lint/pylint_checker.py | 6 +- manager/manager/lint/pylint_checker_style.py | 10 +- manager/manager/manager.py | 261 +++++++++++------- manager/ram_logging/log_manager.py | 13 +- test/dummyclient.py | 23 +- 38 files changed, 640 insertions(+), 407 deletions(-) diff --git a/manager/comms/consumer.py b/manager/comms/consumer.py index 5182e40..e8e0a0d 100644 --- a/manager/comms/consumer.py +++ b/manager/comms/consumer.py @@ -6,11 +6,15 @@ import websockets from websockets.server import WebSocketServerProtocol -from manager.comms.consumer_message import ManagerConsumerMessage, ManagerConsumerMessageException +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 @@ -19,7 +23,6 @@ class ManagerConsumer: 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 @@ -36,7 +39,9 @@ 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") + await websocket.close( + 1008, "This RADI server can't accept more than one connection" + ) async def handler(self, websocket: WebSocketServerProtocol): """ @@ -62,7 +67,9 @@ async def handler(self, websocket: WebSocketServerProtocol): 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}"} + 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)) @@ -70,12 +77,16 @@ async def handler(self, websocket: WebSocketServerProtocol): if message is None: ex = ManagerConsumerMessageException(message, str(e)) else: - ex = ManagerConsumerMessageException(id=str(uuid4()), message=str(e)) + 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) + message = ManagerConsumerMessage( + id=str(uuid4()), command="state-changed", data=message_data + ) await self.client.send(str(message)) def start(self): @@ -83,11 +94,13 @@ 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}") + 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) +if __name__ == "__main__": + consumer = ManagerConsumer("0.0.0.0", 7163) consumer.start() diff --git a/manager/comms/consumer_message.py b/manager/comms/consumer_message.py index 7b621da..ae8d065 100644 --- a/manager/comms/consumer_message.py +++ b/manager/comms/consumer_message.py @@ -14,6 +14,7 @@ class ManagerConsumerMessage(BaseModel): @param command: message command @param data: message data (optional) """ + id: str command: str data: Optional[Any] = None @@ -24,7 +25,7 @@ def response(self, response: Any = None) -> ManagerConsumerMessage: @param response: response data @return: the response message as a ManagerConsumerMessage """ - return ManagerConsumerMessage(id=self.id, command='ack', message=response) + return ManagerConsumerMessage(id=self.id, command="ack", message=response) def __repr__(self): return self.json() @@ -37,11 +38,17 @@ class ManagerConsumerMessageException(BaseException): def __init__(self, id: str, message: str = None): super(ManagerConsumerMessageException, self).__init__(message) self.id = id - self.command = 'error' + self.command = "error" self.message = message def consumer_message(self): - return ManagerConsumerMessage(id=self.id, command=self.command, data={'message': self.message}) + return ManagerConsumerMessage( + id=self.id, command=self.command, data={"message": self.message} + ) def __str__(self): - return str(ManagerConsumerMessage(id=self.id, command=self.command, data={'message': self.message})) + return str( + ManagerConsumerMessage( + id=self.id, command=self.command, data={"message": self.message} + ) + ) diff --git a/manager/comms/new_consumer.py b/manager/comms/new_consumer.py index 4d19728..2c7d215 100644 --- a/manager/comms/new_consumer.py +++ b/manager/comms/new_consumer.py @@ -4,16 +4,19 @@ from uuid import uuid4 from datetime import datetime -from manager.comms.consumer_message import ManagerConsumerMessageException, ManagerConsumerMessage +from manager.comms.consumer_message import ( + ManagerConsumerMessageException, + ManagerConsumerMessage, +) from manager.comms.websocker_server import WebsocketServer from manager.ram_logging.log_manager import LogManager class Client: def __init__(self, **kwargs): - self.id = kwargs['id'] - self.handler = kwargs['handler'] - self.address = kwargs['address'] + self.id = kwargs["id"] + self.handler = kwargs["handler"] + self.address = kwargs["address"] class ManagerConsumer: @@ -26,16 +29,17 @@ class ManagerConsumer: def __init__(self, host, port, manager_queue: Queue): self.host = host self.port = port - self.server = WebsocketServer( - host=host, port=port, loglevel=logging.INFO) + self.server = WebsocketServer(host=host, port=port, loglevel=logging.INFO) # Configurar el logger de websocket_server para salida a consola - ws_logger = logging.getLogger('websocket_server.websocket_server') + ws_logger = logging.getLogger("websocket_server.websocket_server") ws_logger.propagate = False ws_logger.setLevel(logging.INFO) ws_logger.handlers.clear() ws_formatter = logging.Formatter( - "%(asctime)s [%(threadName)-12.12s] [%(levelname)-5.5s] (%(name)s) %(message)s", "%H:%M:%S") + "%(asctime)s [%(threadName)-12.12s] [%(levelname)-5.5s] (%(name)s) %(message)s", + "%H:%M:%S", + ) ws_console_handler = logging.StreamHandler() ws_console_handler.setFormatter(ws_formatter) ws_logger.addHandler(ws_console_handler) @@ -59,16 +63,19 @@ def handle_client_disconnect(self, client, server): time_string = now.strftime("%H:%M:%S") print(time_string) message = ManagerConsumerMessage( - **{'id': str(uuid4()), 'command': 'disconnect'}) + **{"id": str(uuid4()), "command": "disconnect"} + ) self.manager_queue.put(message) self.client = None self.server.allow_new_connections() def handle_message_received(self, client, server, websocket_message): LogManager.logger.info( - f"message received length: {len(websocket_message)} from client {client}") + f"message received length: {len(websocket_message)} from client {client}" + ) LogManager.logger.info( - f"message received: {websocket_message} from client {client}") + f"message received: {websocket_message} from client {client}" + ) message = None try: s = json.loads(websocket_message) @@ -76,11 +83,9 @@ def handle_message_received(self, client, server, websocket_message): self.manager_queue.put(message) except Exception as e: if message is not None: - ex = ManagerConsumerMessageException( - id=message.id, message=str(e)) + ex = ManagerConsumerMessageException(id=message.id, message=str(e)) else: - ex = ManagerConsumerMessageException( - id=str(uuid4()), message=str(e)) + ex = ManagerConsumerMessageException(id=str(uuid4()), message=str(e)) self.server.send_message(client, str(ex)) raise e @@ -92,7 +97,8 @@ def send_message(self, message_data, command=None): message = message_data.consumer_message() else: message = ManagerConsumerMessage( - id=str(uuid4()), command=command, data=message_data) + id=str(uuid4()), command=command, data=message_data + ) self.server.send_message(self.client, str(message)) diff --git a/manager/comms/thread.py b/manager/comms/thread.py index 8d34efb..4477895 100644 --- a/manager/comms/thread.py +++ b/manager/comms/thread.py @@ -14,7 +14,7 @@ class ThreadWithLoggedException(threading.Thread): Exception is also reachable via .exception from the main thread. """ - DIVIDER = "*"*80 + DIVIDER = "*" * 80 def __init__(self, *args, **kwargs): try: @@ -31,11 +31,14 @@ def run(self): except Exception as exception: thread = threading.current_thread() self.exception = exception - self.logger.exception(f"{self.DIVIDER}\nException in child thread {thread}: {exception}\n{self.DIVIDER}") + self.logger.exception( + f"{self.DIVIDER}\nException in child thread {thread}: {exception}\n{self.DIVIDER}" + ) finally: del self._target, self._args, self._kwargs class WebsocketServerThread(ThreadWithLoggedException): """Dummy wrapper to make debug messages a bit more readable""" + pass diff --git a/manager/comms/websocker_server.py b/manager/comms/websocker_server.py index 03ff865..4c0a510 100644 --- a/manager/comms/websocker_server.py +++ b/manager/comms/websocker_server.py @@ -19,7 +19,7 @@ logger = logging.getLogger(__name__) logging.basicConfig() -''' +""" +-+-+-+-+-------+-+-------------+-------------------------------+ 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 @@ -33,27 +33,27 @@ + - - - - - - - - - - - - - - - +-------------------------------+ | Payload Data continued ... | +---------------------------------------------------------------+ -''' +""" -FIN = 0x80 -OPCODE = 0x0f +FIN = 0x80 +OPCODE = 0x0F MASKED = 0x80 -PAYLOAD_LEN = 0x7f -PAYLOAD_LEN_EXT16 = 0x7e -PAYLOAD_LEN_EXT64 = 0x7f +PAYLOAD_LEN = 0x7F +PAYLOAD_LEN_EXT16 = 0x7E +PAYLOAD_LEN_EXT64 = 0x7F OPCODE_CONTINUATION = 0x0 -OPCODE_TEXT = 0x1 -OPCODE_BINARY = 0x2 -OPCODE_CLOSE_CONN = 0x8 -OPCODE_PING = 0x9 -OPCODE_PONG = 0xA +OPCODE_TEXT = 0x1 +OPCODE_BINARY = 0x2 +OPCODE_CLOSE_CONN = 0x8 +OPCODE_PING = 0x9 +OPCODE_PONG = 0xA CLOSE_STATUS_NORMAL = 1000 -DEFAULT_CLOSE_REASON = bytes('', encoding='utf-8') +DEFAULT_CLOSE_REASON = bytes("", encoding="utf-8") -class API(): +class API: def run_forever(self, threaded=False): return self._run_forever(threaded) @@ -82,19 +82,25 @@ def send_message(self, client, msg): def send_message_to_all(self, msg): self._multicast(msg) - def deny_new_connections(self, status=CLOSE_STATUS_NORMAL, reason=DEFAULT_CLOSE_REASON): + def deny_new_connections( + self, status=CLOSE_STATUS_NORMAL, reason=DEFAULT_CLOSE_REASON + ): self._deny_new_connections(status, reason) def allow_new_connections(self): self._allow_new_connections() - def shutdown_gracefully(self, status=CLOSE_STATUS_NORMAL, reason=DEFAULT_CLOSE_REASON): + def shutdown_gracefully( + self, status=CLOSE_STATUS_NORMAL, reason=DEFAULT_CLOSE_REASON + ): self._shutdown_gracefully(status, reason) def shutdown_abruptly(self): self._shutdown_abruptly() - def disconnect_clients_gracefully(self, status=CLOSE_STATUS_NORMAL, reason=DEFAULT_CLOSE_REASON): + def disconnect_clients_gracefully( + self, status=CLOSE_STATUS_NORMAL, reason=DEFAULT_CLOSE_REASON + ): self._disconnect_clients_gracefully(status, reason) def disconnect_clients_abruptly(self): @@ -103,7 +109,7 @@ def disconnect_clients_abruptly(self): class WebsocketServer(ThreadingMixIn, TCPServer, API): """ - A websocket server waiting for clients to connect. + A websocket server waiting for clients to connect. Args: port(int): Port to bind to @@ -126,7 +132,9 @@ class WebsocketServer(ThreadingMixIn, TCPServer, API): allow_reuse_address = True daemon_threads = True # comment to keep threads alive until finished - def __init__(self, host='127.0.0.1', port=0, loglevel=logging.WARNING, key=None, cert=None): + def __init__( + self, host="127.0.0.1", port=0, loglevel=logging.WARNING, key=None, cert=None + ): logger.setLevel(loglevel) TCPServer.__init__(self, (host, port), WebSocketHandler) self.host = host @@ -147,7 +155,9 @@ def _run_forever(self, threaded): logger.info("Listening on port %d for clients.." % self.port) if threaded: self.daemon = True - self.thread = WebsocketServerThread(target=super().serve_forever, daemon=True, logger=logger) + self.thread = WebsocketServerThread( + target=super().serve_forever, daemon=True, logger=logger + ) logger.info(f"Starting {cls_name} on thread {self.thread.getName()}.") self.thread.start() else: @@ -180,9 +190,9 @@ def _new_client_(self, handler): self.id_counter += 1 client = { - 'id': self.id_counter, - 'handler': handler, - 'address': handler.client_address + "id": self.id_counter, + "handler": handler, + "address": handler.client_address, } self.clients.append(client) self.new_client(client, self) @@ -194,7 +204,7 @@ def _client_left_(self, handler): self.clients.remove(client) def _unicast(self, receiver_client, msg): - receiver_client['handler'].send_message(msg) + receiver_client["handler"].send_message(msg) def _multicast(self, msg): for client in self.clients: @@ -202,7 +212,7 @@ def _multicast(self, msg): def handler_to_client(self, handler): for client in self.clients: - if client['handler'] == handler: + if client["handler"] == handler: return client def _terminate_client_handler(self, handler): @@ -217,7 +227,9 @@ def _terminate_client_handlers(self): for client in self.clients: self._terminate_client_handler(client["handler"]) - def _shutdown_gracefully(self, status=CLOSE_STATUS_NORMAL, reason=DEFAULT_CLOSE_REASON): + def _shutdown_gracefully( + self, status=CLOSE_STATUS_NORMAL, reason=DEFAULT_CLOSE_REASON + ): """ Send a CLOSE handshake to all connected clients before terminating server """ @@ -235,7 +247,9 @@ def _shutdown_abruptly(self): self.server_close() self.shutdown() - def _disconnect_clients_gracefully(self, status=CLOSE_STATUS_NORMAL, reason=DEFAULT_CLOSE_REASON): + def _disconnect_clients_gracefully( + self, status=CLOSE_STATUS_NORMAL, reason=DEFAULT_CLOSE_REASON + ): """ Terminate clients gracefully without shutting down the server """ @@ -267,9 +281,15 @@ def __init__(self, socket, addr, server): self._send_lock = threading.Lock() if server.key and server.cert: try: - socket = ssl.wrap_socket(socket, server_side=True, certfile=server.cert, keyfile=server.key) - except: # Not sure which exception it throws if the key/cert isn't found - logger.warning("SSL not available (are the paths {} and {} correct for the key and cert?)".format(server.key, server.cert)) + socket = ssl.wrap_socket( + socket, server_side=True, certfile=server.cert, keyfile=server.key + ) + except: # Not sure which exception it throws if the key/cert isn't found + logger.warning( + "SSL not available (are the paths {} and {} correct for the key and cert?)".format( + server.key, server.cert + ) + ) StreamRequestHandler.__init__(self, socket, addr, server) def setup(self): @@ -302,7 +322,7 @@ def read_next_message(self): except ValueError as e: b1, b2 = 0, 0 - fin = b1 & FIN + fin = b1 & FIN opcode = b1 & OPCODE masked = b2 & MASKED payload_length = b2 & PAYLOAD_LEN @@ -335,28 +355,30 @@ def read_next_message(self): message_byte ^= masks[len(payload) % 4] payload.append(message_byte) - if fin and opcode != OPCODE_CONTINUATION: # simple msg + if fin and opcode != OPCODE_CONTINUATION: # simple msg if opcode == OPCODE_PING: - self.server._ping_received_(self, payload.decode('utf8')) + self.server._ping_received_(self, payload.decode("utf8")) elif opcode == OPCODE_PONG: - self.server._pong_received_(self, payload.decode('utf8')) + self.server._pong_received_(self, payload.decode("utf8")) elif opcode == OPCODE_TEXT: - self.server._message_received_(self, payload.decode('utf8')) + self.server._message_received_(self, payload.decode("utf8")) return - if not fin and opcode: # fragment msg start + if not fin and opcode: # fragment msg start self.fragment_opcode = opcode self.fragment_payload_buf = payload return # "not opcode" is the same as "opcode == OPCODE_CONTINUATION" - if not fin and not opcode: # fragment msg ing + if not fin and not opcode: # fragment msg ing self.fragment_payload_buf.extend(payload) return - if fin and opcode == OPCODE_CONTINUATION: # fragment msg end + if fin and opcode == OPCODE_CONTINUATION: # fragment msg end if self.fragment_opcode == OPCODE_TEXT: - self.server._message_received_(self, (self.fragment_payload_buf + payload).decode('utf8')) + self.server._message_received_( + self, (self.fragment_payload_buf + payload).decode("utf8") + ) elif self.fragment_opcode == OPCODE_BINARY: pass return @@ -379,9 +401,11 @@ def send_close(self, status=CLOSE_STATUS_NORMAL, reason=DEFAULT_CLOSE_REASON): raise Exception(f"CLOSE status must be between 1000 and 1015, got {status}") header = bytearray() - payload = struct.pack('!H', status) + reason + payload = struct.pack("!H", status) + reason payload_length = len(payload) - assert payload_length <= 125, "We only support short closing reasons at the moment" + assert ( + payload_length <= 125 + ), "We only support short closing reasons at the moment" # Send CLOSE with status & reason header.append(FIN | OPCODE_CLOSE_CONN) @@ -397,15 +421,20 @@ def send_text(self, message, opcode=OPCODE_TEXT): # Validate message if isinstance(message, bytes): - message = try_decode_UTF8(message) # this is slower but ensures we have UTF-8 + message = try_decode_UTF8( + message + ) # this is slower but ensures we have UTF-8 if not message: - logger.warning("Can\'t send message, message is not valid UTF-8") + logger.warning("Can't send message, message is not valid UTF-8") return False elif not isinstance(message, str): - logger.warning('Can\'t send message, message has to be a string or bytes. Got %s' % type(message)) + logger.warning( + "Can't send message, message has to be a string or bytes. Got %s" + % type(message) + ) return False - header = bytearray() + header = bytearray() payload = encode_to_UTF8(message) payload_length = len(payload) @@ -437,13 +466,13 @@ def read_http_headers(self): headers = {} # first line should be HTTP GET http_get = self.rfile.readline().decode().strip() - assert http_get.upper().startswith('GET') + assert http_get.upper().startswith("GET") # remaining should be headers while True: header = self.rfile.readline().decode().strip() if not header: break - head, value = header.split(':', 1) + head, value = header.split(":", 1) headers[head.lower().strip()] = value.strip() return headers @@ -451,13 +480,13 @@ def handshake(self): headers = self.read_http_headers() try: - assert headers['upgrade'].lower() == 'websocket' + assert headers["upgrade"].lower() == "websocket" except AssertionError: self.keep_alive = False return try: - key = headers['sec-websocket-key'] + key = headers["sec-websocket-key"] except KeyError: logger.warning("Client tried to connect but was missing a key") self.keep_alive = False @@ -471,19 +500,20 @@ def handshake(self): @classmethod def make_handshake_response(cls, key): - return \ - 'HTTP/1.1 101 Switching Protocols\r\n'\ - 'Upgrade: websocket\r\n' \ - 'Connection: Upgrade\r\n' \ - 'Sec-WebSocket-Accept: %s\r\n' \ - '\r\n' % cls.calculate_response_key(key) + return ( + "HTTP/1.1 101 Switching Protocols\r\n" + "Upgrade: websocket\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Accept: %s\r\n" + "\r\n" % cls.calculate_response_key(key) + ) @classmethod def calculate_response_key(cls, key): - GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11' + GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" hash = sha1(key.encode() + GUID.encode()) response_key = b64encode(hash.digest()).strip() - return response_key.decode('ASCII') + return response_key.decode("ASCII") def finish(self): self.server._client_left_(self) @@ -491,19 +521,19 @@ def finish(self): def encode_to_UTF8(data): try: - return data.encode('UTF-8') + return data.encode("UTF-8") except UnicodeEncodeError as e: logger.error("Could not encode data to UTF-8 -- %s" % e) return False except Exception as e: - raise(e) + raise (e) return False def try_decode_UTF8(data): try: - return data.decode('utf-8') + return data.decode("utf-8") except UnicodeDecodeError: return False except Exception as e: - raise(e) + raise (e) diff --git a/manager/libs/applications/brain_exercise.py b/manager/libs/applications/brain_exercise.py index 738ad08..fd967c2 100644 --- a/manager/libs/applications/brain_exercise.py +++ b/manager/libs/applications/brain_exercise.py @@ -1,4 +1,6 @@ -from manager.manager.application.robotics_python_application_interface import IRoboticsPythonApplication +from manager.manager.application.robotics_python_application_interface import ( + IRoboticsPythonApplication, +) class BrainExercise(IRoboticsPythonApplication): @@ -16,4 +18,4 @@ def restart(self): @property def is_alive(self): - pass \ No newline at end of file + pass diff --git a/manager/libs/applications/compatibility/client.py b/manager/libs/applications/compatibility/client.py index 41780c8..1dcc044 100644 --- a/manager/libs/applications/compatibility/client.py +++ b/manager/libs/applications/compatibility/client.py @@ -15,7 +15,8 @@ def __init__(self, url, name, callback): on_message=self.on_message, on_close=self.on_close, on_error=self.on_error, - on_open=self.on_open) + on_open=self.on_open, + ) def run(self) -> None: try: @@ -40,7 +41,9 @@ 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}") + 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 index ac88d31..443d94f 100644 --- a/manager/libs/applications/compatibility/exercise_wrapper.py +++ b/manager/libs/applications/compatibility/exercise_wrapper.py @@ -10,18 +10,20 @@ 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.application.robotics_python_application_interface import ( + IRoboticsPythonApplication, +) from manager.manager.lint.linter import Lint -class CompatibilityExerciseWrapper(): +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() + self.run() def save_pick(self, pick): self.pick = pick @@ -31,18 +33,26 @@ def send_pick(self, pick): print("#pick" + json.dumps(pick)) def handle_client_gui(self, msg): - if msg['msg'] == "#pick": - self.pick = msg['data'] + if msg["msg"] == "#pick": + self.pick = msg["data"] else: - self.gui_connection.send(msg['msg']) + 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) + 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") + self.exercise = self._run_server( + f"python3 $EXERCISE_FOLDER/entry_point/exercise.py" + ) def stop(self): pass @@ -58,7 +68,7 @@ def is_alive(self): return self.running def terminate(self): - + if self.exercise is not None: stop_process_and_children(self.exercise) diff --git a/manager/libs/applications/compatibility/exercise_wrapper_ros2.py b/manager/libs/applications/compatibility/exercise_wrapper_ros2.py index 76539d1..08129e3 100644 --- a/manager/libs/applications/compatibility/exercise_wrapper_ros2.py +++ b/manager/libs/applications/compatibility/exercise_wrapper_ros2.py @@ -10,7 +10,9 @@ 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.application.robotics_python_application_interface import ( + IRoboticsPythonApplication, +) from manager.manager.lint.linter import Lint @@ -18,58 +20,71 @@ class CompatibilityExerciseWrapperRos2(IRoboticsPythonApplication): def __init__(self, exercise_command, gui_command, update_callback): super().__init__(update_callback) - home_dir = os.path.expanduser('~') + 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') + 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") + 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) + "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') + 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) + "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}""") + 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 = 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 = subprocess.Popen( + f"{cmd}", + shell=True, + stdout=sys.stdout, + stderr=subprocess.STDOUT, + bufsize=1024, + universal_newlines=True, + ) process_ready = False while not process_ready: @@ -80,22 +95,19 @@ def _run_exercise_server(self, cmd, log_file, load_string, timeout: int = 5): f.close() time.sleep(0.2) except Exception as e: - LogManager.logger.debug( - f"waiting for server string '{load_string}'...") + 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]}") + 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]}") + LogManager.logger.info(f"Message received from exercise: {message[:30]}") self._process_exercise_message(message) def _process_gui_message(self, message): @@ -105,7 +117,7 @@ def _process_gui_message(self, message): def _process_exercise_message(self, message): comand = message[:5] - if (message==comand): + if message == comand: payload = comand else: payload = json.loads(message[5:]) @@ -114,24 +126,30 @@ def _process_exercise_message(self, message): 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) - + 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.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.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.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.call_service("/pause_physics", "std_srvs/srv/Empty") self.exercise_connection.send("#stop") def restart(self): @@ -159,4 +177,4 @@ def terminate(self): self.gui_connection.stop() stop_process_and_children(self.exercise_server) - stop_process_and_children(self.gui_server) \ No newline at end of file + stop_process_and_children(self.gui_server) diff --git a/manager/libs/applications/compatibility/file_watchdog.py b/manager/libs/applications/compatibility/file_watchdog.py index f3a5334..3c6e91e 100644 --- a/manager/libs/applications/compatibility/file_watchdog.py +++ b/manager/libs/applications/compatibility/file_watchdog.py @@ -6,21 +6,23 @@ from manager.ram_logging.log_manager import LogManager + class Handler(FileSystemEventHandler): - + def __init__(self, file, callback): self.update_callback = callback self.file = file self.hash = None - + def on_modified(self, event): - if event.event_type == 'modified': - with open(self.file, 'r') as f: - data = f.read() + if event.event_type == "modified": + with open(self.file, "r") as f: + data = f.read() if self.hash is None or self.hash != hash(data): self.hash = hash(data) self.update_callback(data) + class FileWatchdog(threading.Thread): def __init__( self, @@ -29,9 +31,9 @@ def __init__( ): super().__init__() # Create blank file - if not os.path.exists(file): - with open(file, 'w') as f: - f.write("") + if not os.path.exists(file): + with open(file, "w") as f: + f.write("") event_handler = Handler(file, callback) self.observer = watchdog.observers.Observer() self.observer.schedule(event_handler, path=file) @@ -42,7 +44,7 @@ def __init__( def run(self) -> None: try: while not self._stop.isSet(): - time.sleep(1/30) + time.sleep(1 / 30) return except Exception as ex: LogManager.logger.exception(ex) diff --git a/manager/libs/applications/compatibility/physical_robot_exercise_wrapper_ros2.py b/manager/libs/applications/compatibility/physical_robot_exercise_wrapper_ros2.py index d6cda97..97d7adc 100644 --- a/manager/libs/applications/compatibility/physical_robot_exercise_wrapper_ros2.py +++ b/manager/libs/applications/compatibility/physical_robot_exercise_wrapper_ros2.py @@ -10,7 +10,9 @@ 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.application.robotics_python_application_interface import ( + IRoboticsPythonApplication, +) from manager.manager.lint.linter import Lint @@ -18,58 +20,71 @@ class CompatibilityExerciseWrapperRos2(IRoboticsPythonApplication): def __init__(self, exercise_command, gui_command, update_callback): super().__init__(update_callback) - home_dir = os.path.expanduser('~') + 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') + 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") + 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) + "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') + 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) + "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}""") + 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 = 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 = subprocess.Popen( + f"{cmd}", + shell=True, + stdout=sys.stdout, + stderr=subprocess.STDOUT, + bufsize=1024, + universal_newlines=True, + ) process_ready = False while not process_ready: @@ -80,22 +95,19 @@ def _run_exercise_server(self, cmd, log_file, load_string, timeout: int = 5): f.close() time.sleep(0.2) except Exception as e: - LogManager.logger.debug( - f"waiting for server string '{load_string}'...") + 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]}") + 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]}") + LogManager.logger.info(f"Message received from exercise: {message[:40]}") self._process_exercise_message(message) def _process_gui_message(self, message): @@ -105,13 +117,13 @@ def _process_gui_message(self, message): def _process_exercise_message(self, message): comand = message[:5] - if (message==comand): + 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") @@ -149,4 +161,4 @@ def terminate(self): self.gui_connection.stop() stop_process_and_children(self.exercise_server) - stop_process_and_children(self.gui_server) \ No newline at end of file + 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 index 9554c08..a44ddf7 100644 --- a/manager/libs/applications/compatibility/robotics_application_wrapper.py +++ b/manager/libs/applications/compatibility/robotics_application_wrapper.py @@ -6,18 +6,23 @@ 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.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.application.robotics_python_application_interface import ( + IRoboticsPythonApplication, +) from manager.manager.lint.linter import Lint import os + class RoboticsApplicationWrapper(IRoboticsPythonApplication): def __init__(self, update_callback): @@ -30,8 +35,16 @@ def __init__(self, update_callback): 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") + # 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 @@ -44,8 +57,10 @@ def terminate(self): 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}") + def run(self): + self.user_process = self._create_process( + f"DISPLAY=:2 python3 {self.entrypoint_path}" + ) self.running = True def stop(self): @@ -72,9 +87,9 @@ def start_console(self): 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') + 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() @@ -89,9 +104,9 @@ def suspend_resume(self, signal): # send signal to processes for p in children: try: - if(signal == "pause"): + if signal == "pause": p.suspend() - if(signal == "resume"): + if signal == "resume": p.resume() except psutil.NoSuchProcess: - pass \ No newline at end of file + pass diff --git a/manager/libs/applications/compatibility/server.py b/manager/libs/applications/compatibility/server.py index d3a0937..d7b1f7c 100644 --- a/manager/libs/applications/compatibility/server.py +++ b/manager/libs/applications/compatibility/server.py @@ -19,7 +19,7 @@ def __init__( self.server.set_fn_client_left(self.on_close) self.server.set_fn_message_received(self.on_message) self.current_client = None - self.client_lock = threading.Lock() # Used to avoid concurrency problems + self.client_lock = threading.Lock() # Used to avoid concurrency problems self._stop = threading.Event() LogManager.logger.info("Server Launched") @@ -43,7 +43,6 @@ def on_message(self, client, server, message): payload = json.loads(message) self.update_callback(payload) LogManager.logger.debug(f"Message received from template: {message[:30]}") - def on_close(self, client, server): LogManager.logger.info("Connection with client closed") diff --git a/manager/libs/applications/robotics_application.py b/manager/libs/applications/robotics_application.py index 0f012df..c62b627 100644 --- a/manager/libs/applications/robotics_application.py +++ b/manager/libs/applications/robotics_application.py @@ -1,4 +1,6 @@ -from manager.manager.application.robotics_python_application_interface import IRoboticsPythonApplication +from manager.manager.application.robotics_python_application_interface import ( + IRoboticsPythonApplication, +) class RoboticsApplication(IRoboticsPythonApplication): @@ -19,4 +21,4 @@ def restart(self): @property def is_alive(self): - pass \ No newline at end of file + pass diff --git a/manager/libs/launch_world_model.py b/manager/libs/launch_world_model.py index 6b13e3f..d59d8c3 100644 --- a/manager/libs/launch_world_model.py +++ b/manager/libs/launch_world_model.py @@ -9,6 +9,7 @@ class ConfigurationModel(BaseModel): world: str launch_file_path: str + # Definición de la clase de datos diff --git a/manager/libs/process_utils.py b/manager/libs/process_utils.py index b5a9e33..b43a704 100644 --- a/manager/libs/process_utils.py +++ b/manager/libs/process_utils.py @@ -15,7 +15,7 @@ def get_class(kls): - parts = kls.split('.') + parts = kls.split(".") module = ".".join(parts[:-1]) m = __import__(module) for comp in parts[1:]: @@ -35,7 +35,7 @@ def class_from_module(module: str): """ Capitalizes a module name to create class name """ - return ''.join([s.capitalize() for s in module.split('_')]) + return "".join([s.capitalize() for s in module.split("_")]) def stop_process_and_children(process: Popen, signal: int = 9, timeout: int = None): @@ -81,7 +81,7 @@ def wait_for_xserver(display, timeout=30): This function continuously checks if the X server is running on the specified display by checking the existence of the Unix domain socket associated with the X server. - It waits until the X server is available or until the timeout is reached, + It waits until the X server is available or until the timeout is reached, whichever comes first.""" start_time = time.time() while time.time() - start_time < timeout: @@ -89,8 +89,7 @@ def wait_for_xserver(display, timeout=30): print(f"Xserver on {display} is running!") return time.sleep(0.1) - print( - f"Timeout: Xserver on {display} is not available after {timeout} seconds.") + print(f"Timeout: Xserver on {display} is not available after {timeout} seconds.") def is_process_running(process_name): @@ -102,10 +101,11 @@ def is_process_running(process_name): """ try: process = subprocess.Popen( - ["pgrep", "-f", process_name], stdout=subprocess.PIPE) + ["pgrep", "-f", process_name], stdout=subprocess.PIPE + ) # Este comando devuelve el PID si existe, o nada si no existe process_return = process.communicate()[0] - return process_return != b'' + return process_return != b"" except subprocess.CalledProcessError: # El proceso no está corriendo return False @@ -132,8 +132,7 @@ def check_gpu_acceleration(): try: # Verifica si /dev/dri existe if not os.path.exists("/dev/dri"): - LogManager.logger.error( - "/dev/dri does not exist. No direct GPU access.") + LogManager.logger.error("/dev/dri does not exist. No direct GPU access.") return "OFF" # # Obtiene la salida de glxinfo @@ -142,9 +141,9 @@ def check_gpu_acceleration(): # print(result) # # Verifica si la aceleración directa está habilitada - # return "direct rendering: Yes" in result + # return "direct rendering: Yes" in result - vendor_name = os.environ['DRI_VENDOR'] + vendor_name = os.environ["DRI_VENDOR"] return vendor_name.upper() except Exception as e: print(f"Error: {e}") @@ -152,28 +151,28 @@ def check_gpu_acceleration(): def get_ros_version(): - output = subprocess.check_output(['bash', '-c', 'echo $ROS_VERSION']) - return output.decode('utf-8')[0] + output = subprocess.check_output(["bash", "-c", "echo $ROS_VERSION"]) + return output.decode("utf-8")[0] def get_user_world(launch_file): - """" - Processes a provided base64 encoded string representing a zip file, decodes it, + """ " + Processes a provided base64 encoded string representing a zip file, decodes it, saves it as a zip file, and then extracts its contents. - The function takes a base64 encoded string (`launch_file`) as input. It first decodes this - string into binary format, then saves this binary content as a zip file in a predetermined - directory ('workspace/binaries/'). After saving, it extracts the contents of the zip file into + The function takes a base64 encoded string (`launch_file`) as input. It first decodes this + string into binary format, then saves this binary content as a zip file in a predetermined + directory ('workspace/binaries/'). After saving, it extracts the contents of the zip file into another specified directory ('workspace/worlds/'). """ try: # Convert base64 to binary binary_content = base64.b64decode(launch_file) # Save the binary content as a file - with open('workspace/binaries/user_worlds.zip', 'wb') as file: + with open("workspace/binaries/user_worlds.zip", "wb") as file: file.write(binary_content) # Unzip the file - with zipfile.ZipFile('workspace/binaries/user_worlds.zip', 'r') as zip_ref: - zip_ref.extractall('workspace/worlds/') + with zipfile.ZipFile("workspace/binaries/user_worlds.zip", "r") as zip_ref: + zip_ref.extractall("workspace/worlds/") except Exception as e: LogManager.logging.error(f"An error occurred getting user world: {e}") diff --git a/manager/libs/singleton.py b/manager/libs/singleton.py index 037e76a..aceb996 100644 --- a/manager/libs/singleton.py +++ b/manager/libs/singleton.py @@ -5,4 +5,5 @@ def get_instance(): if cls not in instances: instances[cls] = cls() return instances[cls] + return get_instance() diff --git a/manager/manager/docker_thread/docker_thread.py b/manager/manager/docker_thread/docker_thread.py index 4fda16a..1531088 100644 --- a/manager/manager/docker_thread/docker_thread.py +++ b/manager/manager/docker_thread/docker_thread.py @@ -1,22 +1,31 @@ """Docker Thread Class""" + import threading import subprocess import os import signal - class DockerThread(threading.Thread): """Threaded Docker Thread Class""" + def __init__(self, cmd, shell=True): threading.Thread.__init__(self) self.cmd = cmd self.process = None - self.shell=shell + self.shell = shell def run(self): - self.process = subprocess.Popen(self.cmd, shell=self.shell, stdout=subprocess.PIPE, stderr=subprocess.PIPE, start_new_session=True, - bufsize=1024, universal_newlines=True, executable="/bin/bash") + self.process = subprocess.Popen( + self.cmd, + shell=self.shell, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + start_new_session=True, + bufsize=1024, + universal_newlines=True, + executable="/bin/bash", + ) self.process.communicate() def terminate(self): @@ -25,4 +34,4 @@ def terminate(self): try: os.killpg(os.getpgid(self.process.pid), signal.SIGTERM) except ProcessLookupError as error: - print(f"{self.process.pid}: Process already terminated {error}") \ No newline at end of file + print(f"{self.process.pid}: Process already terminated {error}") diff --git a/manager/manager/editor/serializers.py b/manager/manager/editor/serializers.py index f779d84..613d757 100644 --- a/manager/manager/editor/serializers.py +++ b/manager/manager/editor/serializers.py @@ -3,7 +3,7 @@ class Completion: - def __init__(self,completion): + def __init__(self, completion): self.label = completion.name self.code = completion.name_with_symbols self.docstring = completion.docstring(raw=True) @@ -13,6 +13,7 @@ def __init__(self,completion): else: self.detail = completion.parent().full_name + def serialize_completions(completions): serialized = [] @@ -24,6 +25,7 @@ def serialize_completions(completions): from rest_framework import serializers + class CompletionSerializer(serializers.Serializer): label = serializers.CharField(max_length=200) detail = serializers.CharField(max_length=200) diff --git a/manager/manager/launcher/launcher_drones.py b/manager/manager/launcher/launcher_drones.py index 7f11b00..7aff0be 100644 --- a/manager/manager/launcher/launcher_drones.py +++ b/manager/manager/launcher/launcher_drones.py @@ -7,6 +7,7 @@ import psutil import threading + class LauncherDrones(ILauncher): exercise_id: str type: str @@ -29,7 +30,7 @@ def run(self, callback: callable = None): 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 = DockerThread(f"python3 {self.launch_file}") self.launch.start() self.threads.append(self.launch) diff --git a/manager/manager/launcher/launcher_drones_gzsim.py b/manager/manager/launcher/launcher_drones_gzsim.py index 7ad0ec4..b3b9628 100644 --- a/manager/manager/launcher/launcher_drones_gzsim.py +++ b/manager/manager/launcher/launcher_drones_gzsim.py @@ -37,4 +37,3 @@ def terminate(self): for thread in self.threads: thread.terminate() thread.join() - \ No newline at end of file diff --git a/manager/manager/launcher/launcher_drones_ros2.py b/manager/manager/launcher/launcher_drones_ros2.py index fc08367..e4c5d6f 100644 --- a/manager/manager/launcher/launcher_drones_ros2.py +++ b/manager/manager/launcher/launcher_drones_ros2.py @@ -36,8 +36,6 @@ def run(self, callback): px4_launch_thread.start() self.threads.append(px4_launch_thread) - - def is_running(self): return True @@ -48,4 +46,3 @@ def terminate(self): thread.terminate() thread.join() self.threads.remove(thread) - \ No newline at end of file diff --git a/manager/manager/launcher/launcher_gzsim_view.py b/manager/manager/launcher/launcher_gzsim_view.py index 2f26856..59d1fb3 100644 --- a/manager/manager/launcher/launcher_gzsim_view.py +++ b/manager/manager/launcher/launcher_gzsim_view.py @@ -82,4 +82,4 @@ def get_dri_path(self): dri_path = os.path.join("/dev/dri", os.environ.get("DRI_NAME", "card1")) else: dri_path = os.path.join("/dev/dri", os.environ.get("DRI_NAME", "card0")) - return dri_path \ No newline at end of file + return dri_path diff --git a/manager/manager/launcher/launcher_interface.py b/manager/manager/launcher/launcher_interface.py index 3c18db4..7654eee 100644 --- a/manager/manager/launcher/launcher_interface.py +++ b/manager/manager/launcher/launcher_interface.py @@ -38,4 +38,4 @@ def check_device(device_path): class LauncherException(Exception): def __init__(self, message): - super(LauncherException, self).__init__(message) \ No newline at end of file + super(LauncherException, self).__init__(message) diff --git a/manager/manager/launcher/launcher_robot.py b/manager/manager/launcher/launcher_robot.py index 303fd4f..00826af 100644 --- a/manager/manager/launcher/launcher_robot.py +++ b/manager/manager/launcher/launcher_robot.py @@ -73,8 +73,8 @@ class LauncherRobot(BaseModel): launchers: Optional[ILauncher] = [] start_pose: Optional[list] = [] - def run(self, start_pose = None): - if (start_pose != None): + def run(self, start_pose=None): + if start_pose != None: self.start_pose = start_pose for module in worlds[self.world][str(self.ros_version)]: module["launch_file"] = self.launch_file_path diff --git a/manager/manager/launcher/launcher_robot_display_view.py b/manager/manager/launcher/launcher_robot_display_view.py index 0460c30..09e153d 100644 --- a/manager/manager/launcher/launcher_robot_display_view.py +++ b/manager/manager/launcher/launcher_robot_display_view.py @@ -21,12 +21,16 @@ def run(self, config_file, callback): robot_display_vnc = Vnc_server() - if (ACCELERATION_ENABLED): - robot_display_vnc.start_vnc_gpu(self.display, self.internal_port, self.external_port,DRI_PATH) + 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) + 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}" diff --git a/manager/manager/launcher/launcher_robot_ros2_api.py b/manager/manager/launcher/launcher_robot_ros2_api.py index 23f568c..9ccffe5 100644 --- a/manager/manager/launcher/launcher_robot_ros2_api.py +++ b/manager/manager/launcher/launcher_robot_ros2_api.py @@ -30,9 +30,7 @@ def run(self, robot_pose, callback): ROBOT_POSE = f"ROBOT_X={robot_pose[0]} ROBOT_Y={robot_pose[1]} ROBOT_Z={robot_pose[2]} ROBOT_ROLL={robot_pose[3]} ROBOT_PITCH={robot_pose[4]} ROBOT_YAW={robot_pose[5]}" if ACCELERATION_ENABLED: - exercise_launch_cmd = ( - f"export VGL_DISPLAY={DRI_PATH}; vglrun {ROBOT_POSE} ros2 launch {self.launch_file}" - ) + exercise_launch_cmd = f"export VGL_DISPLAY={DRI_PATH}; vglrun {ROBOT_POSE} ros2 launch {self.launch_file}" else: exercise_launch_cmd = f"{ROBOT_POSE} ros2 launch {self.launch_file}" @@ -75,4 +73,3 @@ def terminate(self): bufsize=1024, universal_newlines=True, ) - diff --git a/manager/manager/launcher/launcher_ros.py b/manager/manager/launcher/launcher_ros.py index d6307df..af91171 100644 --- a/manager/manager/launcher/launcher_ros.py +++ b/manager/manager/launcher/launcher_ros.py @@ -28,6 +28,7 @@ class LauncherRos(ILauncher): "launch_file": "$EXERCISE_FOLDER/launch/simple_line_follower_ros_headless_default.launch" } """ + exercise_id: str type: str module: str @@ -37,35 +38,45 @@ class LauncherRos(ILauncher): parameters: List[str] launch_file: str - ros_command_line: str = shutil.which('roslaunch') + 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}" + 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] + 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)}" + 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 - ) + 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() diff --git a/manager/manager/launcher/launcher_ros2_api.py b/manager/manager/launcher/launcher_ros2_api.py index 17feb1b..81e17b9 100644 --- a/manager/manager/launcher/launcher_ros2_api.py +++ b/manager/manager/launcher/launcher_ros2_api.py @@ -28,9 +28,7 @@ def run(self, callback): self.threads.append(xserver_thread) if ACCELERATION_ENABLED: - exercise_launch_cmd = ( - f"source /.env;export VGL_DISPLAY={DRI_PATH}; vglrun ros2 launch {self.launch_file}" - ) + exercise_launch_cmd = f"source /.env;export VGL_DISPLAY={DRI_PATH}; vglrun ros2 launch {self.launch_file}" else: exercise_launch_cmd = f"source /.env;ros2 launch {self.launch_file}" diff --git a/manager/manager/launcher/launcher_ros_api.py b/manager/manager/launcher/launcher_ros_api.py index 1c0abdc..b133936 100644 --- a/manager/manager/launcher/launcher_ros_api.py +++ b/manager/manager/launcher/launcher_ros_api.py @@ -15,7 +15,7 @@ class RosProcessListener(roslaunch.pmon.ProcessListener): def __init__(self, *args, **kwargs): - self.callback = kwargs.get('callback', None) + self.callback = kwargs.get("callback", None) def process_died(self, name, exit_code): print(f"ROS process {name} terminated with code {exit_code}") @@ -47,7 +47,8 @@ def run(self, callback: callable = None): 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]) + uuid, [self.launch_file], process_listeners=[self.listener] + ) self.launch.start() wait_for_process_to_start("rosmaster", timeout=60) diff --git a/manager/manager/launcher/launcher_rviz_ros2.py b/manager/manager/launcher/launcher_rviz_ros2.py index 3790802..06fb629 100755 --- a/manager/manager/launcher/launcher_rviz_ros2.py +++ b/manager/manager/launcher/launcher_rviz_ros2.py @@ -4,6 +4,7 @@ import os import stat + class LauncherRvizRos2(ILauncher): display: str internal_port: str @@ -17,7 +18,9 @@ def run(self, callback): rviz_vnc = Vnc_server() if ACCELERATION_ENABLED: - rviz_vnc.start_vnc_gpu(self.display, self.internal_port, self.external_port, DRI_PATH) + rviz_vnc.start_vnc_gpu( + self.display, self.internal_port, self.external_port, DRI_PATH + ) rviz_cmd = f"export DISPLAY={self.display}; export VGL_DISPLAY={DRI_PATH}; vglrun rviz2" else: rviz_vnc.start_vnc(self.display, self.internal_port, self.external_port) diff --git a/manager/manager/launcher/launcher_teleoperator_ros2.py b/manager/manager/launcher/launcher_teleoperator_ros2.py index 9b2058c..409ad21 100644 --- a/manager/manager/launcher/launcher_teleoperator_ros2.py +++ b/manager/manager/launcher/launcher_teleoperator_ros2.py @@ -13,7 +13,7 @@ def run(self, callback): DRI_PATH = self.get_dri_path() ACCELERATION_ENABLED = self.check_device(DRI_PATH) - if (ACCELERATION_ENABLED): + 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" @@ -36,4 +36,4 @@ def terminate(self): self.running = False def died(self): - pass \ No newline at end of file + pass diff --git a/manager/manager/lint/linter.py b/manager/manager/lint/linter.py index 06a77a2..189f3fb 100644 --- a/manager/manager/lint/linter.py +++ b/manager/manager/lint/linter.py @@ -2,6 +2,7 @@ import os import subprocess + class Lint: def clean_pylint_output(self, result, warnings=False): @@ -16,23 +17,24 @@ def clean_pylint_output(self, result, warnings=False): r":[0-9]+:[0-9]+: E1101:.*Module 'ompl.*", # ompl E1101 error r":[0-9]+:[0-9]+:.*value.*argument.*unbound.*method.*", # No value for argument 'self' error r":[0-9]+:[0-9]+: E1111:.*", # Assignment from no return error - r":[0-9]+:[0-9]+: E1136:.*" # E1136 until issue is resolved + r":[0-9]+:[0-9]+: E1136:.*", # E1136 until issue is resolved ] if not warnings: # Remove convention, refactor, and warning messages if warnings are not desired for pattern in patterns[:3]: - result = re.sub(r"^[^:]*" + pattern, '', result, flags=re.MULTILINE) - + result = re.sub(r"^[^:]*" + pattern, "", result, flags=re.MULTILINE) + # Remove specific errors for pattern in patterns[3:]: - result = re.sub(r"^[^:]*" + pattern, '', result, flags=re.MULTILINE) + result = re.sub(r"^[^:]*" + pattern, "", result, flags=re.MULTILINE) - - result = re.sub(r"^\s*$\n", '', result, flags=re.MULTILINE) + result = re.sub(r"^\s*$\n", "", result, flags=re.MULTILINE) # Transform the remaining error messages - result = re.sub(r"^[^:]+:(\d+):\d+: \w*:\s*(.*)", r"line \1: \2", result, flags=re.MULTILINE) + result = re.sub( + r"^[^:]+:(\d+):\d+: \w*:\s*(.*)", r"line \1: \2", result, flags=re.MULTILINE + ) if result.strip() and re.search(r"line", result): result = "Traceback (most recent call last):\n" + result.strip() @@ -40,53 +42,58 @@ def clean_pylint_output(self, result, warnings=False): return result def append_rating_if_missing(self, result): - rating_message = "-----------------------------------\nYour code has been rated at 0.00/10" - + rating_message = ( + "-----------------------------------\nYour code has been rated at 0.00/10" + ) + # Check if the rating message already exists if not re.search(r"Your code has been rated", result): result += "\n" + rating_message if not re.search(r"error", result) and not re.search(r"undefined", result): - result = '' + result = "" return result - def evaluate_code(self, code, ros_version, warnings=False, py_lint_source="pylint_checker.py"): + def evaluate_code( + self, code, ros_version, warnings=False, py_lint_source="pylint_checker.py" + ): try: - code = re.sub(r'from HAL import HAL', 'from hal import HAL', code) - code = re.sub(r'from GUI import GUI', 'from gui import GUI', code) - code = re.sub(r'from MAP import MAP', 'from map import MAP', code) - code = re.sub(r'\nimport cv2\n', '\nfrom cv2 import cv2\n', code) + code = re.sub(r"from HAL import HAL", "from hal import HAL", code) + code = re.sub(r"from GUI import GUI", "from gui import GUI", code) + code = re.sub(r"from MAP import MAP", "from map import MAP", code) + code = re.sub(r"\nimport cv2\n", "\nfrom cv2 import cv2\n", code) # Avoids EOF error when iterative code is empty (which prevents other errors from showing) while_position = re.search( - r'[^ ]while\s*\(\s*True\s*\)\s*:|[^ ]while\s*True\s*:|[^ ]while\s*1\s*:|[^ ]while\s*\(\s*1\s*\)\s*:', code) + r"[^ ]while\s*\(\s*True\s*\)\s*:|[^ ]while\s*True\s*:|[^ ]while\s*1\s*:|[^ ]while\s*\(\s*1\s*\)\s*:", + code, + ) if while_position is None: while_error = "ERROR: While loop is required and was not found.\n" return while_error.strip() - sequential_code = code[:while_position.start()] - iterative_code = code[while_position.start():] + sequential_code = code[: while_position.start()] + iterative_code = code[while_position.start() :] iterative_code = re.sub( - r'[^ ]while\s*\(\s*True\s*\)\s*:|[^ ]while\s*True\s*:|[^ ]while\s*1\s*:|[^ ]while\s*\(\s*1\s*\)\s*:', '\n', iterative_code, 1) - iterative_code = re.sub(r'^[ ]{4}', '', iterative_code, flags=re.M) + r"[^ ]while\s*\(\s*True\s*\)\s*:|[^ ]while\s*True\s*:|[^ ]while\s*1\s*:|[^ ]while\s*\(\s*1\s*\)\s*:", + "\n", + iterative_code, + 1, + ) + iterative_code = re.sub(r"^[ ]{4}", "", iterative_code, flags=re.M) code = sequential_code + iterative_code - + f = open("user_code.py", "w") f.write(code) f.close() command = "" - if "humble" in str(ros_version): + if "humble" in str(ros_version): command = f"export PYTHONPATH=$PYTHONPATH:/workspace/code; python3 /RoboticsApplicationManager/manager/manager/lint/{py_lint_source}" else: command = f"export PYTHONPATH=$PYTHONPATH:/workspace/code; python3 /RoboticsApplicationManager/manager/manager/lint/{py_lint_source}" - - ret = subprocess.run( - command, - capture_output=True, - text=True, - shell=True - ) - + + ret = subprocess.run(command, capture_output=True, text=True, shell=True) + result = ret.stdout result = result + "\n" diff --git a/manager/manager/lint/pylint_checker.py b/manager/manager/lint/pylint_checker.py index df89f19..e25ab94 100644 --- a/manager/manager/lint/pylint_checker.py +++ b/manager/manager/lint/pylint_checker.py @@ -3,7 +3,7 @@ import os # Read user code -code = open('user_code.py') +code = open("user_code.py") python_code = code.read() code.close() @@ -16,7 +16,7 @@ options = f"{code_file.name} --enable=similarities --disable=C0114,C0116" # Run pylint using subprocess -result = subprocess.run(['pylint'] + options.split(), capture_output=True, text=True) +result = subprocess.run(["pylint"] + options.split(), capture_output=True, text=True) # Process pylint exit stdout = result.stdout @@ -25,4 +25,4 @@ if os.path.exists(code_file.name): os.remove(code_file.name) -print(stdout) \ No newline at end of file +print(stdout) diff --git a/manager/manager/lint/pylint_checker_style.py b/manager/manager/lint/pylint_checker_style.py index bad58b6..ceb5586 100644 --- a/manager/manager/lint/pylint_checker_style.py +++ b/manager/manager/lint/pylint_checker_style.py @@ -3,7 +3,7 @@ import os # Read user code -code = open('user_code.py') +code = open("user_code.py") python_code = code.read() code.close() @@ -16,7 +16,7 @@ options = f"{code_file.name} --enable=similarities --disable=C0114,C0116,C0411,E0401,R0022,W0012 --max-line-length=80 --reports=y" # Run pylint using subprocess -result = subprocess.run(['pylint'] + options.split(), capture_output=True, text=True) +result = subprocess.run(["pylint"] + options.split(), capture_output=True, text=True) # Process pylint exit stdout = result.stdout @@ -26,7 +26,7 @@ os.remove(code_file.name) # Replace tmp file name with user_code -output = stdout.replace(code_file.name, "user_code") # For tmp/**** -output = output.replace(code_file.name.replace("/tmp/",""), "user_code") # For **** +output = stdout.replace(code_file.name, "user_code") # For tmp/**** +output = output.replace(code_file.name.replace("/tmp/", ""), "user_code") # For **** -print(output) \ No newline at end of file +print(output) diff --git a/manager/manager/manager.py b/manager/manager/manager.py index 89ab767..6a34c61 100644 --- a/manager/manager/manager.py +++ b/manager/manager/manager.py @@ -6,8 +6,7 @@ import black - -sys.path.insert(0, '/RoboticsApplicationManager') +sys.path.insert(0, "/RoboticsApplicationManager") import os import signal @@ -44,6 +43,7 @@ from manager.manager.lint.linter import Lint from manager.manager.editor.serializers import serialize_completions + class Manager: states = [ "idle", @@ -122,34 +122,58 @@ class Manager: "dest": "idle", "before": "on_disconnect", }, - # Style check + # Style check { "trigger": "style_check", - "source": ["idle", "connected", "paused", "world_ready","visualization_ready"], + "source": [ + "idle", + "connected", + "paused", + "world_ready", + "visualization_ready", + ], "dest": "=", "before": "on_style_check_application", }, - # Code analysis + # Code analysis { "trigger": "code_analysis", - "source": ["idle", "connected", "paused", "world_ready","visualization_ready"], + "source": [ + "idle", + "connected", + "paused", + "world_ready", + "visualization_ready", + ], "dest": "=", "before": "on_code_analysis", }, - # Code analysis + # Code analysis { "trigger": "code_format", - "source": ["idle", "connected", "paused", "world_ready","visualization_ready"], + "source": [ + "idle", + "connected", + "paused", + "world_ready", + "visualization_ready", + ], "dest": "=", "before": "on_code_format", }, - # Code analysis + # Code analysis { "trigger": "code_autocomplete", - "source": ["idle", "connected", "paused", "world_ready","visualization_ready"], + "source": [ + "idle", + "connected", + "paused", + "world_ready", + "visualization_ready", + ], "dest": "=", "before": "on_code_autocomplete", - } + }, ] def __init__(self, host: str, port: int): @@ -245,12 +269,12 @@ def on_launch_world(self, event): The method logs the start of the launch transition and the configuration details for debugging and traceability. """ cfg_dict = event.kwargs.get("data", {}) - world_cfg = cfg_dict['world'] - robot_cfg = cfg_dict['robot'] + world_cfg = cfg_dict["world"] + robot_cfg = cfg_dict["robot"] # Launch world try: - if world_cfg['world'] == None: + if world_cfg["world"] == None: self.world_launcher = None LogManager.logger.info("Launch transition finished") return @@ -272,7 +296,7 @@ def on_launch_world(self, event): # Launch robot try: - if robot_cfg['world'] == None: + if robot_cfg["world"] == None: self.robot_launcher = None LogManager.logger.info("Launch transition finished") return @@ -285,7 +309,7 @@ def on_launch_world(self, event): self.robot_launcher = LauncherRobot(**cfg.model_dump()) LogManager.logger.info(str(self.robot_launcher)) - self.robot_launcher.run(robot_cfg['start_pose']) + self.robot_launcher.run(robot_cfg["start_pose"]) LogManager.logger.info("Launch transition finished") def prepare_custom_universe(self, cfg_dict): @@ -314,28 +338,29 @@ def prepare_custom_universe(self, cfg_dict): zip_ref.extractall(universe_folder + "/") zip_ref.close() - os.system('/bin/bash -c "cd /workspace/worlds; source /opt/ros/humble/setup.bash; colcon build --symlink-install; source install/setup.bash; cd ../.."') + os.system( + '/bin/bash -c "cd /workspace/worlds; source /opt/ros/humble/setup.bash; colcon build --symlink-install; source install/setup.bash; cd ../.."' + ) def on_prepare_visualization(self, event): LogManager.logger.info("Visualization transition started") cfg_dict = event.kwargs.get("data", {}) - self.visualization_type = cfg_dict['type'] - config_file = cfg_dict['file'] + self.visualization_type = cfg_dict["type"] + config_file = cfg_dict["file"] self.visualization_launcher = LauncherVisualization( - visualization=self.visualization_type, - visualization_config_path = config_file + visualization=self.visualization_type, visualization_config_path=config_file ) - + self.visualization_launcher.run() if self.visualization_type in ["gazebo_rae", "gzsim_rae", "console"]: self.gui_server = Server(2303, self.update) self.gui_server.start() elif self.visualization_type in ["bt_studio", "bt_studio_gz"]: - self.gui_server = FileWatchdog('/tmp/tree_state', self.update_bt_studio) + self.gui_server = FileWatchdog("/tmp/tree_state", self.update_bt_studio) self.gui_server.start() LogManager.logger.info("Visualization transition finished") @@ -374,7 +399,7 @@ def on_style_check_application(self, event): """ Handles the 'style_check' event, does not change the state and returns the current state. - It uses the linter to check if the style of the code is correct, if there + It uses the linter to check if the style of the code is correct, if there are errors it writes them in all the consoles and raises the errors. Parameters: @@ -383,21 +408,24 @@ def on_style_check_application(self, event): Raises: Exception: with the errors found in the linter """ + def find_docker_console(): """Search console in docker different of /dev/pts/0""" - pts_consoles = [f"/dev/pts/{dev}" for dev in os.listdir('/dev/pts/') if dev.isdigit()] + pts_consoles = [ + f"/dev/pts/{dev}" for dev in os.listdir("/dev/pts/") if dev.isdigit() + ] consoles = [] for console in pts_consoles: if console != "/dev/pts/0": try: # Search if it's a console - with open(console, 'w') as f: + with open(console, "w") as f: f.write("") consoles.append(console) except Exception: # Continue searching continue - + # raise Exception("No active console other than /dev/pts/0") return consoles @@ -417,19 +445,24 @@ def find_docker_console(): code = code.replace("from HAL import HAL", "import HAL") # Create executable app - errors = self.linter.evaluate_code(code, exercise_id, self.ros_version, py_lint_source="pylint_checker_style.py") + errors = self.linter.evaluate_code( + code, + exercise_id, + self.ros_version, + py_lint_source="pylint_checker_style.py", + ) if errors == "": errors = "No errors found" console_path = find_docker_console() for i in console_path: - with open(i, 'w') as console: + with open(i, "w") as console: console.write(errors + "\n\n") raise Exception(errors) - def on_code_analysis(self, event): + def on_code_analysis(self, event): """ Handles the 'code_analysis' event, does not change the state and returns the current state. @@ -452,48 +485,45 @@ def on_code_analysis(self, event): LogManager.logger.info("User code not found") return - # Save the code string to a temporary file with tempfile.NamedTemporaryFile(suffix=".py", delete=False) as temp_file: - temp_file.write(code_string.encode('utf-8')) + temp_file.write(code_string.encode("utf-8")) temp_file_path = temp_file.name - - + # terminal command - command = ['pylint', '--output-format=json',] + [temp_file_path] + command = [ + "pylint", + "--output-format=json", + ] + [temp_file_path] # '--extension-pkg-whitelist=cv2' - + # Add the disable option for specific error IDs if disable_error_ids: - disable_str = ','.join(disable_error_ids) - command.append(f'--disable={disable_str}') - + disable_str = ",".join(disable_error_ids) + command.append(f"--disable={disable_str}") + # run the command result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - + # Decode the results - pylint_output = result.stdout.decode('utf-8') - pylint_errors = result.stderr.decode('utf-8') - + pylint_output = result.stdout.decode("utf-8") + pylint_errors = result.stderr.decode("utf-8") + # Parse the JSON output if pylint output is not empty try: pylint_json = json.loads(pylint_output) if pylint_output else [] except json.JSONDecodeError as e: LogManager.logger.info(f"Failed to parse JSON: {str(e)}") - # Clean up the temporary file after Pylint run if os.path.exists(temp_file_path): os.remove(temp_file_path) - + if pylint_errors: LogManager.logger.info("Found errors in code") - + self.consumer.send_message( - { - "pylint_output": pylint_json, - "pylint_errors": pylint_errors - }, + {"pylint_output": pylint_json, "pylint_errors": pylint_errors}, command="code-analysis", ) @@ -518,7 +548,7 @@ def on_code_format(self, event): if not code: LogManager.logger.info("User code not found") return - + try: # Format the code with Black formatted_code = black.format_str(code, mode=black.Mode()) @@ -529,7 +559,7 @@ def on_code_format(self, event): command="code-format", ) except Exception as e: - LogManager.logger.info('Error formating code' + str(e)) + LogManager.logger.info("Error formating code" + str(e)) def on_code_autocomplete(self, event): """ @@ -550,18 +580,18 @@ def on_code_autocomplete(self, event): line = app_cfg["line"] col = app_cfg["col"] - jedi.settings.add_bracket_after_function= True - + jedi.settings.add_bracket_after_function = True + # if code string is empty if not code: LogManager.logger.info("User code not found") return - + if not line or not col: LogManager.logger.info("User code position not found") return - - script = jedi.Script(code, path='/workspace/code/academy.py') + + script = jedi.Script(code, path="/workspace/code/academy.py") try: completions = script.complete(line, col) @@ -574,27 +604,29 @@ def on_code_autocomplete(self, event): command="code-autocomplete", ) except Exception as e: - LogManager.logger.info('Error formating code' + str(e)) - + LogManager.logger.info("Error formating code" + str(e)) + def on_run_application(self, event): def find_docker_console(): """Search console in docker different of /dev/pts/0""" - pts_consoles = [f"/dev/pts/{dev}" for dev in os.listdir('/dev/pts/') if dev.isdigit()] + pts_consoles = [ + f"/dev/pts/{dev}" for dev in os.listdir("/dev/pts/") if dev.isdigit() + ] consoles = [] for console in pts_consoles: if console != "/dev/pts/0": try: # Search if it's a console - with open(console, 'w') as f: + with open(console, "w") as f: f.write("") consoles.append(console) except Exception: # Continue searching continue - + # raise Exception("No active console other than /dev/pts/0") return consoles - + def prepare_RA_code(code_path): f = open(code_path, "r") code = f.read() @@ -616,7 +648,7 @@ def prepare_RA_code(code_path): else: console_path = find_docker_console() for i in console_path: - with open(i, 'w') as console: + with open(i, "w") as console: console.write(errors + "\n\n") raise Exception(errors) @@ -655,9 +687,9 @@ def prepare_RA_code(code_path): if not os.path.isfile(code_path): LogManager.logger.info("User code not found") raise Exception("User code not found") - + try: - if (type == "robotics-academy"): + if type == "robotics-academy": prepare_RA_code(code_path) fds = os.listdir("/dev/pts/") @@ -665,7 +697,7 @@ def prepare_RA_code(code_path): self.application_process = subprocess.Popen( ["python3", code_path], - stdin=open('/dev/pts/' + console_fd, 'r'), + stdin=open("/dev/pts/" + console_fd, "r"), stdout=sys.stdout, stderr=subprocess.STDOUT, bufsize=1024, @@ -674,43 +706,49 @@ def prepare_RA_code(code_path): self.unpause_sim() except: LogManager.logger.info("Run application failed") - + LogManager.logger.info("Run application transition finished") - + def terminate_harmonic_processes(self): """ Terminate all processes within the Docker container whose command line contains 'gz' or 'launch'. """ LogManager.logger.info("Terminate Harmonic process") - keywords = ['gz', 'launch'] + keywords = ["gz", "launch"] for keyword in keywords: try: - ps_aux_cmd = ['ps', 'aux'] - grep_cmd = ['grep', keyword] - grep_exclude_cmd = ['grep', '-v', 'grep'] + ps_aux_cmd = ["ps", "aux"] + grep_cmd = ["grep", keyword] + grep_exclude_cmd = ["grep", "-v", "grep"] ps_aux_proc = subprocess.Popen(ps_aux_cmd, stdout=subprocess.PIPE) - grep_proc = subprocess.Popen(grep_cmd, stdin=ps_aux_proc.stdout, stdout=subprocess.PIPE) - exclude_grep_proc = subprocess.Popen(grep_exclude_cmd, stdin=grep_proc.stdout, stdout=subprocess.PIPE) + grep_proc = subprocess.Popen( + grep_cmd, stdin=ps_aux_proc.stdout, stdout=subprocess.PIPE + ) + exclude_grep_proc = subprocess.Popen( + grep_exclude_cmd, stdin=grep_proc.stdout, stdout=subprocess.PIPE + ) ps_aux_proc.stdout.close() grep_proc.stdout.close() - output = exclude_grep_proc.communicate()[0].decode('utf-8') - + output = exclude_grep_proc.communicate()[0].decode("utf-8") + for line in output.splitlines(): try: # Extract PID pid = int(line.split()[1]) - subprocess.run(['kill', '-15', str(pid)], check=True) - + subprocess.run(["kill", "-15", str(pid)], check=True) + # Avoid zombies try: os.waitpid(pid, 0) except ChildProcessError: pass except Exception as e: - LogManager.logger.exception(f"Failed to terminate process with line: {line}. Error: {e}") + LogManager.logger.exception( + f"Failed to terminate process with line: {line}. Error: {e}" + ) except Exception as e: LogManager.logger.exception( @@ -778,7 +816,7 @@ def on_disconnect(self, event): self.world_launcher.terminate() except Exception as e: LogManager.logger.exception("Exception terminating world launcher") - + self.terminate_harmonic_processes() # Reiniciar el script @@ -803,7 +841,9 @@ def on_pause(self, msg): except Exception as e: LogManager.logger.exception("Error suspending process") else: - LogManager.logger.warning("Application process was None during pause. Calling termination.") + LogManager.logger.warning( + "Application process was None during pause. Calling termination." + ) self.pause_sim() self.reset_sim() @@ -816,31 +856,57 @@ def on_resume(self, msg): except Exception as e: LogManager.logger.exception("Error suspending process") else: - LogManager.logger.warning("Application process was None during resume. Calling termination.") + LogManager.logger.warning( + "Application process was None during resume. Calling termination." + ) self.reset_sim() def pause_sim(self): if self.visualization_type in ["gzsim_rae", "bt_studio_gz"]: - self.call_gzservice("$(gz service -l | grep '^/world/\w*/control$')","gz.msgs.WorldControl","gz.msgs.Boolean","3000","pause: true") + self.call_gzservice( + "$(gz service -l | grep '^/world/\w*/control$')", + "gz.msgs.WorldControl", + "gz.msgs.Boolean", + "3000", + "pause: true", + ) elif not self.visualization_type in ["console"]: self.call_service("/pause_physics", "std_srvs/srv/Empty") def unpause_sim(self): if self.visualization_type in ["gzsim_rae", "bt_studio_gz"]: - self.call_gzservice("$(gz service -l | grep '^/world/\w*/control$')","gz.msgs.WorldControl","gz.msgs.Boolean","3000","pause: false") + self.call_gzservice( + "$(gz service -l | grep '^/world/\w*/control$')", + "gz.msgs.WorldControl", + "gz.msgs.Boolean", + "3000", + "pause: false", + ) elif not self.visualization_type in ["console"]: self.call_service("/unpause_physics", "std_srvs/srv/Empty") def reset_sim(self): if self.robot_launcher: self.robot_launcher.terminate() - + if self.visualization_type in ["gzsim_rae", "bt_studio_gz"]: if self.is_ros_service_available("/drone0/platform/state_machine/_reset"): - self.call_service("/drone0/platform/state_machine/_reset", "std_srvs/srv/Trigger", "{}") - self.call_gzservice("$(gz service -l | grep '^/world/\w*/control$')","gz.msgs.WorldControl","gz.msgs.Boolean","3000","reset: {all: true}") + self.call_service( + "/drone0/platform/state_machine/_reset", + "std_srvs/srv/Trigger", + "{}", + ) + self.call_gzservice( + "$(gz service -l | grep '^/world/\w*/control$')", + "gz.msgs.WorldControl", + "gz.msgs.Boolean", + "3000", + "reset: {all: true}", + ) if self.is_ros_service_available("/drone0/controller/_reset"): - self.call_service("/drone0/controller/_reset", "std_srvs/srv/Trigger", "{}") + self.call_service( + "/drone0/controller/_reset", "std_srvs/srv/Trigger", "{}" + ) elif not self.visualization_type in ["console"]: self.call_service("/reset_world", "std_srvs/srv/Empty") @@ -860,7 +926,7 @@ def call_service(self, service, service_type, request_data="{}"): bufsize=1024, universal_newlines=True, ) - + def call_gzservice(self, service, reqtype, reptype, timeout, req): command = f"gz service -s {service} --reqtype {reqtype} --reptype {reptype} --timeout {timeout} --req '{req}'" subprocess.call( @@ -874,12 +940,17 @@ def call_gzservice(self, service, reqtype, reptype, timeout, req): def is_ros_service_available(self, service_name): try: - result = subprocess.run(['ros2', 'service', 'list', '--include-hidden-services'], capture_output=True, text=True, check=True) + result = subprocess.run( + ["ros2", "service", "list", "--include-hidden-services"], + capture_output=True, + text=True, + check=True, + ) return service_name in result.stdout except subprocess.CalledProcessError as e: LogManager.logger.exception(f"Error checking service availability: {e}") return False - + def start(self): """ Starts the RAM @@ -910,7 +981,9 @@ def signal_handler(sign, frame): stop_process_and_children(self.application_process) self.application_process = None except Exception as e: - LogManager.logger.exception("Exception stopping application process") + LogManager.logger.exception( + "Exception stopping application process" + ) if self.visualization_launcher: try: @@ -931,7 +1004,7 @@ def signal_handler(sign, frame): self.world_launcher.terminate() except Exception as e: LogManager.logger.exception("Exception terminating world launcher") - + self.terminate_harmonic_processes() exit() diff --git a/manager/ram_logging/log_manager.py b/manager/ram_logging/log_manager.py index ba8c6a1..ed97894 100644 --- a/manager/ram_logging/log_manager.py +++ b/manager/ram_logging/log_manager.py @@ -40,11 +40,14 @@ def format(self, record): if color: msg = f"{color}{msg}{self.RESET}" if len(msg) > self.MAX_LENGTH: - final_msg = msg[:self.MAX_LENGTH] + "....." + msg[len(msg)-self.MAX_LENGTH:] + final_msg = ( + msg[: self.MAX_LENGTH] + "....." + msg[len(msg) - self.MAX_LENGTH :] + ) return final_msg else: return msg + @singleton class LogManager: def __init__(self): @@ -57,11 +60,13 @@ def __init__(self): date_format = "%H:%M:%S" self.log_formatter = logging.Formatter(log_format, date_format) self.color_formatter = ColorFormatter( - log_format, date_format) # Formatter con color + log_format, date_format + ) # Formatter con color self.max_color_formatter = MaxLengthColorFormatter( - log_format, date_format) # Formatter con color + log_format, date_format + ) # Formatter con color - self.logger = logging.getLogger('my_app_logger') + self.logger = logging.getLogger("my_app_logger") self.logger.setLevel(log_level) self.logger.propagate = False diff --git a/test/dummyclient.py b/test/dummyclient.py index 20a6cee..1f58524 100755 --- a/test/dummyclient.py +++ b/test/dummyclient.py @@ -1,28 +1,31 @@ import websocket import sys import os + sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from manager.comms.consumer_message import ManagerConsumerMessage from manager.libs.launch_world_model import ConfigurationModel class ConnectCmd(ManagerConsumerMessage): - id: str = '1' - command: str = 'connect' + id: str = "1" + command: str = "connect" class LaunchWorldCmd(ManagerConsumerMessage): - id: str = '2' - command: str = 'launch_world' + id: str = "2" + command: str = "launch_world" data: ConfigurationModel = ConfigurationModel( - world='gazebo', - launch_file_path='/opt/jderobot/Launchers/simple_circuit_followingcam.launch.py') + world="gazebo", + launch_file_path="/opt/jderobot/Launchers/simple_circuit_followingcam.launch.py", + ) class LaunchPrepareViz(ManagerConsumerMessage): - id: str = '3' - command: str = 'prepare_visualization' - data: str = 'gazebo_rae' + id: str = "3" + command: str = "prepare_visualization" + data: str = "gazebo_rae" + websocket.enableTrace(True) ws = websocket.create_connection("ws://localhost:7163") @@ -39,4 +42,4 @@ class LaunchPrepareViz(ManagerConsumerMessage): exit() -# Open VNC: http://127.0.0.1:6080/vnc.html \ No newline at end of file +# Open VNC: http://127.0.0.1:6080/vnc.html