Skip to content

refactor(robot): replace YAM, OpenArm, and Metal adapters with connection modules - #4960

Open
TomCC7 wants to merge 25 commits into
mainfrom
cc/refactor/damiao-connections-main
Open

TomCC7 wants to merge 25 commits into
mainfrom
cc/refactor/damiao-connections-main

Conversation

@TomCC7

@TomCC7 TomCC7 commented Oct 31, 2026 •

Copy link
Copy Markdown
Member

Stack: main → #4938 → #4939 → #4960. Each layer is based on the refreshed preceding branch; shared gripper code and tests stay in #4939.

YAM, dual YAM, OpenArm, single Metal, and dual Metal use direct connection modules for CAN ownership, measured state, and position commands. The legacy Damiao adapters, registry entries, hardware factories, and Metal adapter coordinators are removed. Damiao-specific implementation and tests live in dimos/robot/manipulators/damiao/.

Native CAN selectors are required strings. Missing, None, blank, or duplicate selectors fail configuration validation. Explicit --simulation composes a separate MockJointConnection; native connections never fall back to mock hardware.

Vendor declarations use plain lists/dictionaries and the SDK's native Robot builder. Connections directly hold the robot and arm/gripper views, with no session wrapper types. SDK configuration owns gripper direction and exposes 0=closed, 1=open. Startup completes calibration, feedback validation, subscriptions, and reader setup before readiness or state streaming. Shutdown, rollback, feedback faults, and deadman holds retain regression coverage.

Metal reuses main #4865's pinned geometry, fixed-attachment API, planning groups, and configurable mount transforms. The single- and dual-arm entry names are preserved. Both arms reuse the same Metal profiles, motor addresses, gains, and gripper settings. Default bench transforms remain examples; the actual mounting geometry is configurable.

RobotModel loaded views add logical gripper joints and namespace joint names without synthetic URDF paths or generated model files. Deepcopy preserves lazy source handles without checking out robot assets. Serialization formats are unchanged. The existing Quest collection caller uses the deployed arm/ joint names; the learning pipeline is unchanged.

The retired-adapter Codecov exclusions cover only the five removed directories; thresholds are unchanged. Supersedes #4959.

Validation

Try it

See Metal setup and mount geometry and the connection test runbook.

gh pr checkout 4960
uv run dimos --simulation run dual-makermods-metal-planner-coordinator --daemon
uv run dimos shell
# Stop after exiting the shell:
uv run dimos stop

Native single Metal requires --can-port; dual Metal requires distinct --left-can-port and --right-can-port. Physical startup enables motors and calibrates gripper endpoints, causing jaw motion.

- XArmConnection: a connection module over the xArm SDK. Servo targets on
  position_command, joint speeds on velocity_command (each with a duration,
  so the arm stops by itself), readings at about 100 Hz with real speeds and
  torques. Start fails on a wrong arm type or firmware below 1.9.0; an arm
  already in error starts but takes no command until clear_errors(). A
  position target further than jump_limit_s at the URDF top speed is refused.
  The xArm Gripper is its URDF drive_joint in radians.
- xarm_model(arm, gripper, robot=...): one URDF builder for the connection
  module, the coordinator and make_xarm_model_config.
- movable_joints() leaves out mimic joints, so the gripper's five followers
  are not driven.
- xarm7-keyboard-teleop / xarm6-keyboard-teleop: keyboard eef twist through
  XArmTwistCoordinator and XArmConnection, arm only. The IP is given at run
  time with --xarmconnection.ip or XARMCONNECTION__IP.
…imOS

When xArm Studio or the arm's stop button stops it (mode 0, state 4, no
error), every motion command returns code 9 and the arm stayed dead until
clear_errors() or a restart. On code 9 with no error or warning, the mode
and set_state(0) are set again (motors left as they are), at most once a second, and the
command retried once. While the arm still answers code 9, motion commands are
dropped for 0.5 s (and halt skips its zero-speed call) so slow refused SDK
calls do not stall the commands behind them. Errors and warnings still wait
for clear_errors().
The eef_twist task in xarm7/xarm6-keyboard-teleop had timeout 0.0, so if
keyboard messages stopped mid-jog the coordinator kept integrating the last
twist and the arm kept moving.
When get_joint_states kept failing, the last good reading was used forever
for the jump guard and to fill joints left out of a position command, so a
target far from where the arm really is could pass. Joint targets are now
refused once the latest reading is older than ten read periods (0.1 s).
Speeds do not use the reading and are unchanged.
The arm refuses to clear a warning (code 1) while an error is still set, so
after an e-stop clear_errors() failed on its first call.
connection.py imported xarm.wrapper at the top, and the CI test venv has no
xArm SDK (it is in the control extra, not the tests group). Every blueprint
that imports xarm/blueprints/teleop.py failed to load: the new keyboard
teleop blueprints, the older keyboard-teleop-* ones, and teleop-webxr-a1z,
learning-collect-webxr-piper and demo-pico-body-tracking through
teleop/webxr/blueprints.py. The SDK is now imported in _open_sdk(), called
from connect(), as the G1 wholebody connection does for unitree_sdk2py.
The tests fake _open_sdk() instead of XArmAPI.
test_every_registered_blueprint_is_annotated fails on every registered
blueprint missing from annotations.yaml. Both take the arm's IP as
--xarmconnection.ip and run on a real arm only.
…ors()

