Skip to content

R1Pro Quest VR teleop: Viser, tuned waist redundancy, Home preset and real entrypoint - #5084

Draft
TomCC7 wants to merge 21 commits into
cc/feat/r1pro-grippersfrom
cc/feat/r1pro-vr-teleop
Draft

TomCC7 wants to merge 21 commits into
cc/feat/r1pro-grippersfrom
cc/feat/r1pro-vr-teleop

Conversation

@TomCC7

@TomCC7 TomCC7 commented Nov 6, 2026 •

Copy link
Copy Markdown
Member

Overview

Add Quest VR teleoperation with a fake default and an explicit real-hardware entrypoint. Stacked on #5019.

Problem

Device input mapping should stay separate from robot commands, and waist lift should not compete with arm IK.

Changes

  • Publish original Joy on receipt; map it in an independent JoystickTeleopMapper.
  • Route Twist to native body-velocity actions and Float32 lift speed to the torso task.
  • Keep Odometry separate from actions in both real and fake connections.
  • Let lift own lower torso joints 1/2, alongside IK owning top pitch/yaw and arms.
  • Use right-stick click as the body clutch: left X/Y translate, right X turns, right Y lifts.
  • Bound lift through measured state, joint limits and rate limits; clear intent on timeout, preemption and E-stop.
  • Preserve independent jaws, upper-body hold, Home preset and Viser. No automatic Home.

Run

dimos run r1pro-vr-teleop --g.listen_host 0.0.0.0 --g.viewer none

Physical entrypoint: r1pro-vr-teleop-real; hardware startup requires operator precautions. Controls and configuration are in the adjacent teleop README.

Validation

  • 46 focused fake/VR/mapper/lift/task checks passed; registry generation check passed.
  • Targeted mypy passed on seven production files; Ruff passed.
  • Evidence is a threaded in-process fake chain, not multiprocess/headset or hardware acceptance. Physical backend was parsed, never started.
  • Defaults: 0.3 m/s translation, 0.5 rad/s yaw and 0.03 m/s lift. Hardware dynamics and new CI remain unverified.

@github-actions github-actions Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

go2 replay realtime (arm64)

Details
Benchmark suite Current: f03b8b2 Previous: 14c62ab Ratio
peak memory 1773.133 MB 1762.355 MB 1.01
peak threads 374 threads 385 threads 0.97
network (transport) 2726.649 MB 2731.013 MB 1.00
disk write 1.855 MB 1.605 MB 1.16
instructions 139.518 G 138.791 G 1.01

This comment was automatically generated by workflow using github-action-benchmark.

@codecov

codecov Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

❌ 21 Tests Failed:

