Skip to main content

dora-openarm-mujoco

MuJoCo simulation node for the OpenArm bimanual robot, designed to run inside a dora-rs dataflow.

It replaces the physical follower arms and cameras: it accepts joint-position commands, publishes canonical arm position/state snapshots on request, and renders JPEG camera frames.

Installation

uv sync

Quick start

A self-contained dummy dataflow is included for testing without real hardware. It wires a dummy leader node to the MuJoCo sim and records to disk.

uv run dora build dataflow-dummy.yaml --uv
uv run dora run dataflow-dummy.yaml

Example

Dataflow configuration

Minimal (headless and position forwarding only no cameras)

- id: openarm-mujoco
  build: pip install -e .
  path: dora-openarm-mujoco
  inputs:
    move_position_right: leader/follower_position_right
    move_position_left:  leader/follower_position_left
    request_position: dora/timer/millis/4
  outputs:
    - status
    - position_right
    - position_left

Full (interactive viewer + all cameras, with contacts, can be used for vr teleoperation)

- id: openarm-mujoco
  build: pip install -e .
  path: dora-openarm-mujoco
  args: "--viewer --render --enable-collision --ctrl --keyframe home"
  inputs:
    move_position_right: leader/follower_position_right
    move_position_left:  leader/follower_position_left
    request_state: dora/timer/millis/4
  outputs:
    - status
    - state_right
    - state_left
    - camera_wrist_right
    - camera_wrist_left
    - camera_head_left
    - camera_head_right
    - camera_ceiling

Inputs

