Skip to content

Refactor keyboard teleop to EEF twist task#2683

Open
TomCC7 wants to merge 20 commits into
mainfrom
cc/refactor/keyboard-teleop
Open

Refactor keyboard teleop to EEF twist task#2683
TomCC7 wants to merge 20 commits into
mainfrom
cc/refactor/keyboard-teleop

Conversation

@TomCC7

@TomCC7 TomCC7 commented Jul 1, 2026

Copy link
Copy Markdown
Member

Problem

Keyboard arm teleop owned robot-derived state: it read coordinator joint state, required FK model and EEF joint configuration, tracked an absolute EEF pose, and published PoseStamped targets. That mixed input-device behavior with coordinator-owned control responsibilities and made manipulator keyboard blueprints carry duplicate FK wiring.

Solution

  • Add routed spatial EEF twist support through coordinator_ee_twist_command: TwistStamped and an EEFTwistTask task type.
  • Move FK seeding, twist integration, IK solving, safety checks, timeout, and zero-command reset into EEFTwistTask.
  • Refactor KeyboardTeleopModule to publish routed TwistStamped intent only and stop on movement-key release.
  • Migrate current manipulator keyboard blueprints for xArm, Piper, OpenArm, and A-750 to EEFTwistTask.

Testing

uv run dimos run keyboard-teleop-xarm7

@mintlify

mintlify Bot commented Jul 1, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
dimensional 🟢 Ready View Preview Jul 1, 2026, 3:37 AM

💡 Tip: Enable Workflows to automatically generate PRs for you.

Comment thread dimos/control/tasks/eef_twist_task/eef_twist_task.py Outdated
Comment thread dimos/control/tasks/eef_twist_task/eef_twist_task.py Outdated
Comment thread docs/coding-agents/index.md
Comment thread dimos/teleop/keyboard/keyboard_teleop_module.py Outdated
Comment thread dimos/control/tasks/eef_twist_task/__init__.py Outdated
@greptile-apps

greptile-apps Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR moves keyboard arm teleop onto routed end-effector twist commands. The main changes are:

  • Added an EEFTwistTask for twist integration, IK solving, safety checks, timeout handling, and zero-command reset.
  • Added coordinator routing for coordinator_ee_twist_command messages.
  • Refactored keyboard teleop to publish TwistStamped intent instead of Cartesian pose targets.
  • Migrated manipulator keyboard blueprints to the new EEF twist task.

Confidence Score: 5/5

This looks safe to merge.

  • No blocking issues found in the changed code.

Important Files Changed

Filename Overview
dimos/control/tasks/eef_twist_task/eef_twist_task.py Adds the EEF twist task with FK seeding, twist integration, IK solving, finite-value checks, joint-delta checks, timeout handling, and zero-command clearing.
dimos/control/coordinator.py Adds the EEF twist command input, task-name routing, subscription setup, and cleanup.
dimos/teleop/keyboard/keyboard_teleop_module.py Changes keyboard teleop into a lightweight TwistStamped publisher for the EEF twist task.
dimos/robot/manipulators/common/blueprints.py Adds a shared helper for configuring EEF twist tasks from manipulator hardware.

Reviews (4): Last reviewed commit: "remove protocol" | Re-trigger Greptile

Comment thread dimos/control/tasks/eef_twist_task/eef_twist_task.py
Comment thread dimos/control/tasks/eef_twist_task/eef_twist_task.py
Comment on lines +168 to +172
values = twist_to_numpy(twist)
candidate.translation = candidate.translation + values[:3] * dt
angular_step = values[3:] * dt
if np.linalg.norm(angular_step) > 0.0:
candidate.rotation = pinocchio.exp3(angular_step) @ candidate.rotation

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.

P2 Twist Frame Is Ignored

The coordinator uses TwistStamped.frame_id only as the task name, and this integration applies linear and angular values directly in the FK/world pose. Any caller that sends a normal end-effector/body-frame twist will move along world axes after the tool rotates, so a rotated wrist can translate or rotate in a direction different from the commanded EEF-relative motion.

@codecov

codecov Bot commented Jul 1, 2026

Copy link
Copy Markdown

❌ 1 Tests Failed:

Tests completed Failed Passed Skipped
2398 1 2397 72
View the full list of 1 ❄️ flaky test(s)
dimos.e2e_tests.test_dimsim_spatial_memory::test_go_to_the_bed