Tests completed Failed Passed Skipped
8174 21 8153 55
View the top 3 failed test(s) by shortest run time
dimos.codebase_checks.test_blueprint_kwargs::test_blueprint_atom_kwargs_match_module_config[xarm6-planner-coordinator]
Stack Traces | 0.002s run time
blueprint_name = 'xarm6-planner-coordinator'
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x17c9a67d0>

    @pytest.mark.parametrize("blueprint_name", _blueprint_params())
    def test_blueprint_atom_kwargs_match_module_config(
        blueprint_name: str, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """Fail when blueprint kwargs cannot be consumed by their target module."""
        # The multi-robot blueprints read ROBOT_IPS at import time.
        monkeypatch.setattr(global_config, "robot_ips", "192.0.2.10,192.0.2.11")
        blueprint = _get_blueprint_or_skip(blueprint_name)
    
        violations: list[str] = []
        for atom in blueprint.blueprints:
            unknown_kwargs = sorted(set(atom.kwargs) - _allowed_kwarg_names(atom.module))
            if unknown_kwargs:
                violations.append(
                    f"{atom.module.__module__}.{atom.module.__name__}: unknown kwargs {unknown_kwargs}"
                )
    
        if violations:
            listing = "\n".join(f"  - {violation}" for violation in violations)
>           raise AssertionError(
                f"Blueprint {blueprint_name!r} passes unknown module kwargs:\n{listing}\n\n"
                "Blueprint kwargs are forwarded into the module constructor. "
                "use fields from the config model or explicitly declared `__init__` keyword names."
            )
E           AssertionError: Blueprint 'xarm6-planner-coordinator' passes unknown module kwargs:
E             - dimos.robot.manipulators.xarm.blueprints.basic.XArmCoordinator: unknown kwargs ['model']
E           
E           Blueprint kwargs are forwarded into the module constructor. use fields from the config model or explicitly declared `__init__` keyword names.

atom       = BlueprintAtom(kwargs={'arm': 'xarm6', 'gripper': 'xarm'}, module=<class 'dimos.robot.manipulators.xarm.connection.XArm...e', type=<class 'dimos.msgs.sensor_msgs.JointState.JointState'>, direction='out')), module_refs=(), instance_name=None)
blueprint  = Blueprint(blueprints=(BlueprintAtom(kwargs={'model': RobotModelConfig(rpc_transport=<class 'dimos.protocol.rpc.zenohrp...Coordinator', 'arm_joint_state'): 'joint_state'}), requirement_checks=(), configurator_checks=(), hosted_placements=())
blueprint_name = 'xarm6-planner-coordinator'
listing    = "  - dimos.robot.manipulators.xarm.blueprints.basic.XArmCoordinator: unknown kwargs ['model']"
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x17c9a67d0>
unknown_kwargs = []
violations = ["dimos.robot.manipulators.xarm.blueprints.basic.XArmCoordinator: unknown kwargs ['model']"]

dimos/codebase_checks/test_blueprint_kwargs.py:90: AssertionError
dimos.codebase_checks.test_blueprint_kwargs::test_blueprint_atom_kwargs_match_module_config[piper-teleop]
Stack Traces | 0.003s run time
blueprint_name = 'piper-teleop'
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x17ae44790>

    @pytest.mark.parametrize("blueprint_name", _blueprint_params())
    def test_blueprint_atom_kwargs_match_module_config(
        blueprint_name: str, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """Fail when blueprint kwargs cannot be consumed by their target module."""
        # The multi-robot blueprints read ROBOT_IPS at import time.
        monkeypatch.setattr(global_config, "robot_ips", "192.0.2.10,192.0.2.11")
        blueprint = _get_blueprint_or_skip(blueprint_name)
    
        violations: list[str] = []
        for atom in blueprint.blueprints:
            unknown_kwargs = sorted(set(atom.kwargs) - _allowed_kwarg_names(atom.module))
            if unknown_kwargs:
                violations.append(
                    f"{atom.module.__module__}.{atom.module.__name__}: unknown kwargs {unknown_kwargs}"
                )
    
        if violations:
            listing = "\n".join(f"  - {violation}" for violation in violations)
>           raise AssertionError(
                f"Blueprint {blueprint_name!r} passes unknown module kwargs:\n{listing}\n\n"
                "Blueprint kwargs are forwarded into the module constructor. "
                "use fields from the config model or explicitly declared `__init__` keyword names."
            )
E           AssertionError: Blueprint 'piper-teleop' passes unknown module kwargs:
E             - dimos.robot.manipulators.piper.blueprints.teleop.PiperTwistCoordinator: unknown kwargs ['model']
E           
E           Blueprint kwargs are forwarded into the module constructor. use fields from the config model or explicitly declared `__init__` keyword names.

atom       = BlueprintAtom(kwargs={'model': RobotModel(_source_path=PosixPath('.../Users/runner/.cache.../robot_assets/sources/9e20...Ref(name='file_info', type=<class 'dimos.msgs.files.File.File'>, direction='out')), module_refs=(), instance_name=None)
blueprint  = Blueprint(blueprints=(BlueprintAtom(kwargs={'instance_name': 'ControlCoordinator', 'model': RobotModel(_source_path=Po...Coordinator', 'arm_joint_state'): 'joint_state'}), requirement_checks=(), configurator_checks=(), hosted_placements=())
blueprint_name = 'piper-teleop'
listing    = "  - dimos.robot.manipulators.piper.blueprints.teleop.PiperTwistCoordinator: unknown kwargs ['model']"
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x17ae44790>
unknown_kwargs = []
violations = ["dimos.robot.manipulators.piper.blueprints.teleop.PiperTwistCoordinator: unknown kwargs ['model']"]

dimos/codebase_checks/test_blueprint_kwargs.py:90: AssertionError
dimos.robot.manipulators.piper.test_connection::test_a_failed_start_puts_the_arm_in_its_emergency_stop
Stack Traces | 0.004s run time
rig = <function rig.<locals>.build at 0x3413f39a0>
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x341413a90>

    def test_a_failed_start_puts_the_arm_in_its_emergency_stop(
        rig: Callable[..., Rig], monkeypatch: pytest.MonkeyPatch
    ) -> None:
        def stuck(self: PiperConnection, home: list[float], timeout_s: float = 0.0) -> None:
            raise PiperError("the arm did not reach home")
    
        monkeypatch.setattr(PiperConnection, "_go_home", stuck)
        sdk = FakePiper()
        with pytest.raises(PiperError):
>           rig(sdk)

monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x341413a90>
rig        = <function rig.<locals>.build at 0x3413f39a0>
sdk        = <dimos.robot.manipulators.piper.test_connection.FakePiper object at 0x3414136d0>
stuck      = <function test_a_failed_start_puts_the_arm_in_its_emergency_stop.<locals>.stuck at 0x3413f3ac0>

.../manipulators/piper/test_connection.py:406: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../manipulators/piper/test_connection.py:231: in build
    module.start()
        fake       = <dimos.robot.manipulators.piper.test_connection.FakePiper object at 0x3414136d0>
        made       = [<dimos.robot.manipulators.piper.test_connection.Rig object at 0x341413f70>]
        module     = <dimos.robot.manipulators.piper.connection.PiperConnection object at 0x3414135b0>
        monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x341413a90>
        sdk        = <dimos.robot.manipulators.piper.test_connection.FakePiper object at 0x3414136d0>
        tmp_path   = PosixPath('.../pytest-0/popen-gw3/test_a_failed_start_puts_the_a0')
.../manipulators/piper/connection.py:190: in start
    super().start()
        __class__  = <class 'dimos.robot.manipulators.piper.connection.PiperConnection'>
        self       = <dimos.robot.manipulators.piper.connection.PiperConnection object at 0x3414135b0>
.../control/connection/connection_module.py:184: in start
    self._limits = limits_of(self.config)
        __class__  = <class 'dimos.control.connection.connection_module.ConnectionModule'>
        self       = <dimos.robot.manipulators.piper.connection.PiperConnection object at 0x3414135b0>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

config = PiperConfig(rpc_transport=<class 'dimos.protocol.rpc.zenohrpc.ZenohRPC'>, default_rpc_timeout=120.0, rpc_timeouts={'bu...can_port='can9', setup_can=True, gripper=True, speed_percent=50, home=(0.0, 0.0, 0.0, 0.0, 0.0, 0.0), jump_limit_s=0.1)

    def limits(config: ConnectionConfig) -> dict[str, Limits]:
        """Every key a robot accepts, with how far each may go, from its config.
    
        Only declared interfaces get keys, in model order. Declare PD_INTERFACES
        for joints driven by position, velocity, effort and gains together. Bounds
        come from the model; commands outside these bounds are rejected.
    
        Raises:
            ValueError: If joints are commanded without a model, no joint of the
                model moves, the model lacks a bound a command needs, or the
                config commands nothing.
        """
        source = config.source
        if not config.joint_commands:
            raise ValueError(f"{source!r} is told nothing: set joint_commands")
        if config.model is None:
            raise ValueError(f"{source!r} commands joints but has no model: set `model`")
        joints = {joint.name: joint for joint in movable_joints(config.model)}
        if not joints:
            raise ValueError(f"{source!r} has no joints to drive: none in its model moves")
>       unknown = config.joint_commands.keys() - joints.keys()
E       AttributeError: 'tuple' object has no attribute 'keys'

config     = PiperConfig(rpc_transport=<class 'dimos.protocol.rpc.zenohrpc.ZenohRPC'>, default_rpc_timeout=120.0, rpc_timeouts={'bu...can_port='can9', setup_can=True, gripper=True, speed_percent=50, home=(0.0, 0.0, 0.0, 0.0, 0.0, 0.0), jump_limit_s=0.1)
joints     = {'gripper': JointDescription(name='gripper', type='prismatic', parent_link='link6', child_link='gripper_link', origin_....0, 0.0), lower=-2.0, upper=2.0, velocity=3.0, acceleration=None, effort=100.0, axis=(1.0, 0.0, 0.0), mimic=None), ...}
source     = 'arm'

.../control/contract/limits.py:98: AttributeError
dimos.robot.manipulators.piper.test_connection::test_a_home_outside_the_limits_is_refused_before_the_arm_is_touched
Stack Traces | 0.004s run time
tmp_path = PosixPath('.../pytest-0/popen-gw3/test_a_home_outside_the_limits0')
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x3415403d0>

    def test_a_home_outside_the_limits_is_refused_before_the_arm_is_touched(
        tmp_path: Path, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        sdk = FakePiper()
        monkeypatch.setattr(connection, "_open_sdk", lambda port: sdk)
        module = PiperConnection(
            can_port=PORT, home=(10.0, 0.0, 0.0, 0.0, 0.0, 0.0), model=piper_like_model(tmp_path)
        )
        Rig(module, sdk)
        try:
            with pytest.raises(ValueError, match="joint1"):
>               module.start()

module     = <dimos.robot.manipulators.piper.connection.PiperConnection object at 0x3415408b0>
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x3415403d0>
sdk        = <dimos.robot.manipulators.piper.test_connection.FakePiper object at 0x341542f80>
tmp_path   = PosixPath('.../pytest-0/popen-gw3/test_a_home_outside_the_limits0')

.../manipulators/piper/test_connection.py:421: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../manipulators/piper/connection.py:190: in start
    super().start()
        __class__  = <class 'dimos.robot.manipulators.piper.connection.PiperConnection'>
        self       = <dimos.robot.manipulators.piper.connection.PiperConnection object at 0x3415408b0>
.../control/connection/connection_module.py:184: in start
    self._limits = limits_of(self.config)
        __class__  = <class 'dimos.control.connection.connection_module.ConnectionModule'>
        self       = <dimos.robot.manipulators.piper.connection.PiperConnection object at 0x3415408b0>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

config = PiperConfig(rpc_transport=<class 'dimos.protocol.rpc.zenohrpc.ZenohRPC'>, default_rpc_timeout=120.0, rpc_timeouts={'bu...n_port='can9', setup_can=True, gripper=False, speed_percent=50, home=(10.0, 0.0, 0.0, 0.0, 0.0, 0.0), jump_limit_s=0.1)

    def limits(config: ConnectionConfig) -> dict[str, Limits]:
        """Every key a robot accepts, with how far each may go, from its config.
    
        Only declared interfaces get keys, in model order. Declare PD_INTERFACES
        for joints driven by position, velocity, effort and gains together. Bounds
        come from the model; commands outside these bounds are rejected.
    
        Raises:
            ValueError: If joints are commanded without a model, no joint of the
                model moves, the model lacks a bound a command needs, or the
                config commands nothing.
        """
        source = config.source
        if not config.joint_commands:
            raise ValueError(f"{source!r} is told nothing: set joint_commands")
        if config.model is None:
            raise ValueError(f"{source!r} commands joints but has no model: set `model`")
        joints = {joint.name: joint for joint in movable_joints(config.model)}
        if not joints:
            raise ValueError(f"{source!r} has no joints to drive: none in its model moves")
>       unknown = config.joint_commands.keys() - joints.keys()
E       AttributeError: 'tuple' object has no attribute 'keys'

config     = PiperConfig(rpc_transport=<class 'dimos.protocol.rpc.zenohrpc.ZenohRPC'>, default_rpc_timeout=120.0, rpc_timeouts={'bu...n_port='can9', setup_can=True, gripper=False, speed_percent=50, home=(10.0, 0.0, 0.0, 0.0, 0.0, 0.0), jump_limit_s=0.1)
joints     = {'gripper': JointDescription(name='gripper', type='prismatic', parent_link='link6', child_link='gripper_link', origin_....0, 0.0), lower=-2.0, upper=2.0, velocity=3.0, acceleration=None, effort=100.0, axis=(1.0, 0.0, 0.0), mimic=None), ...}
source     = 'arm'

.../control/contract/limits.py:98: AttributeError
dimos.robot.manipulators.piper.test_connection::test_an_arm_with_a_fault_is_not_sent_home_when_stopping
Stack Traces | 0.004s run time
rig = <function rig.<locals>.build at 0x3413f0d30>

    def test_an_arm_with_a_fault_is_not_sent_home_when_stopping(rig: Callable[..., Rig]) -> None:
>       r = rig()

rig        = <function rig.<locals>.build at 0x3413f0d30>

.../manipulators/piper/test_connection.py:428: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../manipulators/piper/test_connection.py:231: in build
    module.start()
        fake       = <dimos.robot.manipulators.piper.test_connection.FakePiper object at 0x34149b370>
        made       = [<dimos.robot.manipulators.piper.test_connection.Rig object at 0x341499fc0>]
        module     = <dimos.robot.manipulators.piper.connection.PiperConnection object at 0x341499960>
        monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x3414996c0>
        sdk        = None
        tmp_path   = PosixPath('.../pytest-0/popen-gw3/test_an_arm_with_a_fault_is_no0')
.../manipulators/piper/connection.py:190: in start
    super().start()
        __class__  = <class 'dimos.robot.manipulators.piper.connection.PiperConnection'>
        self       = <dimos.robot.manipulators.piper.connection.PiperConnection object at 0x341499960>
.../control/connection/connection_module.py:184: in start
    self._limits = limits_of(self.config)
        __class__  = <class 'dimos.control.connection.connection_module.ConnectionModule'>
        self       = <dimos.robot.manipulators.piper.connection.PiperConnection object at 0x341499960>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

config = PiperConfig(rpc_transport=<class 'dimos.protocol.rpc.zenohrpc.ZenohRPC'>, default_rpc_timeout=120.0, rpc_timeouts={'bu...can_port='can9', setup_can=True, gripper=True, speed_percent=50, home=(0.0, 0.0, 0.0, 0.0, 0.0, 0.0), jump_limit_s=0.1)

    def limits(config: ConnectionConfig) -> dict[str, Limits]:
        """Every key a robot accepts, with how far each may go, from its config.
    
        Only declared interfaces get keys, in model order. Declare PD_INTERFACES
        for joints driven by position, velocity, effort and gains together. Bounds
        come from the model; commands outside these bounds are rejected.
    
        Raises:
            ValueError: If joints are commanded without a model, no joint of the
                model moves, the model lacks a bound a command needs, or the
                config commands nothing.
        """
        source = config.source
        if not config.joint_commands:
            raise ValueError(f"{source!r} is told nothing: set joint_commands")
        if config.model is None:
            raise ValueError(f"{source!r} commands joints but has no model: set `model`")
        joints = {joint.name: joint for joint in movable_joints(config.model)}
        if not joints:
            raise ValueError(f"{source!r} has no joints to drive: none in its model moves")
>       unknown = config.joint_commands.keys() - joints.keys()
E       AttributeError: 'tuple' object has no attribute 'keys'

config     = PiperConfig(rpc_transport=<class 'dimos.protocol.rpc.zenohrpc.ZenohRPC'>, default_rpc_timeout=120.0, rpc_timeouts={'bu...can_port='can9', setup_can=True, gripper=True, speed_percent=50, home=(0.0, 0.0, 0.0, 0.0, 0.0, 0.0), jump_limit_s=0.1)
joints     = {'gripper': JointDescription(name='gripper', type='prismatic', parent_link='link6', child_link='gripper_link', origin_....0, 0.0), lower=-2.0, upper=2.0, velocity=3.0, acceleration=None, effort=100.0, axis=(1.0, 0.0, 0.0), mimic=None), ...}
source     = 'arm'

.../control/contract/limits.py:98: AttributeError
dimos.robot.manipulators.piper.test_connection::test_a_silent_arm_is_not_reported_and_takes_no_joint_target
Stack Traces | 0.005s run time
rig = <function rig.<locals>.build at 0x3413ac940>
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x34149a7d0>

    def test_a_silent_arm_is_not_reported_and_takes_no_joint_target(
        rig: Callable[..., Rig], monkeypatch: pytest.MonkeyPatch
    ) -> None:
>       r = rig()

monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x34149a7d0>
rig        = <function rig.<locals>.build at 0x3413ac940>

.../manipulators/piper/test_connection.py:366: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../manipulators/piper/test_connection.py:231: in build
    module.start()
        fake       = <dimos.robot.manipulators.piper.test_connection.FakePiper object at 0x341499240>
        made       = [<dimos.robot.manipulators.piper.test_connection.Rig object at 0x341499ed0>]
        module     = <dimos.robot.manipulators.piper.connection.PiperConnection object at 0x341499b10>
        monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x34149a7d0>
        sdk        = None
        tmp_path   = PosixPath('.../pytest-0/popen-gw3/test_a_silent_arm_is_not_repor0')
.../manipulators/piper/connection.py:190: in start
    super().start()
        __class__  = <class 'dimos.robot.manipulators.piper.connection.PiperConnection'>
        self       = <dimos.robot.manipulators.piper.connection.PiperConnection object at 0x341499b10>
.../control/connection/connection_module.py:184: in start
    self._limits = limits_of(self.config)
        __class__  = <class 'dimos.control.connection.connection_module.ConnectionModule'>
        self       = <dimos.robot.manipulators.piper.connection.PiperConnection object at 0x341499b10>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

config = PiperConfig(rpc_transport=<class 'dimos.protocol.rpc.zenohrpc.ZenohRPC'>, default_rpc_timeout=120.0, rpc_timeouts={'bu...can_port='can9', setup_can=True, gripper=True, speed_percent=50, home=(0.0, 0.0, 0.0, 0.0, 0.0, 0.0), jump_limit_s=0.1)

    def limits(config: ConnectionConfig) -> dict[str, Limits]:
        """Every key a robot accepts, with how far each may go, from its config.
    
        Only declared interfaces get keys, in model order. Declare PD_INTERFACES
        for joints driven by position, velocity, effort and gains together. Bounds
        come from the model; commands outside these bounds are rejected.
    
        Raises:
            ValueError: If joints are commanded without a model, no joint of the
                model moves, the model lacks a bound a command needs, or the
                config commands nothing.
        """
        source = config.source
        if not config.joint_commands:
            raise ValueError(f"{source!r} is told nothing: set joint_commands")
        if config.model is None:
            raise ValueError(f"{source!r} commands joints but has no model: set `model`")
        joints = {joint.name: joint for joint in movable_joints(config.model)}
        if not joints:
            raise ValueError(f"{source!r} has no joints to drive: none in its model moves")
>       unknown = config.joint_commands.keys() - joints.keys()
E       AttributeError: 'tuple' object has no attribute 'keys'

config     = PiperConfig(rpc_transport=<class 'dimos.protocol.rpc.zenohrpc.ZenohRPC'>, default_rpc_timeout=120.0, rpc_timeouts={'bu...can_port='can9', setup_can=True, gripper=True, speed_percent=50, home=(0.0, 0.0, 0.0, 0.0, 0.0, 0.0), jump_limit_s=0.1)
joints     = {'gripper': JointDescription(name='gripper', type='prismatic', parent_link='link6', child_link='gripper_link', origin_....0, 0.0), lower=-2.0, upper=2.0, velocity=3.0, acceleration=None, effort=100.0, axis=(1.0, 0.0, 0.0), mimic=None), ...}
source     = 'arm'

.../control/contract/limits.py:98: AttributeError
dimos.robot.manipulators.piper.test_connection::test_a_target_past_the_jump_guard_is_refused_and_nothing_reaches_the_sdk
Stack Traces | 0.005s run time
rig = <function rig.<locals>.build at 0x341309b40>

    def test_a_target_past_the_jump_guard_is_refused_and_nothing_reaches_the_sdk(
        rig: Callable[..., Rig],
    ) -> None:
>       r = rig()

rig        = <function rig.<locals>.build at 0x341309b40>

.../manipulators/piper/test_connection.py:322: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../manipulators/piper/test_connection.py:231: in build
    module.start()
        fake       = <dimos.robot.manipulators.piper.test_connection.FakePiper object at 0x3412d8550>
        made       = [<dimos.robot.manipulators.piper.test_connection.Rig object at 0x3412da650>]
        module     = <dimos.robot.manipulators.piper.connection.PiperConnection object at 0x3412d8d90>
        monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x3412da890>
        sdk        = None
        tmp_path   = PosixPath('.../pytest-0/popen-gw3/test_a_target_past_the_jump_gu0')
.../manipulators/piper/connection.py:190: in start
    super().start()
        __class__  = <class 'dimos.robot.manipulators.piper.connection.PiperConnection'>
        self       = <dimos.robot.manipulators.piper.connection.PiperConnection object at 0x3412d8d90>
.../control/connection/connection_module.py:184: in start
    self._limits = limits_of(self.config)
        __class__  = <class 'dimos.control.connection.connection_module.ConnectionModule'>
        self       = <dimos.robot.manipulators.piper.connection.PiperConnection object at 0x3412d8d90>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

config = PiperConfig(rpc_transport=<class 'dimos.protocol.rpc.zenohrpc.ZenohRPC'>, default_rpc_timeout=120.0, rpc_timeouts={'bu...can_port='can9', setup_can=True, gripper=True, speed_percent=50, home=(0.0, 0.0, 0.0, 0.0, 0.0, 0.0), jump_limit_s=0.1)

    def limits(config: ConnectionConfig) -> dict[str, Limits]:
        """Every key a robot accepts, with how far each may go, from its config.
    
        Only declared interfaces get keys, in model order. Declare PD_INTERFACES
        for joints driven by position, velocity, effort and gains together. Bounds
        come from the model; commands outside these bounds are rejected.
    
        Raises:
            ValueError: If joints are commanded without a model, no joint of the
                model moves, the model lacks a bound a command needs, or the
                config commands nothing.
        """
        source = config.source
        if not config.joint_commands:
            raise ValueError(f"{source!r} is told nothing: set joint_commands")
        if config.model is None:
            raise ValueError(f"{source!r} commands joints but has no model: set `model`")
        joints = {joint.name: joint for joint in movable_joints(config.model)}
        if not joints:
            raise ValueError(f"{source!r} has no joints to drive: none in its model moves")
>       unknown = config.joint_commands.keys() - joints.keys()
E       AttributeError: 'tuple' object has no attribute 'keys'

config     = PiperConfig(rpc_transport=<class 'dimos.protocol.rpc.zenohrpc.ZenohRPC'>, default_rpc_timeout=120.0, rpc_timeouts={'bu...can_port='can9', setup_can=True, gripper=True, speed_percent=50, home=(0.0, 0.0, 0.0, 0.0, 0.0, 0.0), jump_limit_s=0.1)
joints     = {'gripper': JointDescription(name='gripper', type='prismatic', parent_link='link6', child_link='gripper_link', origin_....0, 0.0), lower=-2.0, upper=2.0, velocity=3.0, acceleration=None, effort=100.0, axis=(1.0, 0.0, 0.0), mimic=None), ...}
source     = 'arm'

.../control/contract/limits.py:98: AttributeError
dimos.robot.manipulators.piper.test_connection::test_an_arm_stopped_outside_dimos_takes_no_command_until_its_errors_are_cleared
Stack Traces | 0.005s run time
rig = <function rig.<locals>.build at 0x3413adcf0>

    def test_an_arm_stopped_outside_dimos_takes_no_command_until_its_errors_are_cleared(
        rig: Callable[..., Rig],
    ) -> None:
>       r = rig()

rig        = <function rig.<locals>.build at 0x3413adcf0>

.../manipulators/piper/test_connection.py:335: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../manipulators/piper/test_connection.py:231: in build
    module.start()
        fake       = <dimos.robot.manipulators.piper.test_connection.FakePiper object at 0x3414f6d40>
        made       = [<dimos.robot.manipulators.piper.test_connection.Rig object at 0x3414f6a40>]
        module     = <dimos.robot.manipulators.piper.connection.PiperConnection object at 0x3414f69b0>
        monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x3414f6d10>
        sdk        = None
        tmp_path   = PosixPath('.../pytest-0/popen-gw3/test_an_arm_stopped_outside_di0')
.../manipulators/piper/connection.py:190: in start
    super().start()
        __class__  = <class 'dimos.robot.manipulators.piper.connection.PiperConnection'>
        self       = <dimos.robot.manipulators.piper.connection.PiperConnection object at 0x3414f69b0>
.../control/connection/connection_module.py:184: in start
    self._limits = limits_of(self.config)
        __class__  = <class 'dimos.control.connection.connection_module.ConnectionModule'>
        self       = <dimos.robot.manipulators.piper.connection.PiperConnection object at 0x3414f69b0>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

config = PiperConfig(rpc_transport=<class 'dimos.protocol.rpc.zenohrpc.ZenohRPC'>, default_rpc_timeout=120.0, rpc_timeouts={'bu...can_port='can9', setup_can=True, gripper=True, speed_percent=50, home=(0.0, 0.0, 0.0, 0.0, 0.0, 0.0), jump_limit_s=0.1)

    def limits(config: ConnectionConfig) -> dict[str, Limits]:
        """Every key a robot accepts, with how far each may go, from its config.
    
        Only declared interfaces get keys, in model order. Declare PD_INTERFACES
        for joints driven by position, velocity, effort and gains together. Bounds
        come from the model; commands outside these bounds are rejected.
    
        Raises:
            ValueError: If joints are commanded without a model, no joint of the
                model moves, the model lacks a bound a command needs, or the
                config commands nothing.
        """
        source = config.source
        if not config.joint_commands:
            raise ValueError(f"{source!r} is told nothing: set joint_commands")
        if config.model is None:
            raise ValueError(f"{source!r} commands joints but has no model: set `model`")
        joints = {joint.name: joint for joint in movable_joints(config.model)}
        if not joints:
            raise ValueError(f"{source!r} has no joints to drive: none in its model moves")
>       unknown = config.joint_commands.keys() - joints.keys()
E       AttributeError: 'tuple' object has no attribute 'keys'

config     = PiperConfig(rpc_transport=<class 'dimos.protocol.rpc.zenohrpc.ZenohRPC'>, default_rpc_timeout=120.0, rpc_timeouts={'bu...can_port='can9', setup_can=True, gripper=True, speed_percent=50, home=(0.0, 0.0, 0.0, 0.0, 0.0, 0.0), jump_limit_s=0.1)
joints     = {'gripper': JointDescription(name='gripper', type='prismatic', parent_link='link6', child_link='gripper_link', origin_....0, 0.0), lower=-2.0, upper=2.0, velocity=3.0, acceleration=None, effort=100.0, axis=(1.0, 0.0, 0.0), mimic=None), ...}
source     = 'arm'

.../control/contract/limits.py:98: AttributeError
dimos.robot.manipulators.piper.test_connection::test_joint_state_names_each_joint_with_its_position_speed_and_torque
Stack Traces | 0.005s run time
rig = <function rig.<locals>.build at 0x341309360>

    def test_joint_state_names_each_joint_with_its_position_speed_and_torque(
        rig: Callable[..., Rig],
    ) -> None:
>       r = rig()

rig        = <function rig.<locals>.build at 0x341309360>

.../manipulators/piper/test_connection.py:278: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../manipulators/piper/test_connection.py:231: in build
    module.start()
        fake       = <dimos.robot.manipulators.piper.test_connection.FakePiper object at 0x3412abeb0>
        made       = [<dimos.robot.manipulators.piper.test_connection.Rig object at 0x3412abf40>]
        module     = <dimos.robot.manipulators.piper.connection.PiperConnection object at 0x3412a9b40>
        monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x3412a8c70>
        sdk        = None
        tmp_path   = PosixPath('.../pytest-0/popen-gw3/test_joint_state_names_each_jo0')
.../manipulators/piper/connection.py:190: in start
    super().start()
        __class__  = <class 'dimos.robot.manipulators.piper.connection.PiperConnection'>
        self       = <dimos.robot.manipulators.piper.connection.PiperConnection object at 0x3412a9b40>
.../control/connection/connection_module.py:184: in start
    self._limits = limits_of(self.config)
        __class__  = <class 'dimos.control.connection.connection_module.ConnectionModule'>
        self       = <dimos.robot.manipulators.piper.connection.PiperConnection object at 0x3412a9b40>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

config = PiperConfig(rpc_transport=<class 'dimos.protocol.rpc.zenohrpc.ZenohRPC'>, default_rpc_timeout=120.0, rpc_timeouts={'bu...can_port='can9', setup_can=True, gripper=True, speed_percent=50, home=(0.0, 0.0, 0.0, 0.0, 0.0, 0.0), jump_limit_s=0.1)

    def limits(config: ConnectionConfig) -> dict[str, Limits]:
        """Every key a robot accepts, with how far each may go, from its config.
    
        Only declared interfaces get keys, in model order. Declare PD_INTERFACES
        for joints driven by position, velocity, effort and gains together. Bounds
        come from the model; commands outside these bounds are rejected.
    
        Raises:
            ValueError: If joints are commanded without a model, no joint of the
                model moves, the model lacks a bound a command needs, or the
                config commands nothing.
        """
        source = config.source
        if not config.joint_commands:
            raise ValueError(f"{source!r} is told nothing: set joint_commands")
        if config.model is None:
            raise ValueError(f"{source!r} commands joints but has no model: set `model`")
        joints = {joint.name: joint for joint in movable_joints(config.model)}
        if not joints:
            raise ValueError(f"{source!r} has no joints to drive: none in its model moves")
>       unknown = config.joint_commands.keys() - joints.keys()
E       AttributeError: 'tuple' object has no attribute 'keys'

config     = PiperConfig(rpc_transport=<class 'dimos.protocol.rpc.zenohrpc.ZenohRPC'>, default_rpc_timeout=120.0, rpc_timeouts={'bu...can_port='can9', setup_can=True, gripper=True, speed_percent=50, home=(0.0, 0.0, 0.0, 0.0, 0.0, 0.0), jump_limit_s=0.1)
joints     = {'gripper': JointDescription(name='gripper', type='prismatic', parent_link='link6', child_link='gripper_link', origin_....0, 0.0), lower=-2.0, upper=2.0, velocity=3.0, acceleration=None, effort=100.0, axis=(1.0, 0.0, 0.0), mimic=None), ...}
source     = 'arm'

.../control/contract/limits.py:98: AttributeError
dimos.robot.manipulators.piper.test_connection::test_joint_targets_go_out_in_thousandths_of_a_degree
Stack Traces | 0.005s run time
rig = <function rig.<locals>.build at 0x3413091b0>

    def test_joint_targets_go_out_in_thousandths_of_a_degree(rig: Callable[..., Rig]) -> None:
>       r = rig()

rig        = <function rig.<locals>.build at 0x3413091b0>

.../manipulators/piper/test_connection.py:269: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../manipulators/piper/test_connection.py:231: in build
    module.start()
        fake       = <dimos.robot.manipulators.piper.test_connection.FakePiper object at 0x341333b80>
        made       = [<dimos.robot.manipulators.piper.test_connection.Rig object at 0x3412a98d0>]
        module     = <dimos.robot.manipulators.piper.connection.PiperConnection object at 0x341333fd0>
        monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x3412a97e0>
        sdk        = None
        tmp_path   = PosixPath('.../pytest-0/popen-gw3/test_joint_targets_go_out_in_t0')
.../manipulators/piper/connection.py:190: in start
    super().start()
        __class__  = <class 'dimos.robot.manipulators.piper.connection.PiperConnection'>
        self       = <dimos.robot.manipulators.piper.connection.PiperConnection object at 0x341333fd0>
.../control/connection/connection_module.py:184: in start
    self._limits = limits_of(self.config)
        __class__  = <class 'dimos.control.connection.connection_module.ConnectionModule'>
        self       = <dimos.robot.manipulators.piper.connection.PiperConnection object at 0x341333fd0>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

config = PiperConfig(rpc_transport=<class 'dimos.protocol.rpc.zenohrpc.ZenohRPC'>, default_rpc_timeout=120.0, rpc_timeouts={'bu...can_port='can9', setup_can=True, gripper=True, speed_percent=50, home=(0.0, 0.0, 0.0, 0.0, 0.0, 0.0), jump_limit_s=0.1)

    def limits(config: ConnectionConfig) -> dict[str, Limits]:
        """Every key a robot accepts, with how far each may go, from its config.
    
        Only declared interfaces get keys, in model order. Declare PD_INTERFACES
        for joints driven by position, velocity, effort and gains together. Bounds
        come from the model; commands outside these bounds are rejected.
    
        Raises:
            ValueError: If joints are commanded without a model, no joint of the
                model moves, the model lacks a bound a command needs, or the
                config commands nothing.
        """
        source = config.source
        if not config.joint_commands:
            raise ValueError(f"{source!r} is told nothing: set joint_commands")
        if config.model is None:
            raise ValueError(f"{source!r} commands joints but has no model: set `model`")
        joints = {joint.name: joint for joint in movable_joints(config.model)}
        if not joints:
            raise ValueError(f"{source!r} has no joints to drive: none in its model moves")
>       unknown = config.joint_commands.keys() - joints.keys()
E       AttributeError: 'tuple' object has no attribute 'keys'

config     = PiperConfig(rpc_transport=<class 'dimos.protocol.rpc.zenohrpc.ZenohRPC'>, default_rpc_timeout=120.0, rpc_timeouts={'bu...can_port='can9', setup_can=True, gripper=True, speed_percent=50, home=(0.0, 0.0, 0.0, 0.0, 0.0, 0.0), jump_limit_s=0.1)
joints     = {'gripper': JointDescription(name='gripper', type='prismatic', parent_link='link6', child_link='gripper_link', origin_....0, 0.0), lower=-2.0, upper=2.0, velocity=3.0, acceleration=None, effort=100.0, axis=(1.0, 0.0, 0.0), mimic=None), ...}
source     = 'arm'

.../control/contract/limits.py:98: AttributeError
dimos.robot.manipulators.piper.test_connection::test_stopping_sends_the_arm_home_then_puts_it_in_its_emergency_stop
Stack Traces | 0.005s run time
rig = <function rig.<locals>.build at 0x3413f2950>

    def test_stopping_sends_the_arm_home_then_puts_it_in_its_emergency_stop(
        rig: Callable[..., Rig],
    ) -> None:
>       r = rig()

rig        = <function rig.<locals>.build at 0x3413f2950>

.../manipulators/piper/test_connection.py:387: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../manipulators/piper/test_connection.py:231: in build
    module.start()
        fake       = <dimos.robot.manipulators.piper.test_connection.FakePiper object at 0x3414453f0>
        made       = [<dimos.robot.manipulators.piper.test_connection.Rig object at 0x341445240>]
        module     = <dimos.robot.manipulators.piper.connection.PiperConnection object at 0x3414452a0>
        monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x3414464d0>
        sdk        = None
        tmp_path   = PosixPath('.../pytest-0/popen-gw3/test_stopping_sends_the_arm_ho0')
.../manipulators/piper/connection.py:190: in start
    super().start()
        __class__  = <class 'dimos.robot.manipulators.piper.connection.PiperConnection'>
        self       = <dimos.robot.manipulators.piper.connection.PiperConnection object at 0x3414452a0>
.../control/connection/connection_module.py:184: in start
    self._limits = limits_of(self.config)
        __class__  = <class 'dimos.control.connection.connection_module.ConnectionModule'>
        self       = <dimos.robot.manipulators.piper.connection.PiperConnection object at 0x3414452a0>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

config = PiperConfig(rpc_transport=<class 'dimos.protocol.rpc.zenohrpc.ZenohRPC'>, default_rpc_timeout=120.0, rpc_timeouts={'bu...can_port='can9', setup_can=True, gripper=True, speed_percent=50, home=(0.0, 0.0, 0.0, 0.0, 0.0, 0.0), jump_limit_s=0.1)

    def limits(config: ConnectionConfig) -> dict[str, Limits]:
        """Every key a robot accepts, with how far each may go, from its config.
    
        Only declared interfaces get keys, in model order. Declare PD_INTERFACES
        for joints driven by position, velocity, effort and gains together. Bounds
        come from the model; commands outside these bounds are rejected.
    
        Raises:
            ValueError: If joints are commanded without a model, no joint of the
                model moves, the model lacks a bound a command needs, or the
                config commands nothing.
        """
        source = config.source
        if not config.joint_commands:
            raise ValueError(f"{source!r} is told nothing: set joint_commands")
        if config.model is None:
            raise ValueError(f"{source!r} commands joints but has no model: set `model`")
        joints = {joint.name: joint for joint in movable_joints(config.model)}
        if not joints:
            raise ValueError(f"{source!r} has no joints to drive: none in its model moves")
>       unknown = config.joint_commands.keys() - joints.keys()
E       AttributeError: 'tuple' object has no attribute 'keys'

config     = PiperConfig(rpc_transport=<class 'dimos.protocol.rpc.zenohrpc.ZenohRPC'>, default_rpc_timeout=120.0, rpc_timeouts={'bu...can_port='can9', setup_can=True, gripper=True, speed_percent=50, home=(0.0, 0.0, 0.0, 0.0, 0.0, 0.0), jump_limit_s=0.1)
joints     = {'gripper': JointDescription(name='gripper', type='prismatic', parent_link='link6', child_link='gripper_link', origin_....0, 0.0), lower=-2.0, upper=2.0, velocity=3.0, acceleration=None, effort=100.0, axis=(1.0, 0.0, 0.0), mimic=None), ...}
source     = 'arm'

.../control/contract/limits.py:98: AttributeError
dimos.robot.manipulators.piper.test_connection::test_teach_mode_stops_every_command
Stack Traces | 0.005s run time
rig = <function rig.<locals>.build at 0x3413095a0>

    def test_teach_mode_stops_every_command(rig: Callable[..., Rig]) -> None:
>       r = rig()

rig        = <function rig.<locals>.build at 0x3413095a0>

.../manipulators/piper/test_connection.py:355: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../manipulators/piper/test_connection.py:231: in build
    module.start()
        fake       = <dimos.robot.manipulators.piper.test_connection.FakePiper object at 0x3415076d0>
        made       = [<dimos.robot.manipulators.piper.test_connection.Rig object at 0x341507190>]
        module     = <dimos.robot.manipulators.piper.connection.PiperConnection object at 0x3415071f0>
        monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x341507340>
        sdk        = None
        tmp_path   = PosixPath('.../pytest-0/popen-gw3/test_teach_mode_stops_every_co0')
.../manipulators/piper/connection.py:190: in start
    super().start()
        __class__  = <class 'dimos.robot.manipulators.piper.connection.PiperConnection'>
        self       = <dimos.robot.manipulators.piper.connection.PiperConnection object at 0x3415071f0>
.../control/connection/connection_module.py:184: in start
    self._limits = limits_of(self.config)
        __class__  = <class 'dimos.control.connection.connection_module.ConnectionModule'>
        self       = <dimos.robot.manipulators.piper.connection.PiperConnection object at 0x3415071f0>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

config = PiperConfig(rpc_transport=<class 'dimos.protocol.rpc.zenohrpc.ZenohRPC'>, default_rpc_timeout=120.0, rpc_timeouts={'bu...can_port='can9', setup_can=True, gripper=True, speed_percent=50, home=(0.0, 0.0, 0.0, 0.0, 0.0, 0.0), jump_limit_s=0.1)

    def limits(config: ConnectionConfig) -> dict[str, Limits]:
        """Every key a robot accepts, with how far each may go, from its config.
    
        Only declared interfaces get keys, in model order. Declare PD_INTERFACES
        for joints driven by position, velocity, effort and gains together. Bounds
        come from the model; commands outside these bounds are rejected.
    
        Raises:
            ValueError: If joints are commanded without a model, no joint of the
                model moves, the model lacks a bound a command needs, or the
                config commands nothing.
        """
        source = config.source
        if not config.joint_commands:
            raise ValueError(f"{source!r} is told nothing: set joint_commands")
        if config.model is None:
            raise ValueError(f"{source!r} commands joints but has no model: set `model`")
        joints = {joint.name: joint for joint in movable_joints(config.model)}
        if not joints:
            raise ValueError(f"{source!r} has no joints to drive: none in its model moves")
>       unknown = config.joint_commands.keys() - joints.keys()
E       AttributeError: 'tuple' object has no attribute 'keys'

config     = PiperConfig(rpc_transport=<class 'dimos.protocol.rpc.zenohrpc.ZenohRPC'>, default_rpc_timeout=120.0, rpc_timeouts={'bu...can_port='can9', setup_can=True, gripper=True, speed_percent=50, home=(0.0, 0.0, 0.0, 0.0, 0.0, 0.0), jump_limit_s=0.1)
joints     = {'gripper': JointDescription(name='gripper', type='prismatic', parent_link='link6', child_link='gripper_link', origin_....0, 0.0), lower=-2.0, upper=2.0, velocity=3.0, acceleration=None, effort=100.0, axis=(1.0, 0.0, 0.0), mimic=None), ...}
source     = 'arm'

.../control/contract/limits.py:98: AttributeError
dimos.robot.manipulators.piper.test_connection::test_the_gripper_goes_out_as_its_gap_with_every_command
Stack Traces | 0.005s run time
rig = <function rig.<locals>.build at 0x341309cf0>

    def test_the_gripper_goes_out_as_its_gap_with_every_command(rig: Callable[..., Rig]) -> None:
>       r = rig()

rig        = <function rig.<locals>.build at 0x341309cf0>

.../manipulators/piper/test_connection.py:295: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../manipulators/piper/test_connection.py:231: in build
    module.start()
        fake       = <dimos.robot.manipulators.piper.test_connection.FakePiper object at 0x3412a9210>
        made       = [<dimos.robot.manipulators.piper.test_connection.Rig object at 0x3412a8d00>]
        module     = <dimos.robot.manipulators.piper.connection.PiperConnection object at 0x3412a8910>
        monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x3412a8d90>
        sdk        = None
        tmp_path   = PosixPath('.../pytest-0/popen-gw3/test_the_gripper_goes_out_as_i0')
.../manipulators/piper/connection.py:190: in start
    super().start()
        __class__  = <class 'dimos.robot.manipulators.piper.connection.PiperConnection'>
        self       = <dimos.robot.manipulators.piper.connection.PiperConnection object at 0x3412a8910>
.../control/connection/connection_module.py:184: in start
    self._limits = limits_of(self.config)
        __class__  = <class 'dimos.control.connection.connection_module.ConnectionModule'>
        self       = <dimos.robot.manipulators.piper.connection.PiperConnection object at 0x3412a8910>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

config = PiperConfig(rpc_transport=<class 'dimos.protocol.rpc.zenohrpc.ZenohRPC'>, default_rpc_timeout=120.0, rpc_timeouts={'bu...can_port='can9', setup_can=True, gripper=True, speed_percent=50, home=(0.0, 0.0, 0.0, 0.0, 0.0, 0.0), jump_limit_s=0.1)

    def limits(config: ConnectionConfig) -> dict[str, Limits]:
        """Every key a robot accepts, with how far each may go, from its config.
    
        Only declared interfaces get keys, in model order. Declare PD_INTERFACES
        for joints driven by position, velocity, effort and gains together. Bounds
        come from the model; commands outside these bounds are rejected.
    
        Raises:
            ValueError: If joints are commanded without a model, no joint of the
                model moves, the model lacks a bound a command needs, or the
                config commands nothing.
        """
        source = config.source
        if not config.joint_commands:
            raise ValueError(f"{source!r} is told nothing: set joint_commands")
        if config.model is None:
            raise ValueError(f"{source!r} commands joints but has no model: set `model`")
        joints = {joint.name: joint for joint in movable_joints(config.model)}
        if not joints:
            raise ValueError(f"{source!r} has no joints to drive: none in its model moves")
>       unknown = config.joint_commands.keys() - joints.keys()
E       AttributeError: 'tuple' object has no attribute 'keys'

config     = PiperConfig(rpc_transport=<class 'dimos.protocol.rpc.zenohrpc.ZenohRPC'>, default_rpc_timeout=120.0, rpc_timeouts={'bu...can_port='can9', setup_can=True, gripper=True, speed_percent=50, home=(0.0, 0.0, 0.0, 0.0, 0.0, 0.0), jump_limit_s=0.1)
joints     = {'gripper': JointDescription(name='gripper', type='prismatic', parent_link='link6', child_link='gripper_link', origin_....0, 0.0), lower=-2.0, upper=2.0, velocity=3.0, acceleration=None, effort=100.0, axis=(1.0, 0.0, 0.0), mimic=None), ...}
source     = 'arm'

.../control/contract/limits.py:98: AttributeError
dimos.robot.manipulators.piper.test_connection::test_the_gripper_is_read_in_metres_in_a_message_of_its_own
Stack Traces | 0.005s run time
rig = <function rig.<locals>.build at 0x3413afd90>

    def test_the_gripper_is_read_in_metres_in_a_message_of_its_own(rig: Callable[..., Rig]) -> None:
>       r = rig()

rig        = <function rig.<locals>.build at 0x3413afd90>

.../manipulators/piper/test_connection.py:312: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../manipulators/piper/test_connection.py:231: in build
    module.start()
        fake       = <dimos.robot.manipulators.piper.test_connection.FakePiper object at 0x3412da290>
        made       = [<dimos.robot.manipulators.piper.test_connection.Rig object at 0x3412d9e70>]
        module     = <dimos.robot.manipulators.piper.connection.PiperConnection object at 0x3412da080>
        monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x3412d9c60>
        sdk        = None
        tmp_path   = PosixPath('.../pytest-0/popen-gw3/test_the_gripper_is_read_in_me0')
.../manipulators/piper/connection.py:190: in start
    super().start()
        __class__  = <class 'dimos.robot.manipulators.piper.connection.PiperConnection'>
        self       = <dimos.robot.manipulators.piper.connection.PiperConnection object at 0x3412da080>
.../control/connection/connection_module.py:184: in start
    self._limits = limits_of(self.config)
        __class__  = <class 'dimos.control.connection.connection_module.ConnectionModule'>
        self       = <dimos.robot.manipulators.piper.connection.PiperConnection object at 0x3412da080>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

config = PiperConfig(rpc_transport=<class 'dimos.protocol.rpc.zenohrpc.ZenohRPC'>, default_rpc_timeout=120.0, rpc_timeouts={'bu...can_port='can9', setup_can=True, gripper=True, speed_percent=50, home=(0.0, 0.0, 0.0, 0.0, 0.0, 0.0), jump_limit_s=0.1)

    def limits(config: ConnectionConfig) -> dict[str, Limits]:
        """Every key a robot accepts, with how far each may go, from its config.
    
        Only declared interfaces get keys, in model order. Declare PD_INTERFACES
        for joints driven by position, velocity, effort and gains together. Bounds
        come from the model; commands outside these bounds are rejected.
    
        Raises:
            ValueError: If joints are commanded without a model, no joint of the
                model moves, the model lacks a bound a command needs, or the
                config commands nothing.
        """
        source = config.source
        if not config.joint_commands:
            raise ValueError(f"{source!r} is told nothing: set joint_commands")
        if config.model is None:
            raise ValueError(f"{source!r} commands joints but has no model: set `model`")
        joints = {joint.name: joint for joint in movable_joints(config.model)}
        if not joints:
            raise ValueError(f"{source!r} has no joints to drive: none in its model moves")
>       unknown = config.joint_commands.keys() - joints.keys()
E       AttributeError: 'tuple' object has no attribute 'keys'

config     = PiperConfig(rpc_transport=<class 'dimos.protocol.rpc.zenohrpc.ZenohRPC'>, default_rpc_timeout=120.0, rpc_timeouts={'bu...can_port='can9', setup_can=True, gripper=True, speed_percent=50, home=(0.0, 0.0, 0.0, 0.0, 0.0, 0.0), jump_limit_s=0.1)
joints     = {'gripper': JointDescription(name='gripper', type='prismatic', parent_link='link6', child_link='gripper_link', origin_....0, 0.0), lower=-2.0, upper=2.0, velocity=3.0, acceleration=None, effort=100.0, axis=(1.0, 0.0, 0.0), mimic=None), ...}
source     = 'arm'

.../control/contract/limits.py:98: AttributeError
dimos.robot.manipulators.piper.test_connection::test_start_resets_the_arm_turns_its_motors_on_and_sends_it_home
Stack Traces | 0.007s run time
rig = <function rig.<locals>.build at 0x3413081f0>

    def test_start_resets_the_arm_turns_its_motors_on_and_sends_it_home(
        rig: Callable[..., Rig],
    ) -> None:
        sdk = FakePiper(arm_status=COLLISION)
        sdk.positions = [10_000, 0, 0, 0, 0, 0]
>       r = rig(sdk)

rig        = <function rig.<locals>.build at 0x3413081f0>
sdk        = <dimos.robot.manipulators.piper.test_connection.FakePiper object at 0x341332dd0>

.../manipulators/piper/test_connection.py:244: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../manipulators/piper/test_connection.py:231: in build
    module.start()
        fake       = <dimos.robot.manipulators.piper.test_connection.FakePiper object at 0x341332dd0>
        made       = [<dimos.robot.manipulators.piper.test_connection.Rig object at 0x341333250>]
        module     = <dimos.robot.manipulators.piper.connection.PiperConnection object at 0x341333280>
        monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x341333430>
        sdk        = <dimos.robot.manipulators.piper.test_connection.FakePiper object at 0x341332dd0>
        tmp_path   = PosixPath('.../pytest-0/popen-gw3/test_start_resets_the_arm_turn0')
.../manipulators/piper/connection.py:190: in start
    super().start()
        __class__  = <class 'dimos.robot.manipulators.piper.connection.PiperConnection'>
        self       = <dimos.robot.manipulators.piper.connection.PiperConnection object at 0x341333280>
.../control/connection/connection_module.py:184: in start
    self._limits = limits_of(self.config)
        __class__  = <class 'dimos.control.connection.connection_module.ConnectionModule'>
        self       = <dimos.robot.manipulators.piper.connection.PiperConnection object at 0x341333280>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

config = PiperConfig(rpc_transport=<class 'dimos.protocol.rpc.zenohrpc.ZenohRPC'>, default_rpc_timeout=120.0, rpc_timeouts={'bu...can_port='can9', setup_can=True, gripper=True, speed_percent=50, home=(0.0, 0.0, 0.0, 0.0, 0.0, 0.0), jump_limit_s=0.1)

    def limits(config: ConnectionConfig) -> dict[str, Limits]:
        """Every key a robot accepts, with how far each may go, from its config.
    
        Only declared interfaces get keys, in model order. Declare PD_INTERFACES
        for joints driven by position, velocity, effort and gains together. Bounds
        come from the model; commands outside these bounds are rejected.
    
        Raises:
            ValueError: If joints are commanded without a model, no joint of the
                model moves, the model lacks a bound a command needs, or the
                config commands nothing.
        """
        source = config.source
        if not config.joint_commands:
            raise ValueError(f"{source!r} is told nothing: set joint_commands")
        if config.model is None:
            raise ValueError(f"{source!r} commands joints but has no model: set `model`")
        joints = {joint.name: joint for joint in movable_joints(config.model)}
        if not joints:
            raise ValueError(f"{source!r} has no joints to drive: none in its model moves")
>       unknown = config.joint_commands.keys() - joints.keys()
E       AttributeError: 'tuple' object has no attribute 'keys'

config     = PiperConfig(rpc_transport=<class 'dimos.protocol.rpc.zenohrpc.ZenohRPC'>, default_rpc_timeout=120.0, rpc_timeouts={'bu...can_port='can9', setup_can=True, gripper=True, speed_percent=50, home=(0.0, 0.0, 0.0, 0.0, 0.0, 0.0), jump_limit_s=0.1)
joints     = {'gripper': JointDescription(name='gripper', type='prismatic', parent_link='link6', child_link='gripper_link', origin_....0, 0.0), lower=-2.0, upper=2.0, velocity=3.0, acceleration=None, effort=100.0, axis=(1.0, 0.0, 0.0), mimic=None), ...}
source     = 'arm'

.../control/contract/limits.py:98: AttributeError
dimos.codebase_checks.test_no_init_files::test_no_init_files
Stack Traces | 0.041s run time
def test_no_init_files():
        dimos_dir = DIMOS_PROJECT_ROOT / "dimos"
        init_files = sorted(dimos_dir.rglob("__init__.py"))
        # The root dimos/__init__.py is allowed for the porcelain lazy import.
        init_files = [f for f in init_files if f != dimos_dir / "__init__.py"]
        if init_files:
            listing = "\n".join(f"  - {f.relative_to(dimos_dir)}" for f in init_files)
>           raise AssertionError(
                f"Found __init__.py files in dimos/:\n{listing}\n\n"
                "__init__.py files are not allowed because they lead to unnecessary "
                "extraneous imports. Everything should be imported straight from the "
                "source module."
            )
E           AssertionError: Found __init__.py files in dimos/:
E             - .../tasks/r1pro_torso_height_task/__init__.py
E           
E           __init__.py files are not allowed because they lead to unnecessary extraneous imports. Everything should be imported straight from the source module.

dimos_dir  = PosixPath('.../dimos/dimos/dimos')
init_files = [PosixPath('.../dimos/dimos/dimos/.../tasks/r1pro_torso_height_task/__init__.py')]
listing    = '  - .../tasks/r1pro_torso_height_task/__init__.py'

dimos/codebase_checks/test_no_init_files.py:25: AssertionError
dimos.control.benchmarking.test_benchmark::test_end_to_end_controller_benchmark_scoring
Stack Traces | 0.068s run time
tmp_path = PosixPath('.../pytest-0/popen-gw0/test_end_to_end_controller_ben0')

    def test_end_to_end_controller_benchmark_scoring(tmp_path):
        """Prove the full decoupled chain in-process against the in-repo FOPDT sim:
        the benchmark publishes a path+speed (here driven directly), the RPP follower
        tracks it against TwistBasePlantSim, the benchmark records the executed odom,
        odom-based completion fires, a run is written, and the offline scorer emits
        metrics."""
        art = TuningConfig.from_json(DEFAULT_ARTIFACT_PATH)
        task = create_task(
            SimpleNamespace(
                name="rpp_follower", joint_names=_JOINTS, priority=10, params={"speed": 0.5}
            ),
            None,
        )
        plant = TwistBasePlantSim(
            TwistBasePlantParams(
                vx=FopdtChannelParams(art.plant.vx.K, art.plant.vx.tau, art.plant.vx.L),
                vy=FopdtChannelParams(art.plant.vy.K, art.plant.vy.tau, art.plant.vy.L),
                wz=FopdtChannelParams(art.plant.wz.K, art.plant.wz.tau, art.plant.wz.L),
            )
        )
        plant.reset(0.0, 0.0, 0.0, 0.1)
    
        # Benchmark anchors the path to the robot's first odom (here the origin).
        ref = shift_path_to_start_at_pose(straight_line(length=2.0), _pose(plant.x, plant.y, plant.yaw))
        task.start_path(ref, _pose(plant.x, plant.y, plant.yaw))
    
        recorder = OdomRecorder()
        monitor = _monitor(ref, dwell_s=0.3)
        done = False
        for k in range(800):
            out = task.compute(_state(plant.x, plant.y, plant.yaw, t=k * 0.1))
            cvx, cvy, cwz = out.velocities if out is not None else (0.0, 0.0, 0.0)
            # Benchmark records what it sees over the transport: cmd_vel + odom.
            recorder.on_cmd_vel(Twist(linear=Vector3(cvx, cvy, 0.0), angular=Vector3(0.0, 0.0, cwz)))
            recorder.on_odom(_pose(plant.x, plant.y, plant.yaw), now=k * 0.1)
            lin, ang = recorder.body_speed()
            if monitor.update(plant.x, plant.y, lin, ang, k * 0.1):
                done = True
                break
            plant.step(cvx, cvy, cwz, 0.1)
>       assert done, "odom-based completion never fired"
E       AssertionError: odom-based completion never fired
E       assert False

ang        = 0.0
art        = TuningConfig(provenance=Provenance(robot_id='go2', surface='concrete', mode='default', date='2026-06-23', git_sha='972...3, L=0.15, r2=0.9999577115547055, source='sweep')]), floor_probe_results=None, valid_for_tuning=True, schema_version=1)
cvx        = 0.0
cvy        = 0.0
cwz        = 0.0
done       = False
k          = 799
lin        = 0.0
monitor    = <dimos.control.benchmarking.benchmark.CompletionMonitor object at 0x179571120>
out        = None
plant      = <dimos.control.benchmarking.plant.TwistBasePlantSim object at 0x177ca01c0>
recorder   = <dimos.control.benchmarking.benchmark.OdomRecorder object at 0x179572620>
ref        = <dimos.msgs.nav_msgs.Path.Path object at 0x177ca32b0>
task       = <dimos.control.tasks.rpp_path_follower_task.rpp_path_follower_task.RPPPathFollowerTask object at 0x177ca2590>
tmp_path   = PosixPath('.../pytest-0/popen-gw0/test_end_to_end_controller_ben0')

.../control/benchmarking/test_benchmark.py:326: AssertionError
dimos.control.benchmarking.test_benchmark::test_end_to_end_fullpose_benchmark_scoring
Stack Traces | 0.07s run time
tmp_path = PosixPath('.../pytest-0/popen-gw0/test_end_to_end_fullpose_bench0')

    def test_end_to_end_fullpose_benchmark_scoring(tmp_path):
        """Same decoupled chain, full-pose flavor: the holonomic follower tracks a
        translate-while-rotating path against the FOPDT sim, completion fires from
        odom, and the offline scorer reports heading error against the COMMANDED
        yaw (which the tangent-facing followers could not keep small)."""
        from dimos.control.tasks.holonomic_pose_follower_task.holonomic_pose_follower_task import (
            create_task as create_holonomic_task,
        )
    
        art = TuningConfig.from_json(DEFAULT_ARTIFACT_PATH)
        task = create_holonomic_task(
            SimpleNamespace(
                name="holonomic_follower", joint_names=_JOINTS, priority=10, params={"speed": 0.5}
            ),
            None,
        )
        plant = TwistBasePlantSim(
            TwistBasePlantParams(
                vx=FopdtChannelParams(art.plant.vx.K, art.plant.vx.tau, art.plant.vx.L),
                vy=FopdtChannelParams(art.plant.vy.K, art.plant.vy.tau, art.plant.vy.L),
                wz=FopdtChannelParams(art.plant.wz.K, art.plant.wz.tau, art.plant.wz.L),
            )
        )
        plant.reset(0.0, 0.0, 0.0, 0.1)
    
        ref = shift_path_to_start_at_pose(
            straight_rotate(length=3.0), _pose(plant.x, plant.y, plant.yaw)
        )
        task.start_path(ref, _pose(plant.x, plant.y, plant.yaw))
    
        recorder = OdomRecorder()
        monitor = _monitor(ref, dwell_s=0.3)
        done = False
        for k in range(800):
            out = task.compute(_state(plant.x, plant.y, plant.yaw, t=k * 0.1))
            cvx, cvy, cwz = out.velocities if out is not None else (0.0, 0.0, 0.0)
            recorder.on_cmd_vel(Twist(linear=Vector3(cvx, cvy, 0.0), angular=Vector3(0.0, 0.0, cwz)))
            recorder.on_odom(_pose(plant.x, plant.y, plant.yaw), now=k * 0.1)
            lin, ang = recorder.body_speed()
            if monitor.update(plant.x, plant.y, lin, ang, k * 0.1):
                done = True
                break
            plant.step(cvx, cvy, cwz, 0.1)
>       assert done, "odom-based completion never fired"
E       AssertionError: odom-based completion never fired
E       assert False

ang        = 0.0
art        = TuningConfig(provenance=Provenance(robot_id='go2', surface='concrete', mode='default', date='2026-06-23', git_sha='972...3, L=0.15, r2=0.9999577115547055, source='sweep')]), floor_probe_results=None, valid_for_tuning=True, schema_version=1)
create_holonomic_task = <function create_task at 0x147efe320>
cvx        = 0.0
cvy        = 0.0
cwz        = 0.0
done       = False
k          = 799
lin        = 0.0
monitor    = <dimos.control.benchmarking.benchmark.CompletionMonitor object at 0x1796f0cd0>
out        = JointCommandOutput(joint_names=['go2/vx', 'go2/vy', 'go2/wz'], positions=None, velocities=[0.0, 0.0, 0.0], efforts=None, mode=<ControlMode.VELOCITY: 'velocity'>)
plant      = <dimos.control.benchmarking.plant.TwistBasePlantSim object at 0x1796f0490>
recorder   = <dimos.control.benchmarking.benchmark.OdomRecorder object at 0x1796f1f60>
ref        = <dimos.msgs.nav_msgs.Path.Path object at 0x1796f3700>
task       = <dimos.control.tasks.holonomic_pose_follower_task.holonomic_pose_follower_task.HolonomicPoseFollowerTask object at 0x1796f2080>
tmp_path   = PosixPath('.../pytest-0/popen-gw0/test_end_to_end_fullpose_bench0')

.../control/benchmarking/test_benchmark.py:396: AssertionError
dimos.robot.galaxea.r1pro.blueprints.teleop.test_r1pro_vr_teleop::test_thumbstick_drives_the_chassis_through_the_velocity_task
Stack Traces | 5.55s run time
def test_thumbstick_drives_the_chassis_through_the_velocity_task() -> None:
        """Left stick forward → Twist on the bus → vel_chassis → cmd_vel → the fake.
    
        The base actually moves in the simulated model, so this is the whole
        chain, not a conversion function.
        """
        with Chain() as chain:
            chain.send_sticks(left_y=-1.0)  # pushed fully forward
            wait_until(
                lambda: chain.base_state()["vx"] == pytest.approx(1.0), timeout=5.0, interval=0.02
            )
>           wait_until(lambda: chain.base_state()["x"] > 0.05, timeout=5.0, interval=0.02)

chain      = <dimos.robot.galaxea.r1pro.blueprints.teleop.test_r1pro_vr_teleop.Chain object at 0x176f993c0>

.../blueprints/teleop/test_r1pro_vr_teleop.py:643: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

predicate = <function test_thumbstick_drives_the_chassis_through_the_velocity_task.<locals>.<lambda> at 0x176f4f5b0>

    def wait_until(
        predicate: Callable[[], bool],
        *,
        timeout: float,
        interval: float = 0.1,
        message: str | None = None,
    ) -> None:
        """Poll ``predicate`` until it returns truthy or ``timeout`` elapses."""
        deadline = time.monotonic() + timeout
        while time.monotonic() < deadline:
            if predicate():
                return
            time.sleep(interval)
>       raise TimeoutError(message or f"Timed out after {timeout}s waiting for condition")
E       TimeoutError: Timed out after 5.0s waiting for condition

deadline   = 458.677797875
interval   = 0.02
message    = None
predicate  = <function test_thumbstick_drives_the_chassis_through_the_velocity_task.<locals>.<lambda> at 0x176f4f5b0>
timeout    = 5.0

.../utils/testing/waiting.py:35: TimeoutError
dimos.robot.galaxea.r1pro.blueprints.teleop.test_r1pro_vr_teleop::test_estop_stops_the_chassis_and_clearing_it_does_not_resume_motion
Stack Traces | 6.27s run time
def test_estop_stops_the_chassis_and_clearing_it_does_not_resume_motion() -> None:
        with Chain(deadman_s=0.5) as chain:
            chain.send_sticks(left_y=-1.0)
            wait_until(
                lambda: chain.base_state()["vx"] == pytest.approx(1.0), timeout=5.0, interval=0.02
            )
            assert chain.coordinator.set_estop(True)
            sent_at_estop = len(chain.chassis_speeds())
            time.sleep(0.3)
            # While estopped the coordinator sends the connection nothing.
            assert len(chain.chassis_speeds()) == sent_at_estop
            wait_until(lambda: chain.fake.status().deadman_fired, timeout=5.0, interval=0.05)
            assert chain.base_state()["vx"] == pytest.approx(0.0)
            # Clearing E-stop alone must not replay the old velocity. Position
            # hold does not inhibit fresh velocity commands after the clear.
            chain.send_sticks()
            assert chain.coordinator.set_estop(False)
            time.sleep(0.1)
            assert chain.base_state()["vx"] == pytest.approx(0.0)
            chain.send_sticks(left_y=-1.0)
>           wait_until(lambda: chain.base_state()["vx"] == pytest.approx(1.0), timeout=5.0)

chain      = <dimos.robot.galaxea.r1pro.blueprints.teleop.test_r1pro_vr_teleop.Chain object at 0x176deaef0>
sent_at_estop = 7

.../blueprints/teleop/test_r1pro_vr_teleop.py:758: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

predicate = <function test_estop_stops_the_chassis_and_clearing_it_does_not_resume_motion.<locals>.<lambda> at 0x175745bd0>

    def wait_until(
        predicate: Callable[[], bool],
        *,
        timeout: float,
        interval: float = 0.1,
        message: str | None = None,
    ) -> None:
        """Poll ``predicate`` until it returns truthy or ``timeout`` elapses."""
        deadline = time.monotonic() + timeout
        while time.monotonic() < deadline:
            if predicate():
                return
            time.sleep(interval)
>       raise TimeoutError(message or f"Timed out after {timeout}s waiting for condition")
E       TimeoutError: Timed out after 5.0s waiting for condition

deadline   = 469.174609416
interval   = 0.1
message    = None
predicate  = <function test_estop_stops_the_chassis_and_clearing_it_does_not_resume_motion.<locals>.<lambda> at 0x175745bd0>
timeout    = 5.0

.../utils/testing/waiting.py:35: TimeoutError
dimos.robot.galaxea.r1pro.blueprints.manipulation.test_r1pro_planar_preview::test_real_control_declares_planar_velocity_joints_without_position_commands
Stack Traces | 15.7s run time
def test_real_control_declares_planar_velocity_joints_without_position_commands() -> None:
        """Measured planar joints are ordinary velocity-only hardware interfaces."""
        coordinator = _coordinator_config(r1pro_control())
    
        # The real robot is driven through R1ProConnection: no hardware adapters.
        assert coordinator.hardware == []
        claimed = {joint for task in coordinator.tasks for joint in task.joint_names}
        assert claimed == {*R1PRO_UPPER_BODY_JOINTS, *R1PRO_BASE_JOINTS}
        from dimos.control.contract.keys import Interface
    
        (declaration,) = coordinator.connections
>       assert all(
            declaration.joint_commands[joint] == (Interface.VELOCITY,)
            for joint in R1PRO_PLANAR_BASE.joint_names
        )

Interface  = <enum 'Interface'>
claimed    = {'r1pro/base_forward', 'r1pro/base_lateral', 'r1pro/base_turn', 'r1pro/left_arm_joint1', 'r1pro/left_arm_joint2', 'r1pro/left_arm_joint3', ...}
coordinator = ControlCoordinatorConfig(rpc_transport=<class 'dimos.protocol.rpc.zenohrpc.ZenohRPC'>, default_rpc_timeout=120.0, rpc_..._turn': (<Interface.VELOCITY: 'velocity'>,)}, deadman_timeout_s=0.1)], reading_timeout_s=0.2, zero_stiffness_joints=[])
declaration = ConnectionConfig(rpc_transport=<class 'dimos.protocol.rpc.zenohrpc.ZenohRPC'>, default_rpc_timeout=120.0, rpc_timeouts...': (<Interface.VELOCITY: 'velocity'>,), 'r1pro/base_turn': (<Interface.VELOCITY: 'velocity'>,)}, deadman_timeout_s=0.1)

.../blueprints/manipulation/test_r1pro_planar_preview.py:123: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

.0 = <tuple_iterator object at 0x3429c5ea0>

    assert all(
>       declaration.joint_commands[joint] == (Interface.VELOCITY,)
        for joint in R1PRO_PLANAR_BASE.joint_names
    )
E   KeyError: 'r1pro/base_x'

.0         = <tuple_iterator object at 0x3429c5ea0>
Interface  = <enum 'Interface'>
declaration = ConnectionConfig(rpc_transport=<class 'dimos.protocol.rpc.zenohrpc.ZenohRPC'>, default_rpc_timeout=120.0, rpc_timeouts...': (<Interface.VELOCITY: 'velocity'>,), 'r1pro/base_turn': (<Interface.VELOCITY: 'velocity'>,)}, deadman_timeout_s=0.1)
joint      = 'r1pro/base_x'

.../blueprints/manipulation/test_r1pro_planar_preview.py:124: KeyError

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

@TomCC7
TomCC7 added this pull request to stack #5018 November 6, 2026 05:02
@TomCC7 TomCC7 changed the title R1Pro Quest VR teleop: 18-DOF upper body with a safe fake backend R1Pro Quest VR teleop: 18-DOF upper body, thumbstick chassis and fake backend Nov 6, 2026
@TomCC7 TomCC7 changed the title R1Pro Quest VR teleop: 18-DOF upper body, thumbstick chassis and fake backend R1Pro Quest VR teleop: Viser, tuned waist redundancy, Home preset and real entrypoint Nov 7, 2026
Hermes Agent and others added 4 commits November 9, 2026 21:49
Preserve the complete original VR layer, including merge-only resolutions, using its parent-to-head tree delta. Adapt both backends to explicit Twist input mapping and canonical JointState ports. Keep tuned pitch redundancy, Home preset, scoped triggers, Viser and the explicit real entrypoint; no hardware is launched.

Position holds remain position-only. Explicit inhibited_joints claim metadata reserves velocity-only resources so normal connection idle zeroes them, without a special-case hold tick gate or fake position command. Command-only grippers remain excluded by their owning declaration.

This branch was successfully deployed

No deployments
cachix — 18462eab Deployed Nov 10, 2026 by TomCC7 via cachix-build-macos #16899
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant