Skip to content

Commit ea6e9a2

Browse files
committed
Modified get_action_status to work with action_name and not action_type
1 parent 03b1dde commit ea6e9a2

File tree

3 files changed

+99
-69
lines changed

3 files changed

+99
-69
lines changed

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,4 +178,7 @@ pyrightconfig.json
178178
.vs/
179179

180180
# camera
181-
/camera/*
181+
camera/*
182+
183+
# tests
184+
tests/

server.py

Lines changed: 82 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -80,26 +80,26 @@ def list_verified_robot_specifications() -> dict:
8080
)
8181
)
8282
def 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)}",
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Robot configuration template
2+
# Replace this text with specific instructions and information about your robot.
3+
# Include details on how to control it, important topics, and any special commands.
4+
# For example:
5+
# - Control commands (e.g., movement, actions)
6+
# - Sensor topics (e.g., camera, lidar)
7+
# - Safety guidelines
8+
# - Any custom messages or services used by the robot
9+
10+
name: local_rosbridge
11+
alias: turtlesim # Optional alias for easier reference
12+
type: sim # or 'real' for simulation
13+
prompts: The local_rosbridge is a generic local turtlesim robot.

0 commit comments

Comments
 (0)