A stop from xArm Studio or the arm's stop button no longer lasts only
until the next command: the module refuses every command until
clear_errors() makes the arm ready again.
…e coordinator

- The gripper task drives a joint of a robot behind a connection module:
  with no adapter hardware, its position limits come from the
  coordinator's model, the same ones the coordinator enforces. The task
  registry passes the model to a factory that takes a `model` argument.
- `open_at` ("upper" or "lower") says which end of the joint is open, so
  set_normalized(1.0) opens a gripper like the xArm Gripper's drive_joint
  (0 rad open, 0.85 rad closed). The default keeps adapter grippers as they
  were.
- xarm7-keyboard-teleop / xarm6-keyboard-teleop: XArmConnection with the
  xArm Gripper, the coordinator's model with it, and an `arm_gripper` task
  on arm/drive_joint fed by the keyboard's `[` and `]`.
…op Manipulation app

- Replaces xarm7-keyboard-teleop / xarm6-keyboard-teleop: no pygame window.
  The app's jog arrives on ee_twist_command and its gripper opening on
  right_gripper_command; the arm's joints go out on coordinator_joint_state.
- The eef_twist task stops the end 0.3 s after the last twist, since the app
  sends twists only while a jog is held.
- XArmTwistCoordinator takes right_gripper_command; the arm_gripper task is
  bound to it.
- A UrdfManager serves the arm's URDF (joints named arm/joint1..., as the
  coordinator names them) on render_file_request / file_info and puts its
  joints on tf, so the Controller's 3D view draws the arm moving.
- xarm_model is given the gripper by keyword.
They replace xarm6/7-keyboard-teleop, so the annotations follow the new
names and say the commands come from the Desktop Manipulation app.

@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: ec41253 Previous: a8ca5ff Ratio
peak memory 1740.473 MB 1752.543 MB 0.99
peak threads 368 threads 371 threads 0.99
network (transport) 2731.051 MB 2725.828 MB 1.00
disk write 1.473 MB 1.652 MB 0.89
instructions 138.13 G 136.444 G 1.01

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

@codecov

codecov Bot commented Oct 31, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.63735% with 65 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...mos/robot/manipulators/damiao/damiao_connection.py 88.33% 16 Missing and 17 partials ⚠️
dimos/robot/assets/model.py 81.81% 5 Missing and 5 partials ⚠️
dimos/control/connection/mock_joint_connection.py 88.88% 7 Missing and 1 partial ⚠️
...obot/manipulators/damiao/test_damiao_connection.py 97.68% 4 Missing ⚠️
...manipulators/damiao/_damiao_native_test_helpers.py 96.93% 2 Missing and 1 partial ⚠️
.../robot/manipulators/damiao/_damiao_test_helpers.py 97.22% 1 Missing and 2 partials ⚠️
dimos/robot/manipulators/damiao/damiao_model.py 96.15% 1 Missing and 1 partial ⚠️
...ipulators/dual_makermods_metal/blueprints/basic.py 50.00% 2 Missing ⚠️
@@                      Coverage Diff                      @@
##           mustafa/feat/xarm-gripper    #4960      +/-   ##
=============================================================
- Coverage                      81.58%   81.35%   -0.24%     
=============================================================
  Files                           1772     1791      +19     
  Lines                         176296   177501    +1205     
  Branches                       15350    15418      +68     
