Skip to content

Teleop restructuring #1113

@ruthwikdasyam

Description

@ruthwikdasyam

Teleop Device Categories

teleop stack divided into device categories, each with its own base module:

  1. VR Headsets (Browser-based WebXR)
    Meta Quest
    Apple Vision Pro
    PICO
  2. Smart Devices (Browser-based / Native App)
    iPhone IMU
    Smart watches (Maybe)
  3. Hand Controllers (USB/Bluetooth)
    Joysticks (single/dual stick)
    Game controllers (Xbox, PlayStation)
    SpaceMouse
    Keyboard

Design Rationale: Each category has distinct input modalities (6DoF tracking vs IMU vs discrete axes), so a single base class for all devices would be bloated. Instead, each category has its own base module (VRBaseModule, SmartDeviceBaseModule, HandControllerBaseModule) with shared interfaces where appropriate.

class VRBaseModule(Module):
    """Base for all VR headset teleop devices."""
    
    # Lifecycle
    def start(self) -> None: ...
    def stop(self) -> None: ...
    
    # Teleop session (toggled by button press)
    def start_teleop(self) -> None: ...
    def stop_teleop(self) -> None: ...
    
    # Core logic
    def calibrate(self) -> bool:
        """Reset t0, store P0/R0 as reference. Called on teleop start."""
    
    def compute_delta(
        self,
        controller_poses: list[PoseStamped | None],
    ) -> list[PoseStamped | None]:
        """Compute delta from calibrated reference. Always PoseStamped."""
    
    def convert_to_output(
        self, 
        pose: PoseStamped, 
        output_type: type
    ) -> PoseStamped | TwistStamped:
        """Convert to target output type at publish boundary."""
class QuestModule(VRBaseModule):
    """Meta Quest teleop via WebSocket/LCM bridge."""
    
    # Inputs (from Deno bridge)
    left_transform: In[LCMTransform]
    right_transform: In[LCMTransform]
    left_trigger: In[Float32]
    right_trigger: In[Float32]
    teleop_enable: In[Bool]  # X button
    
    # Outputs (user configurable - PoseStamped or TwistStamped)
    left_controller: Out[PoseStamped] 
    right_controller: Out[PoseStamped]
    left_trigger_value: Out[Float32]
    right_trigger_value: Out[Float32]
    
    # Callbacks
    def _on_teleop_enable(self, msg: Bool) -> None:
        """Toggle start_teleop/stop_teleop on button press."""
    def _on_transform(self, index: int, transform: Transform) -> None: ...
    def _on_trigger(self, index: int, msg: Float32) -> None: ...
    
    # Main loop
    def control_loop(self) -> None:
        """Compute deltas, convert to output type, publish."""
  • X press to start, and X press to stop. When pressed X again, it starts from zero

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions