Buckets:
| # Joystick Controller | |
| This example demonstrates how to control Reachy Mini's head yaw angle using a joystick (PS4 or Xbox controller). The left joystick controls the head's left-right rotation, providing intuitive real-time control of the robot. | |
| **Controls:** | |
| - **LEFT JOYSTICK (Left/Right)**: Control head yaw angle | |
| - **CIRCLE / B BUTTON**: Quit the application safely | |
| - **CTRL-C**: Quit the application | |
| **Requirements:** | |
| - Install pygame: `pip install pygame` | |
| - Connect a PS4 or Xbox controller to your computer | |
| **Controller mappings:** | |
| - PS4: Button 1 = Circle (O), Axis 0 = Left Stick Horizontal | |
| - Xbox: Button 1 = B, Axis 0 = Left Stick Horizontal | |
| ```python | |
| # Standard library imports | |
| import os | |
| import sys | |
| import time | |
| # Third-party imports | |
| import numpy as np | |
| import pygame | |
| # Local application/library-specific imports | |
| from reachy_mini import ReachyMini, utils | |
| # --- Configuration --- | |
| CONTROL_LOOP_RATE = 0.02 | |
| # Maximum yaw angle. The joystick's -1 to 1 input will be mapped to this range. | |
| YAW_ANGLE_LIMIT = np.pi / 4 * 1.3 # Radians | |
| # To use pygame "headlessly" (without a GUI window). | |
| os.environ["SDL_VIDEODRIVER"] = "dummy" | |
| # --- Controller Bindings Comment --- | |
| # PS4 controller: | |
| # Button 1 = O (Circle) | |
| # Axis 0: Left Joy Left/Right (-1 left, 1 right) | |
| # Axis 3 or 4: Right Joy Left/Right | |
| # | |
| # XBOX controller: | |
| # Button 1 = B | |
| # Axis 0: Left Joy Left/Right | |
| # Axis 2 or 3: Right Joy Left/Right | |
| class Controller: | |
| """Handle joystick input using pygame.""" | |
| def __init__(self, deadzone: float = 0.08): | |
| """Initialize the controller and find the first joystick. | |
| Args: | |
| deadzone (float): Axis value below which input is ignored. | |
| Raises: | |
| IOError: If no joystick is found. | |
| """ | |
| pygame.init() | |
| pygame.joystick.init() | |
| if pygame.joystick.get_count() < 1: | |
| raise IOError("No joystick controller found.") | |
| self.joystick: pygame.joystick.Joystick = pygame.joystick.Joystick(0) | |
| self.deadzone = deadzone | |
| print(f"Initialized joystick: {self.joystick.get_name()}") | |
| def _apply_deadzone(self, value: float) -> float: | |
| """Apply a deadzone to a joystick axis value.""" | |
| return value if abs(value) > self.deadzone else 0.0 | |
| def get_horizontal_inputs(self) -> tuple[float, float]: | |
| """Read the horizontal axes of the left and right joysticks. | |
| Returns: | |
| tuple[float, float]: (left_joy_h, right_joy_h) from -1.0 to 1.0. | |
| """ | |
| pygame.event.pump() # Update pygame's internal event state. | |
| left_joy_h = self._apply_deadzone(self.joystick.get_axis(0)) | |
| # Right joystick horizontal axis can be 2, 3 or 4 depending on controller | |
| right_joy_h = 0.0 | |
| if self.joystick.get_numaxes() > 3: | |
| right_joy_h = self._apply_deadzone(self.joystick.get_axis(3)) | |
| elif self.joystick.get_numaxes() > 2: | |
| right_joy_h = self._apply_deadzone(self.joystick.get_axis(2)) | |
| return left_joy_h, right_joy_h | |
| def check_for_quit(self) -> bool: | |
| """Check pygame events for a quit signal. | |
| Returns: | |
| bool: True if the designated quit button (Circle/B) is pressed. | |
| """ | |
| for event in pygame.event.get(): | |
| if event.type == pygame.JOYBUTTONDOWN: | |
| if self.joystick.get_button(1): # Button 1 is Circle/B | |
| print("\nQuit button pressed.") | |
| return True | |
| return False | |
| def main() -> None: | |
| """Run the main joystick control loop.""" | |
| try: | |
| controller = Controller() | |
| except IOError as e: | |
| print(f"Error: {e}", file=sys.stderr) | |
| return | |
| print("Connecting to Reachy Mini...") | |
| try: | |
| # The 'with' statement ensures the robot is properly handled on exit | |
| with ReachyMini(automatic_body_yaw=True) as mini: | |
| print("Robot connected.") | |
| print("\n" + "=" * 50) | |
| print(" Reachy Head Yaw Joystick Controller") | |
| print(" CONTROLS: [Left Stick] to turn | [Circle/B] to quit") | |
| print("=" * 50 + "\n") | |
| while True: | |
| if controller.check_for_quit(): | |
| break | |
| # Get scaled joystick values | |
| left_joy, right_joy = controller.get_horizontal_inputs() | |
| # Map joystick input (-1 to 1) to the desired angle range | |
| target_yaw = left_joy * YAW_ANGLE_LIMIT | |
| target_body_yaw = right_joy * YAW_ANGLE_LIMIT | |
| # Define the target pose: x,y,z and roll,pitch,yaw | |
| target_position = np.array([0, 0, 0.0]) | |
| target_orientation = np.array([0, 0, target_yaw]) | |
| # Create and send the command to the robot | |
| mini.set_target( | |
| utils.create_head_pose( | |
| x=target_position[0], | |
| y=target_position[1], | |
| z=target_position[2], | |
| roll=target_orientation[0], | |
| pitch=target_orientation[1], | |
| yaw=target_orientation[2], | |
| degrees=False, | |
| ), | |
| body_yaw=target_body_yaw, | |
| ) | |
| # Print status, overwriting the line | |
| print( | |
| f"\rHead Yaw: {target_yaw:6.2f} rad | " | |
| f"Body Yaw: {target_body_yaw:6.2f} rad", | |
| end="", | |
| ) | |
| sys.stdout.flush() | |
| time.sleep(CONTROL_LOOP_RATE) | |
| except KeyboardInterrupt: | |
| print("\nCTRL+C detected. Shutting down...") | |
| except Exception as e: | |
| print(f"\nAn unexpected error occurred: {e}", file=sys.stderr) | |
| finally: | |
| print("\n\nApplication finished. Robot will go to sleep.") | |
| pygame.quit() | |
| if __name__ == "__main__": | |
| main() | |
| ``` | |
Xet Storage Details
- Size:
- 5.93 kB
- Xet hash:
- e01a432b3b05ee0a899dc926dd2bcec51cbca3930c22519374f9862aae9b957c
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.