# !/usr/bin/env python # Copyright 2025 The HuggingFace Inc. team. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import sys from enum import IntEnum from typing import Any import numpy as np from ..teleoperator import Teleoperator from .configuration_gamepad import GamepadTeleopConfig class GripperAction(IntEnum): CLOSE = 0 STAY = 1 OPEN = 2 gripper_action_map = { "close": GripperAction.CLOSE.value, "open": GripperAction.OPEN.value, "stay": GripperAction.STAY.value, } class GamepadTeleop(Teleoperator): """ Teleop class to use gamepad inputs for control. """ config_class = GamepadTeleopConfig name = "gamepad" def __init__(self, config: GamepadTeleopConfig): super().__init__(config) self.config = config self.robot_type = config.type self.gamepad = None @property def action_features(self) -> dict: if self.config.use_gripper: return { "dtype": "float32", "shape": (4,), "names": {"delta_x": 0, "delta_y": 1, "delta_z": 2, "gripper": 3}, } else: return { "dtype": "float32", "shape": (3,), "names": {"delta_x": 0, "delta_y": 1, "delta_z": 2}, } @property def feedback_features(self) -> dict: return {} def connect(self) -> None: # use HidApi for macos if sys.platform == "darwin": # NOTE: On macOS, pygame doesn’t reliably detect input from some controllers so we fall back to hidapi from .gamepad_utils import GamepadControllerHID as Gamepad else: from .gamepad_utils import GamepadController as Gamepad self.gamepad = Gamepad() self.gamepad.start() def get_action(self) -> dict[str, Any]: # Update the controller to get fresh inputs self.gamepad.update() # Get movement deltas from the controller delta_x, delta_y, delta_z = self.gamepad.get_deltas() # Create action from gamepad input gamepad_action = np.array([delta_x, delta_y, delta_z], dtype=np.float32) action_dict = { "delta_x": gamepad_action[0], "delta_y": gamepad_action[1], "delta_z": gamepad_action[2], } # Default gripper action is to stay gripper_action = GripperAction.STAY.value if self.config.use_gripper: gripper_command = self.gamepad.gripper_command() gripper_action = gripper_action_map[gripper_command] action_dict["gripper"] = gripper_action return action_dict def disconnect(self) -> None: """Disconnect from the gamepad.""" if self.gamepad is not None: self.gamepad.stop() self.gamepad = None def is_connected(self) -> bool: """Check if gamepad is connected.""" return self.gamepad is not None def calibrate(self) -> None: """Calibrate the gamepad.""" # No calibration needed for gamepad pass def is_calibrated(self) -> bool: """Check if gamepad is calibrated.""" # Gamepad doesn't require calibration return True def configure(self) -> None: """Configure the gamepad.""" # No additional configuration needed pass def send_feedback(self, feedback: dict) -> None: """Send feedback to the gamepad.""" # Gamepad doesn't support feedback pass