88import time
99import signal
1010import sys
11+ import os
12+ import subprocess
13+ import tempfile
14+ from pathlib import Path
1115from cobot_codebotler_actions .action import (
1216 GoTo ,
1317 GetCurrentLocation ,
@@ -132,32 +136,130 @@ def _cancel_goals(self):
132136
133137
134138def execute_task_program (program : str , robot : RobotInterface ):
135- # every time this is called, a RobotInterface Instance is created in the local scope:
136- # might affect performance, but this is clean
139+ """
140+ Executes the task program in a separate process.
141+ Ignored argument: robot (a new instance is created in the subprocess).
142+ """
143+
144+ # We need to add the root of the workspace (where 'src' is) or the package root to sys.path
145+ # so that 'robot_interface' can be imported.
146+ # robot_client_interface.py is in .../src/codebotler/robot_interface/src/robot_client_interface.py
147+ # we want .../src/codebotler in sys.path
148+
149+ current_file_path = Path (__file__ ).resolve ()
150+ # Go up 3 levels: src -> robot_interface -> codebotler
151+ codebotler_pkg_path = current_file_path .parent .parent .parent
152+
153+ script_template = """
154+ import sys
155+ import os
156+ import rclpy
157+ import traceback
158+
159+ # Add the package path to sys.path so we can import the interface
160+ sys.path.insert(0, "{pkg_path}")
161+
162+ try:
163+ from robot_interface.src.robot_client_interface import RobotInterface, RobotExecutionInterrupted
164+ except ImportError:
165+ # Fallback if the path structure is different (e.g. installed package)
137166 try:
138- namespace = {
139- "robot" : robot ,
140- "say" : robot .say ,
141- "go_to" : robot .go_to ,
142- "ask" : robot .ask ,
143- "is_in_room" : robot .is_in_room ,
144- "pick" : robot .pick ,
145- "place" : robot .place ,
146- "get_all_rooms" : robot .get_all_rooms ,
147- "get_current_location" : robot .get_current_location ,
148- "time" : time ,
149- }
150- program_with_call = program + "\n \n task_program()\n "
151- print ("Executing program..." )
152- exec (program_with_call , namespace )
167+ from codebotler.robot_interface.src.robot_client_interface import RobotInterface, RobotExecutionInterrupted
168+ except ImportError as e:
169+ print(f"Could not import RobotInterface: {{e}}")
170+ sys.exit(1)
171+
172+ def main():
173+ rclpy.init()
174+ node = None
175+ try:
176+ node = RobotInterface()
177+
178+ # Define helpers as locals so the nested user function can capture them
179+ say = node.say
180+ go_to = node.go_to
181+ ask = node.ask
182+ is_in_room = node.is_in_room
183+ pick = node.pick
184+ place = node.place
185+ get_all_rooms = node.get_all_rooms
186+ get_current_location = node.get_current_location
187+
188+ print("Executing program in subprocess...")
189+
190+ # User program (should define task_program)
191+ {user_program}
192+
193+ # Call the user program
194+ if 'task_program' in locals():
195+ task_program()
196+ else:
197+ print("Error: task_program() not defined by user code.")
198+
153199 print("Program executed successfully.")
200+
154201 except RobotExecutionInterrupted as i:
155- print (
156- f"Robot Execution stopped as { i } was interrupted! Terminating execution!!"
157- )
202+ print(f"Robot Execution stopped as {{i}} was interrupted! Terminating execution!!")
158203 except Exception as e:
159- print (
160- "There is a problem with executing the program: {}. \n Quitting Execution!! " .format (
161- e
204+ traceback.print_exc()
205+ print(f"There is a problem with executing the program: {{e}}. \\ nQuitting Execution!! ")
206+ sys.exit(1)
207+ finally:
208+ if node:
209+ node.destroy_node()
210+ rclpy.shutdown()
211+
212+ if __name__ == "__main__":
213+ main()
214+ """
215+
216+ # Indent the user program to fit inside main function
217+ # Note: we use a real newline character here for the join
218+ indented_program = "\n " .join (program .splitlines ())
219+
220+ script_content = script_template .format (
221+ pkg_path = str (codebotler_pkg_path ),
222+ user_program = indented_program
223+ )
224+
225+ with tempfile .NamedTemporaryFile (mode = 'w' , suffix = '.py' , delete = False ) as temp_file :
226+ temp_file_path = temp_file .name
227+ temp_file .write (script_content )
228+ temp_file .flush ()
229+
230+ try :
231+ # Pass the current environment to ensure ROS 2 environment variables are inherited
232+ env = os .environ .copy ()
233+
234+ print (f"Starting { temp_file_path } " )
235+
236+ # ensure stdio and stderr is redirected to this processes and printed until the process ends
237+ process = subprocess .Popen (
238+ [sys .executable , temp_file_path ],
239+ env = env ,
240+ stdout = subprocess .PIPE ,
241+ stderr = subprocess .STDOUT ,
242+ # text=True,
243+ # bufsize=1
162244 )
163- )
245+
246+ print (f"Executed" )
247+
248+ ## Stream the output to sys.stdout so it can be captured by the parent process mechanisms (if any)
249+ #with process.stdout:
250+ # for line in iter(process.stdout.readline, ''):
251+ # print(line, end='')
252+
253+ returncode = process .wait ()
254+ if returncode != 0 :
255+ raise subprocess .CalledProcessError (returncode , process .args )
256+
257+ except subprocess .CalledProcessError as e :
258+ print (f"Subprocess execution failed with return code { e .returncode } " )
259+ except Exception as e :
260+ print (f"Error launching subprocess: { e } " )
261+ finally :
262+ print (f"Task complete" )
263+ # file removed by with tempfile block
264+ #if os.path.exists(temp_file_path):
265+ # os.remove(temp_file_path)
0 commit comments