Flake rate in main: 19.44% (Passed 58 times, Failed 14 times)

Stack Traces | 552s run time
lcm_spy = <dimos.e2e_tests.lcm_spy.LcmSpy object at 0x758924f09d00>
start_blueprint = <function start_blueprint.<locals>.set_name_and_start at 0x758921f3d4e0>
human_input = <function human_input.<locals>.send_human_input at 0x758921f3d580>
dim_sim = <dimos.e2e_tests.dim_sim_client.DimSimClient object at 0x758923d42db0>
explore_house = <function explore_house.<locals>.explore at 0x758921f3da80>

    @pytest.mark.self_hosted_large
    def test_go_to_the_bed(lcm_spy, start_blueprint, human_input, dim_sim, explore_house) -> None:
        start_blueprint(
            "run",
            "unitree-go2-agentic",
            simulator="dimsim",
        )
        lcm_spy.save_topic(".../McpClient/on_system_modules/res")
        lcm_spy.wait_for_saved_topic(".../McpClient/on_system_modules/res", timeout=1200.0)
    
        explore_house()
    
        human_input("go to the bed")
    
>       lcm_spy.wait_until_odom_position(-3.567, -1.332, threshold=2, timeout=180)

dim_sim    = <dimos.e2e_tests.dim_sim_client.DimSimClient object at 0x758923d42db0>
explore_house = <function explore_house.<locals>.explore at 0x758921f3da80>
human_input = <function human_input.<locals>.send_human_input at 0x758921f3d580>
lcm_spy    = <dimos.e2e_tests.lcm_spy.LcmSpy object at 0x758924f09d00>
start_blueprint = <function start_blueprint.<locals>.set_name_and_start at 0x758921f3d4e0>

dimos/e2e_tests/test_dimsim_spatial_memory.py:32: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
dimos/e2e_tests/lcm_spy.py:182: in wait_until_odom_position
    self.wait_for_message_result(
        predicate  = <function LcmSpy.wait_until_odom_position.<locals>.predicate at 0x758921f3dc60>
        self       = <dimos.e2e_tests.lcm_spy.LcmSpy object at 0x758924f09d00>
        threshold  = 2
        timeout    = 180
        x          = -3.567
        y          = -1.332
dimos/e2e_tests/lcm_spy.py:168: in wait_for_message_result
    self.wait_until(
        event      = <threading.Event at 0x758923d43fb0: unset>
        fail_message = 'Failed to get to position x=-3.567, y=-1.332'
        listener   = <function LcmSpy.wait_for_message_result.<locals>.listener at 0x758921f3db20>
        predicate  = <function LcmSpy.wait_until_odom_position.<locals>.predicate at 0x758921f3dc60>
        self       = <dimos.e2e_tests.lcm_spy.LcmSpy object at 0x758924f09d00>
        timeout    = 180
        topic      = '/odom#geometry_msgs.PoseStamped'
        type       = <class 'dimos.msgs.geometry_msgs.PoseStamped.PoseStamped'>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <dimos.e2e_tests.lcm_spy.LcmSpy object at 0x758924f09d00>

    def wait_until(
        self,
        *,
        condition: Callable[[], bool],
        timeout: float,
        error_message: str,
        poll_interval: float = 0.1,
    ) -> None:
        start_time = time.time()
        while time.time() - start_time < timeout:
            if condition():
                return
            time.sleep(poll_interval)
>       raise TimeoutError(error_message)
E       TimeoutError: Failed to get to position x=-3.567, y=-1.332

condition  = <bound method Event.is_set of <threading.Event at 0x758923d43fb0: unset>>
error_message = 'Failed to get to position x=-3.567, y=-1.332'
poll_interval = 0.1
self       = <dimos.e2e_tests.lcm_spy.LcmSpy object at 0x758924f09d00>
start_time = 1782935451.6446774
timeout    = 180

dimos/e2e_tests/lcm_spy.py:105: TimeoutError

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

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Comment thread dimos/control/tasks/eef_twist_task/eef_twist_task.py
@github-actions github-actions Bot added the ready-to-merge Required CI checks have passed on this PR label Jul 1, 2026
@github-actions github-actions Bot added ready-to-merge Required CI checks have passed on this PR and removed ready-to-merge Required CI checks have passed on this PR labels Jul 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-to-merge Required CI checks have passed on this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant