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