API source snapshot · 2026-09-24
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | #!/usr/bin/env python3 """Send task JSON to the Beambot orchestrator. Usage: ros2 run beambot beambot_client.py /path/to/task_sequence.json """ import argparse import json import sys from pathlib import Path import rclpy from rclpy.node import Node from rclpy.action import ActionClient from action_msgs.msg import GoalStatus from beambot_interfaces.action import MTCExecution class TaskGoalSender(Node): def __init__(self): super().__init__('task_goal_sender') self._client = ActionClient(self, MTCExecution, 'beambot_execution') self._goal_handle = None def send_goal(self, json_content: str) -> bool: """Send the task goal and wait for completion.""" self.get_logger().info('Waiting for beambot_execution action server...') if not self._client.wait_for_server(timeout_sec=10.0): self.get_logger().error('Action server not available!') return False goal = MTCExecution.Goal() goal.full_json = json_content self.get_logger().info('Sending task goal...') send_future = self._client.send_goal_async( goal, feedback_callback=self._feedback_callback ) rclpy.spin_until_future_complete(self, send_future) self._goal_handle = send_future.result() if not self._goal_handle.accepted: self.get_logger().error('Goal was rejected!') return False self.get_logger().info('Goal accepted, executing...') result_future = self._goal_handle.get_result_async() rclpy.spin_until_future_complete(self, result_future) result = result_future.result() status = result.status if status == GoalStatus.STATUS_SUCCEEDED: self.get_logger().info( f'✅ Task completed successfully! ' f'Steps: {result.result.completed_steps}/{result.result.total_steps}' ) return True elif status == GoalStatus.STATUS_CANCELED: self.get_logger().warning(f'Task was canceled: {result.result.error_message}') return False else: self.get_logger().error( f'❌ Task failed: {result.result.error_message} ' f'(completed {result.result.completed_steps} steps)' ) return False def _feedback_callback(self, feedback_msg): """Handle progress feedback.""" fb = feedback_msg.feedback self.get_logger().info( f'[{fb.progress_percentage:.0f}%] Step {fb.current_step}: ' f'{fb.current_action} | Gripper: {fb.current_gripper} | {fb.status_message}' ) def main(): parser = argparse.ArgumentParser(description='Send task sequence to MTC Orchestrator') parser.add_argument('json_file', help='Path to task sequence JSON file') args = parser.parse_args() json_path = Path(args.json_file) if not json_path.exists(): print(f'Error: File not found: {json_path}', file=sys.stderr) sys.exit(1) try: json_content = json_path.read_text() json.loads(json_content) except json.JSONDecodeError as e: print(f'Error: Invalid JSON in {json_path}: {e}', file=sys.stderr) sys.exit(1) except IOError as e: print(f'Error reading file: {e}', file=sys.stderr) sys.exit(1) print(f'Loaded task sequence from: {json_path}') rclpy.init() node = TaskGoalSender() exit_code = 1 try: success = node.send_goal(json_content) exit_code = 0 if success else 1 except KeyboardInterrupt: print('\nCanceled by user') if node._goal_handle: try: cancel_future = node._goal_handle.cancel_goal_async() # Process cancellation for up to two seconds. rclpy.spin_until_future_complete(node, cancel_future, timeout_sec=2.0) except KeyboardInterrupt: pass # Exit on a second interrupt. exit_code = 130 finally: node.destroy_node() try: rclpy.shutdown() except Exception: pass # The context may already be shut down. sys.exit(exit_code) if __name__ == '__main__': main() |