1+ import json
2+ import logging
3+ import os .path
4+ import subprocess
5+ import sys
6+ import threading
7+ import time
8+ from threading import Thread
9+
10+ from src .manager .libs .applications .compatibility .client import Client
11+ from src .manager .libs .process_utils import stop_process_and_children
12+ from src .manager .ram_logging .log_manager import LogManager
13+ from src .manager .manager .application .robotics_python_application_interface import IRoboticsPythonApplication
14+ from src .manager .manager .lint .linter import Lint
15+
16+
17+ class CompatibilityExerciseWrapperRos2 (IRoboticsPythonApplication ):
18+ def __init__ (self , exercise_command , gui_command , update_callback ):
19+ super ().__init__ (update_callback )
20+
21+ home_dir = os .path .expanduser ('~' )
22+ self .running = False
23+ self .linter = Lint ()
24+ self .brain_ready_event = threading .Event ()
25+ # TODO: review hardcoded values
26+ process_ready , self .exercise_server = self ._run_exercise_server (f"python3 { exercise_command } " ,
27+ f'{ home_dir } /ws_code.log' ,
28+ 'websocket_code=ready' )
29+ if process_ready :
30+ LogManager .logger .info (
31+ f"Exercise code { exercise_command } launched" )
32+ time .sleep (1 )
33+ self .exercise_connection = Client (
34+ 'ws://127.0.0.1:1905' , 'exercise' , self .server_message )
35+ self .exercise_connection .start ()
36+ else :
37+ self .exercise_server .kill ()
38+ raise RuntimeError (f"Exercise { exercise_command } could not be run" )
39+
40+ process_ready , self .gui_server = self ._run_exercise_server (f"python3 { gui_command } " , f'{ home_dir } /ws_gui.log' ,
41+ 'websocket_gui=ready' )
42+ if process_ready :
43+ LogManager .logger .info (f"Exercise gui { gui_command } launched" )
44+ time .sleep (1 )
45+ self .gui_connection = Client (
46+ 'ws://127.0.0.1:2303' , 'gui' , self .server_message )
47+ self .gui_connection .start ()
48+ else :
49+ self .gui_server .kill ()
50+ raise RuntimeError (f"Exercise GUI { gui_command } could not be run" )
51+
52+ self .running = True
53+
54+ self .start_send_freq_thread ()
55+
56+
57+ def send_freq (self , exercise_connection , is_alive ):
58+ """Send the frequency of the brain and gui to the exercise server"""
59+ while is_alive ():
60+ exercise_connection .send (
61+ """#freq{"brain": 20, "gui": 10, "rtf": 100}""" )
62+ time .sleep (1 )
63+
64+ def start_send_freq_thread (self ):
65+ """Start a thread to send the frequency of the brain and gui to the exercise server"""
66+ daemon = Thread (target = lambda : self .send_freq (self .exercise_connection ,
67+ lambda : self .is_alive ), daemon = False , name = 'Monitor frequencies' )
68+ daemon .start ()
69+
70+ def _run_exercise_server (self , cmd , log_file , load_string , timeout : int = 5 ):
71+ process = subprocess .Popen (f"{ cmd } " , shell = True , stdout = sys .stdout , stderr = subprocess .STDOUT ,
72+ bufsize = 1024 , universal_newlines = True )
73+
74+ process_ready = False
75+ while not process_ready :
76+ try :
77+ f = open (log_file , "r" )
78+ if f .readline () == load_string :
79+ process_ready = True
80+ f .close ()
81+ time .sleep (0.2 )
82+ except Exception as e :
83+ LogManager .logger .debug (
84+ f"waiting for server string '{ load_string } '..." )
85+ time .sleep (0.2 )
86+
87+ return process_ready , process
88+
89+ def server_message (self , name , message ):
90+ if name == "gui" : # message received from GUI server
91+ LogManager .logger .debug (
92+ f"Message received from gui: { message [:30 ]} " )
93+ self ._process_gui_message (message )
94+ elif name == "exercise" : # message received from EXERCISE server
95+ if message .startswith ("#exec" ):
96+ self .brain_ready_event .set ()
97+ LogManager .logger .info (
98+ f"Message received from exercise: { message [:30 ]} " )
99+ self ._process_exercise_message (message )
100+
101+ def _process_gui_message (self , message ):
102+ payload = json .loads (message [4 :])
103+ self .update_callback (payload )
104+ self .gui_connection .send ("#ack" )
105+
106+ def _process_exercise_message (self , message ):
107+ comand = message [:5 ]
108+ if (message == comand ):
109+ payload = comand
110+ else :
111+ payload = json .loads (message [5 :])
112+ self .update_callback (payload )
113+ self .exercise_connection .send ("#ack" )
114+
115+ def call_service (self , service , service_type ):
116+ command = f"ros2 service call { service } { service_type } "
117+ subprocess .call (f"{ command } " , shell = True , stdout = sys .stdout , stderr = subprocess .STDOUT , bufsize = 1024 ,
118+ universal_newlines = True )
119+
120+ def run (self ):
121+ self .call_service ("/unpause_physics" ,"std_srvs/srv/Empty" )
122+ self .exercise_connection .send ("#play" )
123+
124+ def stop (self ):
125+ self .call_service ("/pause_physics" ,"std_srvs/srv/Empty" )
126+ self .call_service ("/reset_world" ,"std_srvs/srv/Empty" )
127+ self .exercise_connection .send ("#rest" )
128+
129+ def resume (self ):
130+ self .call_service ("/unpause_physics" ,"std_srvs/srv/Empty" )
131+ self .exercise_connection .send ("#play" )
132+
133+ def pause (self ):
134+ self .call_service ("/pause_physics" ,"std_srvs/srv/Empty" )
135+ self .exercise_connection .send ("#stop" )
136+
137+ def restart (self ):
138+ # pause_cmd = "ros2 service call /restart_simulation std_srvs/srv/Empty"
139+ # subprocess.call(f"{pause_cmd}", shell=True, stdout=sys.stdout, stderr=subprocess.STDOUT, bufsize=1024,
140+ # universal_newlines=True)
141+ pass
142+
143+ @property
144+ def is_alive (self ):
145+ return self .running
146+
147+ def load_code (self , code : str ):
148+ errors = self .linter .evaluate_code (code )
149+ if errors == "" :
150+ self .brain_ready_event .clear ()
151+ self .exercise_connection .send (f"#code { code } " )
152+ self .brain_ready_event .wait ()
153+ else :
154+ raise Exception (errors )
155+
156+ def terminate (self ):
157+ self .running = False
158+ self .exercise_connection .stop ()
159+ self .gui_connection .stop ()
160+
161+ stop_process_and_children (self .exercise_server )
162+ stop_process_and_children (self .gui_server )
0 commit comments