=============================================================
+ Hits                          143836   144409     +573     
- Misses                         28771    29406     +635     
+ Partials                        3689     3686       -3     
Components Coverage Δ
Tests 92.97% <99.59%> (-0.51%) ⬇️
Flag Coverage Δ
OS-macos-latest 77.68% <96.27%> (+0.06%) ⬆️
OS-ubuntu-24.04-arm 77.47% <96.27%> (+0.06%) ⬆️
OS-ubuntu-latest 77.86% <96.27%> (+0.05%) ⬆️
Py-3.10 77.85% <96.27%> (+0.05%) ⬆️
Py-3.11 77.84% <96.27%> (+0.06%) ⬆️
Py-3.12 77.85% <96.27%> (+0.05%) ⬆️
SelfHosted-Linux 39.68% <34.81%> (-0.36%) ⬇️
SelfHosted-macOS 39.41% <34.81%> (-0.35%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...s/control/connection/test_mock_joint_connection.py 100.00% <100.00%> (ø)
dimos/control/test_coordinator_connections.py 99.30% <100.00%> (+0.06%) ⬆️
dimos/control/tick_loop.py 86.79% <100.00%> (+0.89%) ⬆️
dimos/hardware/test_adapter_registries.py 91.74% <ø> (-0.23%) ⬇️
dimos/robot/all_blueprints.py 100.00% <ø> (ø)
dimos/robot/assets/source.py 82.67% <ø> (ø)
dimos/robot/assets/test_model_views.py 100.00% <100.00%> (ø)
dimos/robot/assets/test_source.py 100.00% <100.00%> (ø)
dimos/robot/manipulators/damiao/conftest.py 100.00% <100.00%> (ø)
...imos/robot/manipulators/damiao/damiao_blueprint.py 100.00% <100.00%> (ø)
... and 52 more

... and 9 files with indirect coverage changes

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@TomCC7 TomCC7 changed the title refactor(robot): add YAM, OpenArm, and Metal connection modules refactor(robot): replace YAM, OpenArm, and Metal adapters with connection modules Oct 31, 2026
@TomCC7
TomCC7 changed the base branch from main to mustafa/feat/xarm-gripper November 2, 2026 00:02
Build directly on main with standard connection ports, dedicated control blueprints and the minimal Metal adapter/model configuration. Keep learning and policy changes out of this branch.

Reuse model-backed gripper support from #4939 (27d3753) and the Metal driver/configuration from #4900. Lock only the required can-motor-control update to 0.0.11.
@TomCC7
TomCC7 force-pushed the cc/refactor/damiao-connections-main branch from 66cd59d to 668933d Compare November 2, 2026 00:09
@TomCC7
TomCC7 added this pull request to stack #4940 November 2, 2026 01:14
Comment thread dimos/control/tick_loop.py Resolved
Comment thread .codecov.yml Resolved
@greptile-apps

greptile-apps Bot commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 4/5

[High impact] The PR changes how several robot families reach and control physical hardware.

Fix the model transform order before merging; it prevents G1 teleop from loading its arm model.

Findings

  1. P1 G1 teleop model cannot load ▶
  2. P2 Documented model method is missing ▶

Summary

Replaces YAM, OpenArm, and Metal adapters with direct connection modules and an explicit mock connection for simulation.

  • Robot coordinators send commands through direct connections to YAM, OpenArm, and Metal arms.
  • Planner and control models use deployed joint names and logical gripper joints.
  • Metal planning keeps configurable mounts and matching settings for both arms.

Reviews (1) · Last reviewed commit: "Merge refreshed xArm stack into Damiao r..." · Reviewed by Greptile

Comment on lines +331 to +333
return RobotModel(
_source_path=self._source_path, _base_model=self, _renamed_joints=tuple(names.items())
)

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.

P1 G1 teleop model cannot load

with_renamed_joints() now loads and renames the base model before later joint transforms run. Existing callers still use the original names. G1_TELEOP_ARM_MODEL, for example, renames the waist joints and then passes their URDF names to with_fixed_joints(). Loading it now raises ValueError: Joint not found, breaking G1 teleop setup. The existing attachment test also replaces limits using a name that has already been renamed.

Preserve the previous transform order, or migrate these callers and cover both chains.

Comment on lines +79 to +81
`OpenYamConnection()` and `DualOpenYamConnection()` default to sources `yam` and
`dual_openyam` when used directly. Use `.models(source=...)` for a matching
coordinator/planner model when composing a custom stack.

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 Documented model method is missing

The custom-stack instructions recommend .models(source=...), but neither connection provides a models() method. Following this example raises AttributeError. Document planning_model(source=...) for the planner and control_model(source=...) for the coordinator instead.

Suggested change
`OpenYamConnection()` and `DualOpenYamConnection()` default to sources `yam` and
`dual_openyam` when used directly. Use `.models(source=...)` for a matching
coordinator/planner model when composing a custom stack.
`OpenYamConnection()` and `DualOpenYamConnection()` default to sources `yam` and
`dual_openyam` when used directly. Use `.planning_model(source=...)` for the planner
and `.control_model(source=...)` for the coordinator when composing a custom stack.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@github-actions github-actions Bot added the ready-to-merge Required CI checks have passed on this PR label Nov 3, 2026
Base automatically changed from mustafa/feat/xarm-gripper to main November 5, 2026 01:04

This branch was previously deployed

1 inactive deployment
cachix — ec412534 Deployed Nov 3, 2026 by TomCC7 via cachix-build #15555
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.

2 participants