ID Type Description
move_position_right float32[8] or struct{qpos: float32[8]} Target joint positions for the right arm: joints 1-7 then the gripper. Legacy new_position structs are also accepted.
move_position_left float32[8] or struct{qpos: float32[8]} Same layout for the left arm.
request_position any Sample both arms and publish only position_*. The payload is ignored; observation_timestamp records the snapshot time.
request_state any Sample both arms and publish only state_*. The payload is ignored; observation_timestamp records the snapshot time.
pose_right float32[7] VR controller pose [x, y, z, qw, qx, qy, qz], expressed in the --origin-frame frame (default: the scene's arm_origin site). Used only with --debug-frames.
pose_left float32[7] Same for the left controller.
button_x bool[1] X button state. Edge-triggered: on press every scene joint on non-arm bodies (freejoint objects plus fixtures like drawers/doors) snaps back to the --keyframe pose; with --randomize-objects the freejoint objects land at a randomized pose instead. The button must be released to re-arm.

Outputs

ID Type Description
status string[1] ready, published once on startup.
position_right, position_left struct{qpos: float32[8]} Joint positions sampled on each request_position event.
state_right, state_left struct Full state sampled on request_state: qpos, qvel, qtorque (float32[8]), tmos, trotor (int32[8]), motor_status (string[8]), and bus (struct). Torque is MuJoCo generalized actuator force; temperatures are zero placeholders.
camera_wrist_right uint8[N] JPEG frame, ~30 Hz. Requires --render.
camera_wrist_left uint8[N] JPEG frame, ~30 Hz. Requires --render.
camera_head_left uint8[N] JPEG frame, ~30 Hz. Requires --render.
camera_head_right uint8[N] JPEG frame, ~30 Hz. Requires --render.
camera_ceiling uint8[N] JPEG frame, ~30 Hz. Requires --render.

Camera outputs carry metadata={"encoding": "jpeg"}.

State diagnostics are simulation placeholders, not hardware health measurements. motor_status follows qpos order: joints 1-7 then finger_joint1, with ENABLED for existing joints and SILENT for missing joints. bus.carrier is always true (bool); the cumulative counters bus_off, error_passive, error_warning, ack_error, tx_overflow, rx_overflow, and net_down are always zero (int64). The simulator does not model CAN communication, motor faults, or temperature.

Arm snapshots are published only on request_position or request_state, including before the first position command. Connect the desired request input to a timer independent of control outputs. request_position publishes only position_*; request_state publishes only state_*, which includes qpos, velocity, and actuator force. Startup publishes ready; position commands only update the simulation target. Each request samples both arms under one lock. After sampling both arms, the node captures one time.time_ns() timestamp before releasing the lock and uses it as observation_timestamp for every output of that request. This is integer Unix wall-clock nanoseconds, not the request time or MuJoCo simulation time (data.time). Request metadata is copied without modification except for timestamp, which is removed from the copy so Dora supplies each output message timestamp.

To migrate an older dataflow, rename command inputs from position_* to move_position_*, replace MuJoCo's arm_right_observation and arm_left_observation outputs with position_* or state_*, and add a request_position or request_state input. The old output ports have been removed. Consumers receive Arrow structs rather than flat arrays. The dummy example connects recorder's arm_right_observation / arm_left_observation inputs to state_right / state_left; these recorder input names remain unchanged.

Arguments

Pass these via the args: field in the dataflow YAML, or directly on the command line.

Argument Default Description
--xml PATH unset MJCF scene file to load. Overrides --scene when set.
--scene NAME cell Bundled scene to load when --xml is not set. Choices: cell, demo, pedestal, bimanual.
--keyframe NAME home Keyframe in the MJCF to reset to on startup.
--randomize-objects [RANGE_M] off Randomize freejoint scene objects (e.g. cubes) on startup and on each button_x reset: uniform xy offset within ±RANGE_M m of the keyframe pose plus a uniform yaw. z and articulated fixtures are unchanged. RANGE_M defaults to 0.05 when omitted.
--enable-collision off Enable contact/collision detection. Disabled by default to avoid unexpected joint-locking during teleoperation.
--ctrl off Write incoming positions to data.ctrl and step the physics (mj_step) to simulate actuator control. The default writes directly to data.qpos with mj_forward.
--viewer off Open the interactive MuJoCo viewer window. Requires a display.
--render off Enable offscreen camera rendering and publish JPEG frames. Leave off if cameras are not needed.
--debug-frames off Draw VR controller poses as coloured arrows in the viewer. Only visible with --viewer.
--origin-frame NAME arm_origin Frame incoming pose_right/pose_left are relative to; the overlay composes poses with its live world pose and draws the reference axes there. Pass world for raw world-frame poses. Missing frame → warning + world fallback.
--origin-frame-type TYPE site MuJoCo object type of --origin-frame. Choices: body, site, geom.

Environment variables

Every argument's default can also be set via an environment variable named DORA_OPENARM_MUJOCO_ + the upper-cased argument name:

Environment variable Argument
DORA_OPENARM_MUJOCO_XML --xml
DORA_OPENARM_MUJOCO_SCENE --scene
DORA_OPENARM_MUJOCO_KEYFRAME --keyframe
DORA_OPENARM_MUJOCO_ENABLE_COLLISION --enable-collision
DORA_OPENARM_MUJOCO_CTRL --ctrl
DORA_OPENARM_MUJOCO_VIEWER --viewer
DORA_OPENARM_MUJOCO_RENDER --render
DORA_OPENARM_MUJOCO_DEBUG_FRAMES --debug-frames
DORA_OPENARM_MUJOCO_ORIGIN_FRAME --origin-frame
DORA_OPENARM_MUJOCO_ORIGIN_FRAME_TYPE --origin-frame-type

Boolean flags accept 1/0, true/false, yes/no and on/off. DORA_OPENARM_MUJOCO_VIEWER additionally accepts an FPS number (e.g. 60); true enables the viewer at the default 30 Hz.

Explicit CLI arguments override the environment. Boolean flags also gain a --no-* form (e.g. --no-render, --no-viewer) to turn an environment-enabled option back off. Set the variables via the env: field of the node in the dataflow YAML:

- id: openarm-mujoco
  path: dora-openarm-mujoco
  env:
    DORA_OPENARM_MUJOCO_VIEWER: "60"
    DORA_OPENARM_MUJOCO_RENDER: "true"

License

Licensed under the Apache License 2.0. See LICENSE for details.

Copyright 2026 Enactic, Inc.

Code of Conduct

All participation in the OpenArm project is governed by our Code of Conduct.

Metadata

Release files for dora-openarm-mujoco 0.5.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for dora-openarm-mujoco 0.5.1
File Size Uploaded
dora_openarm_mujoco-0.5.1.tar.gz 24.7 kB Details

Release files / dora_openarm_mujoco-0.5.1.tar.gz

Download URL dora_openarm_mujoco-0.5.1.tar.gz
Size 24.7 kB
Tags Source
SHA-256 checksum
How to use checksums
d54edea67db0c90ff829b9a1ca1f9f8a0f564e969a7237459314ca8d7941e321
BLAKE2b-256 checksum
How to use checksums
c1d7af33b630190a7a767d85953364153504d318ebd90f9400996ddb58a2f906
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 18, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.5.1 This release

1 release file

0.5.0

1 release file

0.4.0

1 release file

0.3.0

1 release file

0.2.0

1 release file

0.1.0

1 release file

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page