Run an Existing Objective
This guide shows how to launch an Objective that already exists in your robot configuration. For both transports the entry point is the /do_objective action (moveit_studio_sdk_msgs/action/DoObjectiveSequence), with objective_name set to the Objective shown in the MoveIt Pro UI. A synchronous service alternative — /execute_objective — is described at the end of this page.
If you have not configured a transport yet, see the Programmatic SDKs overview.
With great power comes great responsibility. If you launch DoObjectiveSequence and lose the goal handle (RCL) or goal ID (websocket), you will no longer be able to cancel the running Objective from that client. If the Objective has no end condition, you will need to use the web UI to cancel it or restart MoveIt Pro.
Only one Objective runs at a time. Sending a new goal cancels any currently running Objective.
Launch an Objective
- Python (rclpy)
- Python (roslibpy)
- Command Line
"""Run an existing Objective via the DoObjectiveSequence action."""
from typing import Any, List, Optional
import rclpy
from rclpy.action import ActionClient
from rclpy.action.client import ClientGoalHandle
from rclpy.node import Node
from rclpy.task import Future
from moveit_studio_sdk_msgs.action import DoObjectiveSequence
def main(args: Optional[List[str]] = None) -> None:
rclpy.init(args=args)
node: Node = Node("objective_client")
action_client: ActionClient = ActionClient(node, DoObjectiveSequence, "/do_objective")
goal_handle: Optional[ClientGoalHandle] = None
try:
if not action_client.wait_for_server(timeout_sec=5.0):
node.get_logger().error("Action server not available after 5s")
return
goal_msg = DoObjectiveSequence.Goal(objective_name="Close Gripper")
send_goal_future: Future = action_client.send_goal_async(goal_msg)
rclpy.spin_until_future_complete(node, send_goal_future)
goal_handle = send_goal_future.result()
if not goal_handle or not goal_handle.accepted:
node.get_logger().error("Objective rejected by server")
return
result_future: Future = goal_handle.get_result_async()
rclpy.spin_until_future_complete(node, result_future)
result_response: Optional[Any] = result_future.result()
if result_response is None:
node.get_logger().error("Failed to get result from action server")
return
result: DoObjectiveSequence.Result = result_response.result
if result.error_code.val == 1: # MoveItErrorCodes.SUCCESS
node.get_logger().info("Objective completed successfully")
else:
node.get_logger().error(
f"Objective failed: code={result.error_code.val} message={result.error_message}"
)
except KeyboardInterrupt:
if goal_handle:
goal_handle.cancel_goal_async()
finally:
node.destroy_node()
rclpy.shutdown()
if __name__ == "__main__":
main()
DoObjectiveSequence.Result.error_code is a moveit_msgs/MoveItErrorCodes. The full list of values is documented in MoveIt's MoveItErrorCodes.msg.
The websocket client speaks to the same /do_objective action through rosbridge. The goal is a plain dict whose field names match the underlying ROS message; replace localhost with the robot's IP when running off-robot.
import time
import roslibpy
from roslibpy import ActionClient
client = roslibpy.Ros(host="localhost", port=3201)
client.run() # Connect before creating the ActionClient.
action = ActionClient(
client,
"/do_objective",
"moveit_studio_sdk_msgs/action/DoObjectiveSequence",
)
action_done = False
def on_result(result):
global action_done
values = result.get("values", {})
error_code = values.get("error_code", {})
print(f"Error code: {error_code.get('val', 'unknown')}")
print(f"Error message: {values.get('error_message', 'none')}")
action_done = True
def on_feedback(feedback):
print(f"Feedback: {feedback}")
def on_error(error):
global action_done
print(f"Action failed: {error}")
action_done = True
goal = {"objective_name": "Close Gripper"}
goal_id = action.send_goal(goal, on_result, on_feedback, on_error)
print(f"Goal ID: {goal_id}")
start = time.time()
while not action_done and (time.time() - start) < 30:
time.sleep(0.1)
client.terminate()
Open a shell inside the MoveIt Pro container (moveit_pro shell) and run:
ros2 action send_goal /do_objective moveit_studio_sdk_msgs/action/DoObjectiveSequence \
"{objective_name: 'Close Gripper'}"
Synchronous vs. Asynchronous Execution
The action interface is inherently asynchronous. The RCL example above blocks the calling thread with spin_until_future_complete; the roslibpy example polls a flag. To overlap the Objective with other work, simply return from the launching code path and react in the result callback (websocket) or the result future (RCL).
- Python (rclpy)
- Python (roslibpy)
def handle_result(future):
result = future.result().result
# ...
def handle_goal_response(future):
goal_handle = future.result()
if not goal_handle.accepted:
return
goal_handle.get_result_async().add_done_callback(handle_result)
send_goal_future = action_client.send_goal_async(goal_msg)
send_goal_future.add_done_callback(handle_goal_response)
goal_id = action.send_goal(goal, on_result, on_feedback, on_error)
for i in range(5):
if action_done:
break
print(f"Doing other work... {i + 1}/5")
time.sleep(1)
Cancel a Running Objective
- Python (rclpy)
- Python (roslibpy)
# Retain the goal handle from the send_goal_async future, then:
cancel_future = goal_handle.cancel_goal_async()
rclpy.spin_until_future_complete(node, cancel_future)
goal_id = action.send_goal(goal, on_result, on_feedback, on_error)
# Later:
action.cancel_goal(goal_id)
Cancellation through roslibpy currently triggers the error callback.
Using the ExecuteObjective Service
/execute_objective (moveit_studio_sdk_msgs/srv/ExecuteObjective) is a synchronous wrapper around DoObjectiveSequence: the service call returns once the Objective finishes. It accepts the same fields as the action goal, including parameter_overrides. Use it when the calling code does not need feedback or asynchronous control and would otherwise spin its own state machine.
- Python (rclpy)
- Python (roslibpy)
import rclpy
from rclpy.node import Node
from moveit_studio_sdk_msgs.srv import ExecuteObjective
rclpy.init()
node = Node("execute_objective_client")
client = node.create_client(ExecuteObjective, "/execute_objective")
client.wait_for_service(timeout_sec=5.0)
request = ExecuteObjective.Request()
request.objective_name = "Look at Table"
future = client.call_async(request)
rclpy.spin_until_future_complete(node, future)
response = future.result()
print(f"Error code: {response.error_code.val}")
node.destroy_node()
rclpy.shutdown()
import roslibpy
client = roslibpy.Ros(host="localhost", port=3201)
client.run()
service = roslibpy.Service(
client,
"/execute_objective",
"moveit_studio_sdk_msgs/srv/ExecuteObjective",
)
request = roslibpy.ServiceRequest({"objective_name": "Look at Table"})
result = service.call(request)
print(f"Service result: {result}")
client.terminate()
Next Steps
- To execute a Behavior Tree that isn't yet saved as an Objective, or to remap Subtree ports per call, see Run an Objective from a custom Behavior Tree XML.
- To inject values into the blackboard at runtime, see Passing Parameters to Objectives at Runtime.