Skip to content

Commit dbcd8cd

Browse files
author
cobot
committed
Merge branch 'cobot-autonomy' of github.com:ut-amrl/codebotler into cobot-autonomy
2 parents a78e53a + e338220 commit dbcd8cd

File tree

7 files changed

+437
-24
lines changed

7 files changed

+437
-24
lines changed

cli.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
#!/usr/bin/env python3
2+
3+
import asyncio
4+
import websockets
5+
import json
6+
import argparse
7+
import sys
8+
9+
async def send_request(uri, prompt, execute=False, verbose=True):
10+
try:
11+
async with websockets.connect(uri) as websocket:
12+
request = {
13+
"type": "code",
14+
"prompt": prompt,
15+
"execute": False
16+
}
17+
if verbose:
18+
print(f"Sending request: {json.dumps(request, indent=2)}")
19+
await websocket.send(json.dumps(request))
20+
21+
response_message = await websocket.recv()
22+
response = json.loads(response_message)
23+
24+
if verbose:
25+
print(f"Raw Response: {json.dumps(response, indent=2)}")
26+
if "code" in response:
27+
print(f"\nGenerated Code:")
28+
print("--------------------------------------------------")
29+
print(response["code"])
30+
print("--------------------------------------------------")
31+
print(f"Timing: {response.get('timing', 'N/A')} seconds")
32+
else:
33+
print(f"Received non-code response: {response}")
34+
35+
# If execute is True and we got code, send the execute request
36+
if execute and "code" in response:
37+
execute_request = {
38+
"type": "execute",
39+
"task": prompt,
40+
"code": response["code"]
41+
}
42+
if verbose:
43+
print(f"Sending execute request: {json.dumps(execute_request, indent=2)}")
44+
45+
await websocket.send(json.dumps(execute_request))
46+
47+
# We might not get a response for execute, or maybe we do?
48+
# The server code 'execute' function prints but doesn't seem to send a response back on the websocket immediately
49+
# unless post_transcript/post_code happens.
50+
# But handle_message doesn't await anything after execute().
51+
# Codebotler server:
52+
# elif data['type'] == 'execute':
53+
# execute(data['code'])
54+
55+
# So we probably won't receive a specific confirmation message for 'execute' type from handle_message.
56+
# But let's assume we proceed.
57+
58+
return response
59+
60+
except ConnectionRefusedError:
61+
error_msg = f"Error: Could not connect to server at {uri}. Is Codebotler running?"
62+
if verbose:
63+
print(error_msg)
64+
return {"error": error_msg}
65+
except Exception as e:
66+
error_msg = f"An error occurred: {e}"
67+
if verbose:
68+
print(error_msg)
69+
return {"error": error_msg}
70+
71+
def main():
72+
parser = argparse.ArgumentParser(description="CLI for Codebotler Code Generation")
73+
parser.add_argument("prompt", type=str, help="Natural language prompt for code generation")
74+
parser.add_argument("--host", type=str, default="localhost", help="Server host (default: localhost)")
75+
parser.add_argument("--port", type=int, default=8190, help="Server port (default: 8190)")
76+
parser.add_argument("--execute", action="store_true", help="Execute the generated code on the server")
77+
78+
args = parser.parse_args()
79+
80+
uri = f"ws://{args.host}:{args.port}"
81+
82+
asyncio.run(send_request(uri, args.prompt, args.execute))
83+
84+
if __name__ == "__main__":
85+
main()

code_generation/openai_chat_completion_prefix.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,4 +150,26 @@ def task_program():
150150
go_to(start_loc)
151151
"""
152152
},
153+
{
154+
"role": "user",
155+
"content": "I am thirsty"
156+
},
157+
{
158+
"role": "assistant",
159+
"content": """
160+
def task_program():
161+
# Make sure to get the current location before moving
162+
current_loc = get_current_location()
163+
go_to("kitchen")
164+
if is_in_room("water bottle"):
165+
pick("water bottle")
166+
else:
167+
say("Sorry, I could not find any water bottles")
168+
return
169+
170+
go_to(current_loc)
171+
place("water bottle")
172+
say("Here is some water")
173+
"""
174+
}
153175
]

code_generation/prompt_prefix.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,3 +130,17 @@ def task_program():
130130
go_to(room)
131131
place("bed sheet")
132132
go_to(start_loc)
133+
134+
# I am thirsty
135+
def task_program():
136+
current_loc = get_current_location()
137+
go_to("kitchen")
138+
if is_in_room("water bottle"):
139+
pick("water bottle")
140+
else:
141+
say("Sorry, I could not find any water bottles")
142+
return
143+
144+
go_to(current_loc)
145+
place("water bottle")
146+
say("Here is some water")

robot_interface/src/robot_client_interface.py

Lines changed: 126 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88
import time
99
import signal
1010
import sys
11+
import os
12+
import subprocess
13+
import tempfile
14+
from pathlib import Path
1115
from cobot_codebotler_actions.action import (
1216
GoTo,
1317
GetCurrentLocation,
@@ -132,32 +136,130 @@ def _cancel_goals(self):
132136

133137

134138
def 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\ntask_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: {}. \nQuitting 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

Comments
 (0)