"""Robot backend factory. Selects and constructs the concrete :class:`RobotInterface` implementation based on the runtime config. The chosen backend is *lazy-imported* so the unused backend's heavy dependencies are never pulled in: * ``cfg.mock`` is ``True`` -> :class:`~gowelcome.robot.mock_robot.MockRobot` (webcam / video file; no robot SDK required). * ``cfg.transport == "webrtc"`` -> :class:`~gowelcome.robot.webrtc_robot.Go2WebRTCRobot` (default; app WebRTC protocol via ``unitree_webrtc_connect``; wifi + audio). * ``cfg.transport == "dds"`` -> :class:`~gowelcome.robot.go2_robot.Go2Robot` (official ``unitree_sdk2py`` over CycloneDDS; wired / EDU). """ from __future__ import annotations from typing import TYPE_CHECKING from gowelcome.robot.interface import RobotInterface if TYPE_CHECKING: # pragma: no cover - type hints only from config import GoWelcomeConfig def build_robot(cfg: "GoWelcomeConfig") -> RobotInterface: """Build the appropriate robot backend for ``cfg``. ``cfg.mock`` -> MockRobot; else ``cfg.transport`` selects the real backend (``"webrtc"`` default, ``"dds"`` for the official SDK). The unselected backends are never imported, so their heavy/optional dependencies need not be installed. Args: cfg: The fully-populated :class:`~config.GoWelcomeConfig`. Returns: A ready-to-use :class:`RobotInterface` instance. Raises: ValueError: if ``cfg.transport`` is not a known value. """ if cfg.mock: from gowelcome.robot.mock_robot import MockRobot return MockRobot(cfg) transport = (cfg.transport or "webrtc").strip().lower() if transport == "webrtc": from gowelcome.robot.webrtc_robot import Go2WebRTCRobot return Go2WebRTCRobot(cfg) if transport == "dds": from gowelcome.robot.go2_robot import Go2Robot return Go2Robot(cfg) raise ValueError( f"Unknown transport {cfg.transport!r} (expected 'webrtc' or 'dds')." )