Skip to content

Commit 93c88dd

Browse files
committed
Switching to using ROS messages as input
(ROS nodes + launchfiles + input() seem to be a problem)
1 parent c15897f commit 93c88dd

File tree

3 files changed

+45
-28
lines changed

3 files changed

+45
-28
lines changed

turtlebot4_openai_tutorials/launch/natural_language_nav_launch.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,16 @@ def generate_launch_description():
2525
return LaunchDescription([
2626
DeclareLaunchArgument('openai_api_key', default_value='', description='API key set up via https://platform.openai.com/account/api-keys'),
2727
DeclareLaunchArgument('model_name', default_value='gpt-3.5-turbo', description='OpenAI model name as selected from https://platform.openai.com/docs/guides/gpt'),
28+
DeclareLaunchArgument('parking_brake', default_value='true', description='Set to false to execute commands on the robot'),
2829
Node(
2930
package='turtlebot4_openai_tutorials',
3031
executable='natural_language_nav',
3132
output='screen',
3233
emulate_tty=True,
3334
parameters=[
3435
{'openai_api_key': LaunchConfiguration('openai_api_key')},
35-
{'model_name': LaunchConfiguration('model_name')}
36+
{'model_name': LaunchConfiguration('model_name')},
37+
{'parking_brake': LaunchConfiguration('parking_brake')}
3638
]
3739
),
3840
])

turtlebot4_openai_tutorials/package.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
<test_depend>python3-pytest</test_depend>
1414

1515
<depend>rclpy</depend>
16+
<depend>std_msgs</depend>
1617
<depend>turtlebot4_navigation</depend>
1718

1819
<exec_depend>python-openai</exec_depend>

turtlebot4_openai_tutorials/turtlebot4_openai_tutorials/natural_language_nav.py

Lines changed: 41 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -20,24 +20,30 @@
2020

2121
import rclpy, ament_index_python
2222
from rclpy.node import Node
23+
from std_msgs.msg import String, Bool
2324
from turtlebot4_navigation.turtlebot4_navigator import TurtleBot4Directions, TurtleBot4Navigator
2425

2526
import openai
2627

27-
PARKING_BRAKE = False # Set to false if you want the results to be executed
28-
2928
class GPTNode(Node):
30-
def __init__(self):
29+
def __init__(self, navigator):
3130
super().__init__('gpt_node')
3231
self.declare_parameter('openai_api_key','')
3332
self.declare_parameter('model_name','gpt-3.5-turbo')
33+
self.declare_parameter('parking_brake',True)
3434

35+
# OpenAI key, model, prompt setup
3536
openai.api_key = self.get_parameter('openai_api_key').value
3637
self.model_name = self.get_parameter('model_name').value
37-
3838
self.prompts = []
3939
self.full_prompt = ""
4040

41+
# ROS stuff. Navigator node handle, topics
42+
self.navigator = navigator
43+
self.sub_input = self.create_subscription(String, 'user_input', self.user_input, 10)
44+
self.pub_ready = self.create_publisher(Bool, 'ready_for_input', 10)
45+
self.publish_status(False)
46+
4147
def query(self, base_prompt, query, stop_tokens=None, query_kwargs=None, log=True):
4248
new_prompt = f'{base_prompt}\n{query}'
4349
""" Query OpenAI API with a prompt and query """
@@ -63,32 +69,37 @@ def query(self, base_prompt, query, stop_tokens=None, query_kwargs=None, log=Tru
6369

6470
return response
6571

66-
def user_input(self, navigator, parking_brake=True):
67-
""" Process user input and optionally execute resulting code
68-
navigator: Instance of TurtleBot4Navigator
69-
parking_brake: Set to false to execute
70-
"""
72+
def publish_status(self, status):
73+
""" Publish whether or not the system is ready for more input """
74+
msg = Bool()
75+
msg.data = status
76+
self.ready_for_input = status
77+
self.pub_ready.publish(msg)
78+
79+
def user_input(self, msg):
80+
""" Process user input and optionally execute resulting code """
7181
# User input
72-
query = input("Please enter request, or type 'exit' to exit> ")
73-
self.info("Received query: " + query)
74-
if query == 'exit':
75-
return False
76-
# Add "# " to the start to match code samples
77-
query = '# ' + query
78-
79-
# Issue query
82+
if not self.ready_for_input:
83+
# This doesn't seem to be an issue when there's only one publisher, but it's good practice
84+
self.info(f"Received input <{msg.data}> when not ready, skipping")
85+
return
86+
self.publish_status(False)
87+
self.info(f"Received input <{msg.data}>")
88+
89+
# Add "# " to the start to match code samples, issue query
90+
query = '# ' + msg.data
8091
result = self.query(f'{self.full_prompt}', query, ['#', 'objects = ['])
8192

8293
# Execute?
83-
if not parking_brake:
94+
navigator = self.navigator # This is because the example API calls 'navigator', not 'self.navigator'
95+
if not self.get_parameter('parking_brake').value:
8496
try:
8597
exec(result, globals(), locals())
8698
except Exception as e:
8799
self.error("Failure to execute resulting code:")
88100
self.error("---------------\n"+result)
89101
self.error("---------------")
90-
91-
return True
102+
self.publish_status(True)
92103

93104
def info(self, msg):
94105
self.get_logger().info(msg)
@@ -119,14 +130,14 @@ def main():
119130
rclpy.init()
120131

121132
navigator = TurtleBot4Navigator()
122-
gpt = GPTNode()
133+
gpt = GPTNode(navigator)
123134

124135
gpt.prompts.append(read_prompt_file('turtlebot4_api.txt'))
125136
for p in gpt.prompts:
126137
gpt.full_prompt = gpt.full_prompt + '\n' + p
127138

128139
# No need to talk to robot if we're not executing
129-
if not PARKING_BRAKE:
140+
if not gpt.get_parameter('parking_brake').value:
130141
gpt.warn("Parking brake not set, robot will execute commands!")
131142
# Start on dock
132143
if not navigator.getDockedStatus():
@@ -146,19 +157,22 @@ def main():
146157
gpt.warn("Parking brake set, robot will not execute commands!")
147158

148159
# Add custom context
149-
context = "destinations = {'iron crate': [0.0, 3.0, 0], 'steel barrels': [2.0, 2.0, 90], 'bathroom door': [-6.0, -6.0, 180] }"
160+
context = "destinations = {'wood crate': [0.0, 3.0, 0], 'steel barrels': [2.0, 2.0, 90], 'bathroom door': [-6.0, -6.0, 180] }"
150161
exec(context, globals())
151162
gpt.info("Entering input parsing loop with context:")
152163
gpt.info(context)
153164
gpt.full_prompt = gpt.full_prompt + '\n' + context
154165

155166
# Main loop
156-
while rclpy.ok():
157-
result = gpt.user_input(navigator, parking_brake=PARKING_BRAKE)
158-
if not result:
159-
break
167+
gpt.publish_status(True)
168+
try:
169+
rclpy.spin(gpt)
170+
except KeyboardInterrupt:
171+
pass
160172

161173
gpt.destroy_node()
174+
navigator.destroy_node()
175+
162176
rclpy.shutdown()
163177

164178
if __name__ == '__main__':

0 commit comments

Comments
 (0)