@@ -80,26 +80,26 @@ def list_verified_robot_specifications() -> dict:
8080 )
8181)
8282def connect_to_robot (
83- ip : Optional [ str ] = None ,
84- port : Optional [ Union [int , str ]] = None ,
83+ ip : str = ROSBRIDGE_IP ,
84+ port : Union [int , str ] = ROSBRIDGE_PORT ,
8585 ping_timeout : float = 2.0 ,
8686 port_timeout : float = 2.0 ,
8787) -> dict :
8888 """
8989 Connect to a robot by setting the IP and port for the WebSocket connection, then testing connectivity.
9090
9191 Args:
92- ip (Optional[ str] ): The IP address of the rosbridge server. Defaults to "127.0.0.1" (localhost).
93- port (Optional[ int] ): The port number of the rosbridge server. Defaults to 9090.
92+ ip (str): The IP address of the rosbridge server. Defaults to "127.0.0.1" (localhost).
93+ port (int): The port number of the rosbridge server. Defaults to 9090.
9494 ping_timeout (float): Timeout for ping in seconds. Default = 2.0.
9595 port_timeout (float): Timeout for port check in seconds. Default = 2.0.
9696
9797 Returns:
9898 dict: Connection status with ping and port check results.
9999 """
100100 # Set default values if None
101- actual_ip = ip if ip is not None else "127.0.0.1"
102- actual_port = int (port ) if port is not None else 9090
101+ actual_ip = str ( ip ). strip () if ip else ROSBRIDGE_IP
102+ actual_port = int (port ) if port else ROSBRIDGE_PORT
103103
104104 # Set the IP and port
105105 ws_manager .set_ip (actual_ip , actual_port )
@@ -2112,12 +2112,7 @@ def get_action_details(action_type: str) -> dict:
21122112 if not action_type or not action_type .strip ():
21132113 return {"error" : "Action type cannot be empty" }
21142114
2115- result = {
2116- "action_type" : action_type ,
2117- "goal" : {},
2118- "result" : {},
2119- "feedback" : {}
2120- }
2115+ result = {"action_type" : action_type , "goal" : {}, "result" : {}, "feedback" : {}}
21212116
21222117 # Get goal, result, and feedback details in a single WebSocket context
21232118 with ws_manager :
@@ -2131,7 +2126,12 @@ def get_action_details(action_type: str) -> dict:
21312126 }
21322127
21332128 goal_response = ws_manager .request (goal_message )
2134- if goal_response and isinstance (goal_response , dict ) and "values" in goal_response and "error" not in goal_response :
2129+ if (
2130+ goal_response
2131+ and isinstance (goal_response , dict )
2132+ and "values" in goal_response
2133+ and "error" not in goal_response
2134+ ):
21352135 typedefs = goal_response ["values" ].get ("typedefs" , [])
21362136 if typedefs :
21372137 for typedef in typedefs :
@@ -2141,24 +2141,24 @@ def get_action_details(action_type: str) -> dict:
21412141 examples = typedef .get ("examples" , [])
21422142 const_names = typedef .get ("constnames" , [])
21432143 const_values = typedef .get ("constvalues" , [])
2144-
2144+
21452145 fields = {}
21462146 field_details = {}
21472147 for i , (name , ftype ) in enumerate (zip (field_names , field_types )):
21482148 fields [name ] = ftype
21492149 field_details [name ] = {
21502150 "type" : ftype ,
21512151 "array_length" : field_array_len [i ] if i < len (field_array_len ) else - 1 ,
2152- "example" : examples [i ] if i < len (examples ) else None
2152+ "example" : examples [i ] if i < len (examples ) else None ,
21532153 }
2154-
2154+
21552155 result ["goal" ] = {
2156- "fields" : fields ,
2156+ "fields" : fields ,
21572157 "field_count" : len (fields ),
21582158 "field_details" : field_details ,
21592159 "message_type" : typedef .get ("type" , "" ),
21602160 "examples" : examples ,
2161- "constants" : dict (zip (const_names , const_values )) if const_names else {}
2161+ "constants" : dict (zip (const_names , const_values )) if const_names else {},
21622162 }
21632163
21642164 # Get result details using action-specific service
@@ -2171,7 +2171,12 @@ def get_action_details(action_type: str) -> dict:
21712171 }
21722172
21732173 result_response = ws_manager .request (result_message )
2174- if result_response and isinstance (result_response , dict ) and "values" in result_response and "error" not in result_response :
2174+ if (
2175+ result_response
2176+ and isinstance (result_response , dict )
2177+ and "values" in result_response
2178+ and "error" not in result_response
2179+ ):
21752180 typedefs = result_response ["values" ].get ("typedefs" , [])
21762181 if typedefs :
21772182 for typedef in typedefs :
@@ -2181,24 +2186,24 @@ def get_action_details(action_type: str) -> dict:
21812186 examples = typedef .get ("examples" , [])
21822187 const_names = typedef .get ("constnames" , [])
21832188 const_values = typedef .get ("constvalues" , [])
2184-
2189+
21852190 fields = {}
21862191 field_details = {}
21872192 for i , (name , ftype ) in enumerate (zip (field_names , field_types )):
21882193 fields [name ] = ftype
21892194 field_details [name ] = {
21902195 "type" : ftype ,
21912196 "array_length" : field_array_len [i ] if i < len (field_array_len ) else - 1 ,
2192- "example" : examples [i ] if i < len (examples ) else None
2197+ "example" : examples [i ] if i < len (examples ) else None ,
21932198 }
2194-
2199+
21952200 result ["result" ] = {
2196- "fields" : fields ,
2201+ "fields" : fields ,
21972202 "field_count" : len (fields ),
21982203 "field_details" : field_details ,
21992204 "message_type" : typedef .get ("type" , "" ),
22002205 "examples" : examples ,
2201- "constants" : dict (zip (const_names , const_values )) if const_names else {}
2206+ "constants" : dict (zip (const_names , const_values )) if const_names else {},
22022207 }
22032208
22042209 # Get feedback details using action-specific service
@@ -2211,7 +2216,12 @@ def get_action_details(action_type: str) -> dict:
22112216 }
22122217
22132218 feedback_response = ws_manager .request (feedback_message )
2214- if feedback_response and isinstance (feedback_response , dict ) and "values" in feedback_response and "error" not in feedback_response :
2219+ if (
2220+ feedback_response
2221+ and isinstance (feedback_response , dict )
2222+ and "values" in feedback_response
2223+ and "error" not in feedback_response
2224+ ):
22152225 typedefs = feedback_response ["values" ].get ("typedefs" , [])
22162226 if typedefs :
22172227 for typedef in typedefs :
@@ -2221,24 +2231,24 @@ def get_action_details(action_type: str) -> dict:
22212231 examples = typedef .get ("examples" , [])
22222232 const_names = typedef .get ("constnames" , [])
22232233 const_values = typedef .get ("constvalues" , [])
2224-
2234+
22252235 fields = {}
22262236 field_details = {}
22272237 for i , (name , ftype ) in enumerate (zip (field_names , field_types )):
22282238 fields [name ] = ftype
22292239 field_details [name ] = {
22302240 "type" : ftype ,
22312241 "array_length" : field_array_len [i ] if i < len (field_array_len ) else - 1 ,
2232- "example" : examples [i ] if i < len (examples ) else None
2242+ "example" : examples [i ] if i < len (examples ) else None ,
22332243 }
2234-
2244+
22352245 result ["feedback" ] = {
2236- "fields" : fields ,
2246+ "fields" : fields ,
22372247 "field_count" : len (fields ),
22382248 "field_details" : field_details ,
22392249 "message_type" : typedef .get ("type" , "" ),
22402250 "examples" : examples ,
2241- "constants" : dict (zip (const_names , const_values )) if const_names else {}
2251+ "constants" : dict (zip (const_names , const_values )) if const_names else {},
22422252 }
22432253
22442254 # Check if we got any data
@@ -2250,33 +2260,33 @@ def get_action_details(action_type: str) -> dict:
22502260
22512261@mcp .tool (
22522262 description = (
2253- "Get action status for a specific action type . Works only with ROS 2.\n "
2263+ "Get action status for a specific action name . Works only with ROS 2.\n "
22542264 "Example:\n "
2255- "get_action_status('action_tutorials_interfaces/action/Fibonacci ')"
2265+ "get_action_status('/fibonacci ')"
22562266 )
22572267)
2258- def get_action_status (action_type : str ) -> dict :
2268+ def get_action_status (action_name : str ) -> dict :
22592269 """
2260- Get action status for a specific action type . Works only with ROS 2.
2270+ Get action status for a specific action name . Works only with ROS 2.
22612271
22622272 Args:
2263- action_type (str): The action type (e.g., 'action_tutorials_interfaces/action/Fibonacci ')
2273+ action_name (str): The action name (e.g., '/fibonacci ')
22642274
22652275 Returns:
22662276 dict: Contains action status information including active goals and their status.
22672277 """
22682278 # Validate input
2269- if not action_type or not action_type .strip ():
2270- return {"error" : "Action type cannot be empty" }
2279+ if not action_name or not action_name .strip ():
2280+ return {"error" : "Action name cannot be empty" }
2281+
2282+ # Ensure action name starts with /
2283+ if not action_name .startswith ("/" ):
2284+ action_name = f"/{ action_name } "
22712285
2272- # Extract action name from action type
2273- # For example: 'action_tutorials_interfaces/action/Fibonacci' -> '/fibonacci'
2274- action_name = f"/{ action_type .split ('/' )[- 1 ].lower ()} "
2275-
22762286 # Try to get action status by subscribing to the status topic
22772287 status_topic = f"{ action_name } /_action/status"
22782288 status_msg_type = "action_msgs/msg/GoalStatusArray"
2279-
2289+
22802290 try :
22812291 # Subscribe to action status topic
22822292 with ws_manager :
@@ -2286,75 +2296,79 @@ def get_action_status(action_type: str) -> dict:
22862296 "type" : status_msg_type ,
22872297 "id" : f"get_action_status_{ action_name .replace ('/' , '_' )} " ,
22882298 }
2289-
2299+
22902300 send_error = ws_manager .send (message )
22912301 if send_error :
22922302 return {
2293- "action_type" : action_type ,
22942303 "action_name" : action_name ,
22952304 "success" : False ,
22962305 "error" : f"Failed to subscribe to status topic: { send_error } " ,
22972306 }
2298-
2307+
22992308 # Wait for status message
23002309 response = ws_manager .receive (timeout = 3.0 )
23012310 if not response :
23022311 return {
2303- "action_type" : action_type ,
23042312 "action_name" : action_name ,
23052313 "success" : False ,
23062314 "error" : "No response from action status topic" ,
23072315 }
2308-
2316+
23092317 response_data = json .loads (response )
2310-
2318+
23112319 if response_data .get ("op" ) == "status" and response_data .get ("level" ) == "error" :
2312- return {"error" : f"Action status error: { response_data .get ('msg' , 'Unknown error' )} " }
2313-
2320+ return {
2321+ "error" : f"Action status error: { response_data .get ('msg' , 'Unknown error' )} "
2322+ }
2323+
23142324 if "msg" not in response_data or "status_list" not in response_data ["msg" ]:
23152325 return {
2316- "action_type" : action_type ,
23172326 "action_name" : action_name ,
23182327 "success" : True ,
23192328 "active_goals" : [],
23202329 "goal_count" : 0 ,
2321- "note" : f"No active goals found for action { action_name } "
2330+ "note" : f"No active goals found for action { action_name } " ,
23222331 }
2323-
2332+
23242333 status_list = response_data ["msg" ]["status_list" ]
23252334 status_map = {
2326- 0 : "STATUS_UNKNOWN" , 1 : "STATUS_ACCEPTED" , 2 : "STATUS_EXECUTING" ,
2327- 3 : "STATUS_CANCELING" , 4 : "STATUS_SUCCEEDED" , 5 : "STATUS_CANCELED" , 6 : "STATUS_ABORTED"
2335+ 0 : "STATUS_UNKNOWN" ,
2336+ 1 : "STATUS_ACCEPTED" ,
2337+ 2 : "STATUS_EXECUTING" ,
2338+ 3 : "STATUS_CANCELING" ,
2339+ 4 : "STATUS_SUCCEEDED" ,
2340+ 5 : "STATUS_CANCELED" ,
2341+ 6 : "STATUS_ABORTED" ,
23282342 }
2329-
2343+
23302344 active_goals = []
23312345 for status_item in status_list :
23322346 goal_info = status_item .get ("goal_info" , {})
23332347 goal_id = goal_info .get ("goal_id" , {}).get ("uuid" , "unknown" )
23342348 status = status_item .get ("status" , - 1 )
23352349 stamp = goal_info .get ("stamp" , {})
2336-
2337- active_goals .append ({
2338- "goal_id" : goal_id ,
2339- "status" : status ,
2340- "status_text" : status_map .get (status , "UNKNOWN" ),
2341- "timestamp" : f"{ stamp .get ('sec' , 0 )} .{ stamp .get ('nanosec' , 0 )} "
2342- })
2343-
2350+
2351+ active_goals .append (
2352+ {
2353+ "goal_id" : goal_id ,
2354+ "status" : status ,
2355+ "status_text" : status_map .get (status , "UNKNOWN" ),
2356+ "timestamp" : f"{ stamp .get ('sec' , 0 )} .{ stamp .get ('nanosec' , 0 )} " ,
2357+ }
2358+ )
2359+
23442360 return {
2345- "action_type" : action_type ,
23462361 "action_name" : action_name ,
23472362 "success" : True ,
23482363 "active_goals" : active_goals ,
23492364 "goal_count" : len (active_goals ),
2350- "note" : f"Found { len (active_goals )} active goal(s) for action { action_name } "
2365+ "note" : f"Found { len (active_goals )} active goal(s) for action { action_name } " ,
23512366 }
2352-
2367+
23532368 except json .JSONDecodeError as e :
23542369 return {"error" : f"Failed to parse status response: { str (e )} " }
23552370 except Exception as e :
23562371 return {
2357- "action_type" : action_type ,
23582372 "action_name" : action_name ,
23592373 "success" : False ,
23602374 "error" : f"Failed to get action status: { str (e )} " ,
0 commit comments