Spaces:
Sleeping
Sleeping
| sidebar_position: 4 | |
| # Chapter 3: Nav2 Path Planning for Bipedal Robots | |
| ## Learning Objectives | |
| - Understand the unique challenges of path planning for bipedal humanoid robots | |
| - Learn Nav2 configuration for humanoid-specific navigation | |
| - Implement dynamic path planning considering bipedal gait constraints | |
| - Adapt global and local planners for bipedal locomotion | |
| - Integrate footstep planning with Nav2 navigation | |
| ## Introduction to Bipedal Navigation Challenges | |
| Navigation for humanoid robots presents unique challenges compared to wheeled robots. Bipedal locomotion requires: | |
| 1. **Dynamic Balance**: Maintaining balance while moving on two legs | |
| 2. **Footstep Planning**: Discrete footsteps rather than continuous paths | |
| 3. **ZMP (Zero Moment Point) Considerations**: Ensuring dynamic stability | |
| 4. **Terrain Adaptation**: Handling uneven surfaces, stairs, obstacles | |
| 5. **Collision Avoidance**: Avoiding obstacles while maintaining balance | |
| ### Differences from Wheeled Robot Navigation | |
| Traditional navigation systems assume continuous, smooth motion. Bipedal robots require: | |
| - **Discrete Path Representation**: Paths as sequences of footsteps | |
| - **Stability Constraints**: Maintaining balance at each step | |
| - **Dynamic Obstacle Avoidance**: Adjusting gait in real-time | |
| - **Terrain Classification**: Different walking patterns for different surfaces | |
| ## Nav2 Architecture Overview | |
| Nav2 consists of several key components that need to be adapted for bipedal navigation: | |
| ``` | |
| [Global Planner] → [Controller Server] → [Local Planner] → [Robot Controller] | |
| ↑ ↑ ↑ | |
| [Costmap] ← → [Costmap] ← → [Costmap] | |
| ``` | |
| For bipedal robots, these components require specific adaptations: | |
| ### Global Planner Adaptations | |
| The global planner needs to account for: | |
| - **Step-to-step connectivity**: Rather than continuous paths | |
| - **Stability zones**: Areas where robot can safely place feet | |
| - **Gait constraints**: Specific patterns for different walking speeds | |
| - **Terrain classification**: Adapting for different surface properties | |
| ```python | |
| import rclpy | |
| from rclpy.node import Node | |
| from nav2_msgs.action import ComputePathToPose | |
| from nav_msgs.msg import Path | |
| from geometry_msgs.msg import PoseStamped | |
| from std_msgs.msg import Header | |
| import numpy as np | |
| class BipedalGlobalPlanner(Node): | |
| def __init__(self): | |
| super().__init__('bipedal_global_planner') | |
| # Action server for path computation | |
| self.action_server = self.create_action_server( | |
| 'compute_path_to_pose', | |
| ComputePathToPose, | |
| self.execute_path_request | |
| ) | |
| # Costmap subscription | |
| self.costmap_sub = self.create_subscription( | |
| OccupancyGrid, # Simplified type representation | |
| '/global_costmap/costmap', | |
| self.costmap_callback, | |
| 10 | |
| ) | |
| self.get_logger().info('Bipedal Global Planner initialized') | |
| def execute_path_request(self, goal_handle): | |
| """Execute path planning request with bipedal constraints""" | |
| start = goal_handle.request.start | |
| goal = goal_handle.request.goal | |
| # Plan path considering bipedal constraints | |
| path = self.plan_path_with_constraints(start, goal) | |
| if path is not None: | |
| # Return the planned path | |
| result = ComputePathToPose.Result() | |
| result.path = path | |
| goal_handle.succeed() | |
| return result | |
| else: | |
| # Planning failed | |
| goal_handle.abort() | |
| return ComputePathToPose.Result() | |
| def plan_path_with_constraints(self, start, goal): | |
| """Plan path considering bipedal locomotion constraints""" | |
| # This is a simplified implementation | |
| # In reality, this would interface with footstep planners | |
| path = Path() | |
| path.header = Header() | |
| path.header.frame_id = 'map' | |
| path.header.stamp = self.get_clock().now().to_msg() | |
| # For bipedal robots, we need to plan footstep locations | |
| # This is a simplified example creating a straight path | |
| steps = self.generate_footsteps(start.pose, goal.pose) | |
| for step in steps: | |
| pose_stamped = PoseStamped() | |
| pose_stamped.header = path.header | |
| pose_stamped.pose = step | |
| path.poses.append(pose_stamped) | |
| return path | |
| def generate_footsteps(self, start_pose, goal_pose): | |
| """Generate discrete footsteps for bipedal navigation""" | |
| # Calculate straight line path | |
| dx = goal_pose.position.x - start_pose.position.x | |
| dy = goal_pose.position.y - start_pose.position.y | |
| distance = np.sqrt(dx*dx + dy*dy) | |
| # Determine step size based on robot parameters | |
| step_length = 0.3 # meters - typical for humanoid | |
| step_width = 0.2 # meters - step width for balance | |
| # Generate footsteps | |
| footsteps = [] | |
| num_steps = max(1, int(distance / step_length)) | |
| for i in range(num_steps + 1): | |
| ratio = i / num_steps if num_steps > 0 else 0 | |
| step_pose = Pose() | |
| step_pose.position.x = start_pose.position.x + dx * ratio | |
| step_pose.position.y = start_pose.position.y + dy * ratio | |
| step_pose.position.z = start_pose.position.z # Maintain height | |
| # Add small offset for alternate feet | |
| if i % 2 == 0: # Left foot | |
| step_pose.position.y += step_width / 2 | |
| else: # Right foot | |
| step_pose.position.y -= step_width / 2 | |
| # Orientation - face toward goal | |
| yaw = np.arctan2(dy, dx) | |
| step_pose.orientation = self.yaw_to_quaternion(yaw) | |
| footsteps.append(step_pose) | |
| return footsteps | |
| def yaw_to_quaternion(self, yaw): | |
| """Convert yaw angle to quaternion""" | |
| from tf_transformations import quaternion_from_euler | |
| q = quaternion_from_euler(0, 0, yaw) | |
| from geometry_msgs.msg import Quaternion | |
| quat_msg = Quaternion() | |
| quat_msg.x = q[0] | |
| quat_msg.y = q[1] | |
| quat_msg.z = q[2] | |
| quat_msg.w = q[3] | |
| return quat_msg | |
| ``` | |
| ## Local Planner for Bipedal Robots | |
| The local planner for bipedal robots must: | |
| - **Generate footstep trajectories** rather than continuous velocity commands | |
| - **Maintain dynamic balance** during obstacle avoidance | |
| - **Adapt gait patterns** in real-time | |
| - **Handle reactive stepping** for sudden obstacles | |
| ```python | |
| from geometry_msgs.msg import Twist, Pose, Point | |
| from nav_msgs.msg import Path | |
| import math | |
| class BipedalLocalPlanner(Node): | |
| def __init__(self): | |
| super().__init__('bipedal_local_planner') | |
| # Subscribers | |
| self.path_sub = self.create_subscription( | |
| Path, | |
| '/global_plan', | |
| self.global_path_callback, | |
| 10 | |
| ) | |
| self.odom_sub = self.create_subscription( | |
| Odometry, # Simplified type | |
| '/odom', | |
| self.odom_callback, | |
| 10 | |
| ) | |
| self.scan_sub = self.create_subscription( | |
| LaserScan, # Simplified type | |
| '/scan', | |
| self.scan_callback, | |
| 10 | |
| ) | |
| # Publishers for footstep commands | |
| self.footstep_pub = self.create_publisher( | |
| String, # In practice, this would be a custom footstep message | |
| '/footstep_commands', | |
| 10 | |
| ) | |
| # Internal state | |
| self.current_path = None | |
| self.current_pose = None | |
| self.current_velocity = Twist() | |
| self.path_index = 0 | |
| # Bipedal-specific parameters | |
| self.step_duration = 0.8 # seconds per step | |
| self.max_step_adjustment = 0.1 # meters | |
| self.balance_margin = 0.3 # safety margin for balance | |
| self.get_logger().info('Bipedal Local Planner initialized') | |
| def global_path_callback(self, path_msg): | |
| """Handle new global path""" | |
| self.current_path = path_msg | |
| self.path_index = 0 | |
| self.get_logger().info(f'Received new path with {len(path_msg.poses)} steps') | |
| def odom_callback(self, odom_msg): | |
| """Update current robot pose""" | |
| self.current_pose = odom_msg.pose.pose | |
| self.current_velocity = odom_msg.twist.twist | |
| # Plan next steps based on current state | |
| if self.current_path: | |
| self.plan_next_footsteps() | |
| def scan_callback(self, scan_msg): | |
| """Handle laser scan for local obstacle detection""" | |
| # Check for obstacles in path | |
| min_range = min(scan_msg.ranges) | |
| if min_range < self.balance_margin: | |
| self.handle_obstacle_avoidance() | |
| def plan_next_footsteps(self): | |
| """Plan next few footsteps based on global path""" | |
| if not self.current_path or self.path_index >= len(self.current_path.poses): | |
| return | |
| # Get current position | |
| current_x = self.current_pose.position.x | |
| current_y = self.current_pose.position.y | |
| # Determine next footsteps | |
| footsteps = [] | |
| for i in range(self.path_index, min(self.path_index + 3, len(self.current_path.poses))): | |
| target_pose = self.current_path.poses[i].pose | |
| # Calculate required footstep with collision avoidance | |
| adjusted_pose = self.adjust_footstep_for_obstacles( | |
| current_x, current_y, | |
| target_pose.position.x, target_pose.position.y | |
| ) | |
| # Check if step is dynamically feasible | |
| if self.is_step_feasible(adjusted_pose): | |
| footsteps.append(adjusted_pose) | |
| # Publish footstep commands | |
| if footsteps: | |
| self.publish_footsteps(footsteps) | |
| def adjust_footstep_for_obstacles(self, current_x, current_y, target_x, target_y): | |
| """Adjust footstep to avoid obstacles""" | |
| # Calculate desired step | |
| dx = target_x - current_x | |
| dy = target_y - current_y | |
| # Check for obstacles along the path | |
| # This is a simplified version | |
| step_pose = Pose() | |
| step_pose.position.x = target_x | |
| step_pose.position.y = target_y | |
| step_pose.position.z = 0.0 # Ground level | |
| # Add orientation | |
| yaw = math.atan2(dy, dx) | |
| step_pose.orientation = self.yaw_to_quaternion(yaw) | |
| return step_pose | |
| def is_step_feasible(self, footstep_pose): | |
| """Check if a footstep is dynamically feasible""" | |
| # Check if step is within balance limits | |
| # Check for obstacles at footstep location | |
| # Check terrain stability | |
| # Simplified feasibility check | |
| # In practice, this would involve ZMP calculations and terrain analysis | |
| return True | |
| def handle_obstacle_avoidance(self): | |
| """Handle local obstacle avoidance""" | |
| # For bipedal robots, this might involve: | |
| # - Adjusting next few footsteps | |
| # - Modifying gait parameters | |
| # - Temporary stopping if necessary | |
| self.get_logger().info('Obstacle detected, adjusting path') | |
| # Implementation would depend on specific robot and sensor setup | |
| def publish_footsteps(self, footsteps): | |
| """Publish planned footsteps to robot controller""" | |
| for i, step in enumerate(footsteps): | |
| step_cmd = String() | |
| step_cmd.data = f"STEP {self.path_index + i}: ({step.position.x:.2f}, {step.position.y:.2f})" | |
| self.footstep_pub.publish(step_cmd) | |
| # Update path index | |
| self.path_index += len(footsteps) | |
| ``` | |
| ## Controller Server Adaptations | |
| The controller server for bipedal robots needs to: | |
| - **Convert paths to footstep sequences** | |
| - **Generate gait patterns** based on path following requirements | |
| - **Maintain balance** during execution | |
| - **Handle disturbances** and recover balance | |
| ```python | |
| class BipedalController(Node): | |
| def __init__(self): | |
| super().__init__('bipedal_controller') | |
| # Action client to send footstep commands to the robot | |
| self.footstep_client = ActionClient( | |
| self, | |
| FollowFootstepPath, # Custom action type | |
| 'follow_footstep_path' | |
| ) | |
| # Controller frequency | |
| self.controller_freq = 10 # Hz for high-level footstep planning | |
| self.controller_timer = self.create_timer( | |
| 1.0 / self.controller_freq, | |
| self.controller_callback | |
| ) | |
| # Path tracking | |
| self.current_path = None | |
| self.path_progress = 0.0 | |
| self.control_loop_counter = 0 | |
| # Bipedal-specific parameters | |
| self.step_length = 0.3 # meters | |
| self.step_height = 0.02 # meters (clearance) | |
| self.step_duration = 0.8 # seconds per step | |
| # Balance parameters | |
| self.balance_threshold = 0.05 # meters max deviation | |
| self.recovery_enabled = True | |
| self.get_logger().info('Bipedal Controller initialized') | |
| def controller_callback(self): | |
| """Main control callback""" | |
| if self.current_path is None or len(self.current_path.poses) == 0: | |
| return | |
| # Determine next segment of path to follow | |
| next_waypoints = self.get_next_waypoints() | |
| if next_waypoints: | |
| # Generate footstep plan for the next segment | |
| footstep_plan = self.plan_footsteps(next_waypoints) | |
| # Send footstep commands to robot | |
| self.execute_footstep_plan(footstep_plan) | |
| def get_next_waypoints(self): | |
| """Get next waypoints from the global path""" | |
| if self.path_progress >= len(self.current_path.poses): | |
| return [] | |
| # Return next few waypoints to plan footsteps for | |
| start_idx = int(self.path_progress) | |
| end_idx = min(start_idx + 5, len(self.current_path.poses)) | |
| waypoints = [] | |
| for i in range(start_idx, end_idx): | |
| waypoints.append(self.current_path.poses[i].pose) | |
| return waypoints | |
| def plan_footsteps(self, waypoints): | |
| """Plan detailed footsteps for a sequence of waypoints""" | |
| # This would interface with a footstep planner | |
| # For this example, we'll create a simple footstep sequence | |
| footsteps = [] | |
| for i, waypoint in enumerate(waypoints): | |
| # Create a footstep at this location | |
| footstep = Footstep() # Custom message type | |
| footstep.pose = waypoint | |
| footstep.step_type = "walk" # Could be walk, step, turn, etc. | |
| footstep.duration = self.step_duration | |
| footstep.foot = "left" if i % 2 == 0 else "right" | |
| footsteps.append(footstep) | |
| return footsteps | |
| def execute_footstep_plan(self, footstep_plan): | |
| """Execute a planned sequence of footsteps""" | |
| if not footstep_plan: | |
| return | |
| # Send the plan to the robot's footstep execution system | |
| goal = FollowFootstepPath.Goal() | |
| goal.footstep_path = footstep_plan | |
| # Wait for server and send goal | |
| self.footstep_client.wait_for_server() | |
| future = self.footstep_client.send_goal_async(goal) | |
| # Handle result | |
| future.add_done_callback(self.footstep_execution_callback) | |
| def footstep_execution_callback(self, future): | |
| """Handle completion of footstep execution""" | |
| goal_handle = future.result() | |
| if goal_handle.accepted: | |
| self.get_logger().info('Footstep plan accepted') | |
| else: | |
| self.get_logger().error('Footstep plan rejected') | |
| ``` | |
| ## Custom Costmaps for Bipedal Navigation | |
| Bipedal robots need specialized costmaps that consider: | |
| - **Balance zones**: Where feet can be safely placed | |
| - **Terrain stability**: Different costs for different ground types | |
| - **Obstacle clearance**: Sufficient space for foot placement | |
| - **Dynamic stability**: Areas that maintain ZMP within safe limits | |
| ```python | |
| class BipedalCostmapGenerator(Node): | |
| def __init__(self): | |
| super().__init__('bipedal_costmap_generator') | |
| # Publishers for specialized costmaps | |
| self.balance_costmap_pub = self.create_publisher( | |
| OccupancyGrid, | |
| '/bipedal_balance_costmap', | |
| 10 | |
| ) | |
| self.foot_placement_costmap_pub = self.create_publisher( | |
| OccupancyGrid, | |
| '/bipedal_foot_placement_costmap', | |
| 10 | |
| ) | |
| # Subscribers for sensor data | |
| self.map_sub = self.create_subscription( | |
| OccupancyGrid, | |
| '/map', | |
| self.map_callback, | |
| 10 | |
| ) | |
| self.imu_sub = self.create_subscription( | |
| Imu, # Simplified type | |
| '/imu', | |
| self.imu_callback, | |
| 10 | |
| ) | |
| # Costmap update timer | |
| self.costmap_timer = self.create_timer(0.5, self.update_costmaps) | |
| # Internal data | |
| self.base_map = None | |
| self.current_terrain_type = "flat" | |
| self.balance_state = "stable" | |
| self.get_logger().info('Bipedal Costmap Generator initialized') | |
| def map_callback(self, map_msg): | |
| """Handle new map data""" | |
| self.base_map = map_msg | |
| self.get_logger().info('Received new map for costmap generation') | |
| def imu_callback(self, imu_msg): | |
| """Update balance state from IMU""" | |
| # Calculate if robot is currently in stable state | |
| # This is simplified - real implementation would use ZMP calculations | |
| orientation = imu_msg.orientation | |
| # Process orientation data to determine balance state | |
| # Update based on IMU data | |
| self.update_balance_state(imu_msg) | |
| def update_balance_state(self, imu_msg): | |
| """Update internal balance state based on IMU""" | |
| # Extract roll and pitch from orientation | |
| import tf_transformations | |
| orientation_list = [imu_msg.orientation.x, imu_msg.orientation.y, | |
| imu_msg.orientation.z, imu_msg.orientation.w] | |
| roll, pitch, yaw = tf_transformations.euler_from_quaternion(orientation_list) | |
| # Determine balance state based on tilt | |
| tilt_magnitude = math.sqrt(roll*roll + pitch*pitch) | |
| if tilt_magnitude > 0.5: # Threshold for instability | |
| self.balance_state = "unstable" | |
| else: | |
| self.balance_state = "stable" | |
| def update_costmaps(self): | |
| """Update specialized costmaps for bipedal navigation""" | |
| if self.base_map is None: | |
| return | |
| # Generate balance-based costmap | |
| balance_costmap = self.generate_balance_costmap() | |
| self.balance_costmap_pub.publish(balance_costmap) | |
| # Generate foot placement costmap | |
| foot_placement_costmap = self.generate_foot_placement_costmap() | |
| self.foot_placement_costmap_pub.publish(foot_placement_costmap) | |
| self.get_logger().debug('Costmaps updated') | |
| def generate_balance_costmap(self): | |
| """Generate costmap considering balance constraints""" | |
| # Start with base map | |
| costmap = OccupancyGrid() | |
| costmap.header = self.base_map.header | |
| costmap.info = self.base_map.info | |
| costmap.data = list(self.base_map.data) # Copy base data | |
| # Add balance-specific costs | |
| for i in range(len(costmap.data)): | |
| if costmap.data[i] > 0: # If there's already an obstacle cost | |
| continue | |
| # Calculate costs based on terrain stability, slope, etc. | |
| x, y = self.grid_to_world(i % costmap.info.width, i // costmap.info.width) | |
| cost = self.calculate_balance_cost(x, y) | |
| # Add to existing cost | |
| if cost > 0: | |
| costmap.data[i] = min(100, costmap.data[i] + int(cost * 100)) | |
| return costmap | |
| def generate_foot_placement_costmap(self): | |
| """Generate costmap for optimal foot placement""" | |
| costmap = OccupancyGrid() | |
| costmap.header = self.base_map.header | |
| costmap.info = self.base_map.info | |
| costmap.data = list(self.base_map.data) | |
| # Consider terrain type and stability for foot placement | |
| for i in range(len(costmap.data)): | |
| if costmap.data[i] > 0: # If obstacle | |
| continue | |
| x, y = self.grid_to_world(i % costmap.info.width, i // costmap.info.width) | |
| cost = self.calculate_foot_placement_cost(x, y) | |
| if cost > 0: | |
| costmap.data[i] = min(100, costmap.data[i] + int(cost * 100)) | |
| return costmap | |
| def calculate_balance_cost(self, x, y): | |
| """Calculate balance-related cost for a position""" | |
| # This would implement complex balance calculations | |
| # For now, a simple implementation | |
| return 0.0 # Simplified | |
| def calculate_foot_placement_cost(self, x, y): | |
| """Calculate foot placement cost for a position""" | |
| # Consider terrain type, stability, etc. | |
| return 0.0 # Simplified | |
| def grid_to_world(self, grid_x, grid_y): | |
| """Convert grid coordinates to world coordinates""" | |
| x = self.base_map.info.origin.position.x + grid_x * self.base_map.info.resolution | |
| y = self.base_map.info.origin.position.y + grid_y * self.base_map.info.resolution | |
| return x, y | |
| ``` | |
| ## Integration with Isaac ROS Components | |
| Isaac ROS provides GPU-accelerated perception that can enhance bipedal navigation: | |
| ```python | |
| class IsaacBipedalIntegration(Node): | |
| def __init__(self): | |
| super().__init__('isaac_bipedal_integration') | |
| # Isaac ROS perception nodes interface | |
| self.depth_sub = self.create_subscription( | |
| Image, # Isaac depth image | |
| '/isaac_ros/depth/image', | |
| self.depth_callback, | |
| 10 | |
| ) | |
| self.segmentation_sub = self.create_subscription( | |
| Image, # Isaac segmentation mask | |
| '/isaac_ros/segmentation/mask', | |
| self.segmentation_callback, | |
| 10 | |
| ) | |
| # Publishers for enhanced navigation | |
| self.terrain_classification_pub = self.create_publisher( | |
| String, # Custom message for terrain type | |
| '/terrain_classification', | |
| 10 | |
| ) | |
| self.foot_placement_analysis_pub = self.create_publisher( | |
| String, # Foot placement analysis results | |
| '/foot_placement_analysis', | |
| 10 | |
| ) | |
| self.get_logger().info('Isaac Bipedal Integration initialized') | |
| def depth_callback(self, depth_msg): | |
| """Process depth data for terrain analysis""" | |
| # Analyze depth data to identify terrain properties | |
| # - Surface inclination for balance planning | |
| # - Obstacle detection for foot placement | |
| # - Step height for stair navigation | |
| # This would use Isaac's GPU-accelerated processing | |
| terrain_info = self.analyze_terrain_from_depth(depth_msg) | |
| self.terrain_classification_pub.publish(terrain_info) | |
| def segmentation_callback(self, seg_msg): | |
| """Process segmentation data for foot placement""" | |
| # Analyze segmentation to identify: | |
| # - Traversable surfaces vs obstacles | |
| # - Different terrain types (grass, concrete, etc.) | |
| # - Hazardous areas (water, holes, etc.) | |
| foot_placement_analysis = self.analyze_foot_placement_area(seg_msg) | |
| self.foot_placement_analysis_pub.publish(foot_placement_analysis) | |
| def analyze_terrain_from_depth(self, depth_msg): | |
| """Analyze terrain properties from depth data""" | |
| # GPU-accelerated terrain analysis using Isaac tools | |
| # This would implement complex terrain classification | |
| result = String() | |
| result.data = "terrain_analysis_result" | |
| return result | |
| def analyze_foot_placement_area(self, seg_msg): | |
| """Analyze suitable areas for foot placement""" | |
| # Use segmentation to identify safe foot placement zones | |
| result = String() | |
| result.data = "foot_placement_analysis_result" | |
| return result | |
| ``` | |
| ## Footstep Planning Integration | |
| A complete bipedal navigation system integrates high-level path planning with footstep generation: | |
| ```python | |
| class IntegratedBipedalNavigator(Node): | |
| def __init__(self): | |
| super().__init__('integrated_bipedal_navigator') | |
| # Interface with Nav2 components | |
| self.path_client = ActionClient( | |
| self, | |
| ComputePathToPose, | |
| 'compute_path_to_pose' | |
| ) | |
| # Interface with footstep planner | |
| self.footstep_planner = ActionClient( | |
| self, | |
| PlanFootsteps, | |
| 'plan_footsteps' | |
| ) | |
| # Navigation command interface | |
| self.nav_goal_sub = self.create_subscription( | |
| PoseStamped, | |
| '/goal_pose', | |
| self.navigation_goal_callback, | |
| 10 | |
| ) | |
| # Initialize components | |
| self.bipedal_costmap_generator = BipedalCostmapGenerator(self) | |
| self.bipedal_local_planner = BipedalLocalPlanner(self) | |
| self.get_logger().info('Integrated Bipedal Navigator initialized') | |
| def navigation_goal_callback(self, goal_msg): | |
| """Handle new navigation goal""" | |
| # Plan high-level path first | |
| self.plan_global_path(goal_msg.pose) | |
| def plan_global_path(self, goal_pose): | |
| """Plan global path considering bipedal constraints""" | |
| # Create path planning request | |
| path_goal = ComputePathToPose.Goal() | |
| path_goal.goal = goal_pose | |
| # Add bipedal-specific parameters | |
| path_goal.planner_id = "bipedal_global_planner" | |
| # Send to global planner | |
| self.path_client.wait_for_server() | |
| future = self.path_client.send_goal_async(path_goal) | |
| future.add_done_callback(self.global_path_callback) | |
| def global_path_callback(self, future): | |
| """Handle global path planning result""" | |
| goal_handle = future.result() | |
| if goal_handle.accepted: | |
| path_result = goal_handle.result() | |
| # Plan detailed footsteps for the path | |
| self.plan_detailed_footsteps(path_result.path) | |
| else: | |
| self.get_logger().error('Global path planning failed') | |
| def plan_detailed_footsteps(self, path): | |
| """Plan detailed footsteps for a high-level path""" | |
| footstep_goal = PlanFootsteps.Goal() | |
| footstep_goal.path = path | |
| footstep_goal.robot_properties = self.get_robot_properties() | |
| self.footstep_planner.wait_for_server() | |
| future = self.footstep_planner.send_goal_async(footstep_goal) | |
| future.add_done_callback(self.footstep_plan_callback) | |
| def footstep_plan_callback(self, future): | |
| """Handle footstep planning result""" | |
| goal_handle = future.result() | |
| if goal_handle.accepted: | |
| footstep_result = goal_handle.result() | |
| # Execute the planned footsteps | |
| self.execute_navigation(footstep_result.footsteps) | |
| else: | |
| self.get_logger().error('Footstep planning failed') | |
| def execute_navigation(self, footsteps): | |
| """Execute the planned navigation""" | |
| # Interface with robot's walking controller | |
| # Monitor execution and handle replanning if needed | |
| self.get_logger().info(f'Executing navigation with {len(footsteps)} steps') | |
| def get_robot_properties(self): | |
| """Get robot-specific properties for planning""" | |
| # Return properties like step length, width, height, etc. | |
| from builtin_interfaces.msg import Duration | |
| props = RobotProperties() | |
| props.max_step_length = 0.3 # meters | |
| props.max_step_width = 0.2 # meters | |
| props.step_height_clearance = 0.05 | |
| props.balance_margin = 0.1 # meters | |
| props.zmp_stability_threshold = 0.05 # meters | |
| props.walk_period = Duration(sec=0, nanosec=800000000) # 0.8 seconds | |
| return props | |
| ``` | |
| ## Performance Optimization | |
| For real-time bipedal navigation, optimization is critical: | |
| ```python | |
| class OptimizedBipedalPlanner: | |
| def __init__(self): | |
| # Pre-computed lookup tables for common gait patterns | |
| self.gait_patterns = self.precompute_gait_patterns() | |
| # Cached inverse kinematics solutions | |
| self.ik_cache = {} | |
| # Multi-resolution path planning | |
| self.coarse_planner = None | |
| self.fine_planner = None | |
| def precompute_gait_patterns(self): | |
| """Pre-compute common gait patterns for fast lookup""" | |
| # This would contain pre-computed footstep sequences | |
| # for common movement patterns (forward, backward, turns, etc.) | |
| patterns = {} | |
| # Forward steps | |
| patterns['forward'] = self.compute_standard_gait(0.0, 0.3, 0.0) # x, y, theta | |
| # Turning steps | |
| patterns['turn_left'] = self.compute_standard_gait(0.1, 0.0, 0.2) # Small forward + rotation | |
| patterns['turn_right'] = self.compute_standard_gait(0.1, 0.0, -0.2) | |
| return patterns | |
| def compute_standard_gait(self, dx, dy, dtheta): | |
| """Compute a standard gait pattern for given movement""" | |
| # Simplified gait pattern computation | |
| # In practice, this would use complex biomechanical models | |
| footsteps = [] | |
| # Implementation would generate appropriate footstep sequence | |
| return footsteps | |
| ``` | |
| ## Safety and Recovery Mechanisms | |
| Bipedal navigation systems need robust safety mechanisms: | |
| ```python | |
| class BipedalSafetyManager(Node): | |
| def __init__(self): | |
| super().__init__('bipedal_safety_manager') | |
| # Monitor system health | |
| self.imu_sub = self.create_subscription(Imu, '/imu', self.imu_callback, 10) | |
| self.joint_state_sub = self.create_subscription(JointState, '/joint_states', self.joint_state_callback, 10) | |
| # Emergency stop publisher | |
| self.emergency_stop_pub = self.create_publisher(Bool, '/emergency_stop', 10) | |
| # Recovery action client | |
| self.recovery_client = ActionClient(self, ExecuteRecovery, 'execute_recovery') | |
| # Safety timers | |
| self.safety_timer = self.create_timer(0.1, self.check_safety) | |
| # Safety thresholds | |
| self.tilt_threshold = 0.785 # 45 degrees | |
| self.velocity_threshold = 1.0 # m/s | |
| self.joint_limit_threshold = 0.1 # radians from limit | |
| self.current_tilt = 0.0 | |
| self.current_velocity = 0.0 | |
| self.in_safe_state = True | |
| self.get_logger().info('Bipedal Safety Manager initialized') | |
| def imu_callback(self, imu_msg): | |
| """Monitor robot's balance state""" | |
| # Calculate tilt angle from IMU | |
| import tf_transformations | |
| orientation_list = [imu_msg.orientation.x, imu_msg.orientation.y, | |
| imu_msg.orientation.z, imu_msg.orientation.w] | |
| roll, pitch, yaw = tf_transformations.euler_from_quaternion(orientation_list) | |
| self.current_tilt = math.sqrt(roll*roll + pitch*pitch) | |
| def joint_state_callback(self, joint_state_msg): | |
| """Monitor joint positions for safety""" | |
| # Check joint limits and velocities | |
| pass | |
| def check_safety(self): | |
| """Check if robot is in safe state""" | |
| if self.current_tilt > self.tilt_threshold: | |
| self.trigger_recovery("balance_loss") | |
| elif self.current_velocity > self.velocity_threshold: | |
| self.trigger_recovery("speed_violation") | |
| def trigger_recovery(self, reason): | |
| """Trigger recovery procedure""" | |
| self.get_logger().warn(f'Safety violation: {reason}, initiating recovery') | |
| # Send emergency stop | |
| stop_msg = Bool() | |
| stop_msg.data = True | |
| self.emergency_stop_pub.publish(stop_msg) | |
| # Trigger recovery action | |
| recovery_goal = ExecuteRecovery.Goal() | |
| recovery_goal.recovery_type = reason | |
| self.recovery_client.wait_for_server() | |
| self.recovery_client.send_goal_async(recovery_goal) | |
| ``` | |
| ## Summary | |
| This chapter covered the adaptation of Nav2 for bipedal humanoid robots: | |
| - Unique challenges of bipedal navigation compared to wheeled robots | |
| - Modifications to Nav2 components (global planner, local planner, controller) for bipedal locomotion | |
| - Integration of footstep planning with traditional path planning | |
| - Specialized costmaps for balance and foot placement | |
| - Safety and recovery mechanisms for bipedal robots | |
| - Performance optimization techniques | |
| Bipedal navigation requires fundamentally different approaches than traditional mobile robotics, focusing on discrete footsteps, balance maintenance, and gait adaptation rather than continuous velocity control. | |
| ## Exercises | |
| 1. Create a simple footstep planner that generates footsteps between two points | |
| 2. Implement a costmap that considers terrain stability for foot placement | |
| 3. Design a safety mechanism that detects balance loss and triggers recovery | |
| ## Next Steps | |
| In the next chapter, we'll explore advanced AI-robot brain techniques that leverage the perception and navigation capabilities we've developed, focusing on higher-level cognitive functions and learning for humanoid robots. |