2020
2121import rclpy , ament_index_python
2222from rclpy .node import Node
23+ from std_msgs .msg import String , Bool
2324from turtlebot4_navigation .turtlebot4_navigator import TurtleBot4Directions , TurtleBot4Navigator
2425
2526import openai
2627
27- PARKING_BRAKE = False # Set to false if you want the results to be executed
28-
2928class 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
164178if __name__ == '__main__' :
0 commit comments