Browse Source

Merge branch 'main' into git-source

tags/v0.3.12-rc0
Philipp Oppermann 9 months ago
parent
commit
6998964cda
Failed to extract signature
100 changed files with 9793 additions and 2315 deletions
  1. +104
    -0
      .github/workflows/cargo-release.yml
  2. +34
    -8
      .github/workflows/ci.yml
  3. +31
    -0
      .github/workflows/docker-image.yml
  4. +10
    -21
      .github/workflows/node_hub_test.sh
  5. +264
    -158
      .github/workflows/release.yml
  6. +3
    -0
      .gitmodules
  7. +3679
    -1910
      Cargo.lock
  8. +11
    -0
      Cargo.toml
  9. +60
    -124
      README.md
  10. +1
    -0
      _typos.toml
  11. +1
    -0
      apis/c++/node/Cargo.toml
  12. +20
    -12
      apis/c++/node/build.rs
  13. +100
    -0
      apis/c++/node/src/lib.rs
  14. +6
    -1
      apis/python/node/Cargo.toml
  15. +1
    -1
      apis/python/node/dora/__init__.pyi
  16. +3
    -7
      apis/python/node/dora/cuda.py
  17. +1
    -2
      apis/python/node/generate_stubs.py
  18. +3
    -2
      apis/python/node/pyproject.toml
  19. +111
    -15
      apis/python/node/src/lib.rs
  20. +48
    -14
      apis/python/operator/src/lib.rs
  21. +4
    -4
      apis/rust/node/Cargo.toml
  22. +43
    -0
      apis/rust/node/src/node/mod.rs
  23. +1
    -0
      benches/llms/.gitignore
  24. +10
    -0
      benches/llms/README.md
  25. +28
    -0
      benches/llms/llama_cpp_python.yaml
  26. +19
    -0
      benches/llms/mistralrs.yaml
  27. +20
    -0
      benches/llms/phi4.yaml
  28. +19
    -0
      benches/llms/qwen2.5.yaml
  29. +20
    -0
      benches/llms/transformers.yaml
  30. +1
    -0
      benches/mllm/.gitignore
  31. +10
    -0
      benches/mllm/README.md
  32. +220
    -0
      benches/mllm/benchmark_script.py
  33. +23
    -0
      benches/mllm/phi4.yaml
  34. +1
    -0
      benches/vlm/.gitignore
  35. +10
    -0
      benches/vlm/README.md
  36. +20
    -0
      benches/vlm/magma.yaml
  37. +22
    -0
      benches/vlm/phi4.yaml
  38. +22
    -0
      benches/vlm/qwen2.5vl.yaml
  39. +10
    -0
      binaries/cli/Cargo.toml
  40. +6
    -0
      binaries/cli/build.rs
  41. +1
    -0
      binaries/cli/pyproject.toml
  42. +23
    -19
      binaries/cli/src/build.rs
  43. +71
    -1
      binaries/cli/src/lib.rs
  44. +2
    -1
      binaries/cli/src/template/python/__node-name__/pyproject.toml
  45. +1
    -0
      binaries/cli/src/template/python/listener/listener-template.py
  46. +1
    -0
      binaries/cli/src/template/python/talker/talker-template.py
  47. +6
    -3
      binaries/daemon/src/spawn.rs
  48. +14
    -12
      binaries/runtime/src/operator/python.rs
  49. +42
    -0
      dist-workspace.toml
  50. +32
    -0
      docker/slim/Dockerfile
  51. +51
    -0
      docker/slim/README.md
  52. +40
    -0
      examples/alexk-lcr/ASSEMBLING.md
  53. +90
    -0
      examples/alexk-lcr/CONFIGURING.md
  54. +82
    -0
      examples/alexk-lcr/INSTALLATION.md
  55. +174
    -0
      examples/alexk-lcr/README.md
  56. +80
    -0
      examples/alexk-lcr/RECORDING.md
  57. +135
    -0
      examples/alexk-lcr/assets/simulation/lift_cube.xml
  58. +135
    -0
      examples/alexk-lcr/assets/simulation/pick_place_cube.xml
  59. +137
    -0
      examples/alexk-lcr/assets/simulation/push_cube.xml
  60. +135
    -0
      examples/alexk-lcr/assets/simulation/reach_cube.xml
  61. +141
    -0
      examples/alexk-lcr/assets/simulation/stack_two_cubes.xml
  62. +435
    -0
      examples/alexk-lcr/bus.py
  63. +0
    -0
      examples/alexk-lcr/configs/.gitkeep
  64. +219
    -0
      examples/alexk-lcr/configure.py
  65. +74
    -0
      examples/alexk-lcr/graphs/bi_teleop_real.yml
  66. +40
    -0
      examples/alexk-lcr/graphs/mono_replay_real.yml
  67. +37
    -0
      examples/alexk-lcr/graphs/mono_teleop_real.yml
  68. +70
    -0
      examples/alexk-lcr/graphs/mono_teleop_real_and_simu.yml
  69. +43
    -0
      examples/alexk-lcr/graphs/mono_teleop_simu.yml
  70. +119
    -0
      examples/alexk-lcr/graphs/record_mono_teleop_real.yml
  71. +153
    -0
      examples/alexk-lcr/nodes/interpolate_lcr_to_lcr.py
  72. +115
    -0
      examples/alexk-lcr/nodes/interpolate_lcr_to_record.py
  73. +138
    -0
      examples/alexk-lcr/nodes/interpolate_lcr_to_simu_lcr.py
  74. +85
    -0
      examples/alexk-lcr/nodes/interpolate_replay_to_lcr.py
  75. +64
    -0
      examples/aloha/ASSEMBLING.md
  76. +95
    -0
      examples/aloha/CONFIGURING.md
  77. +87
    -0
      examples/aloha/INSTALLATION.md
  78. +42
    -0
      examples/aloha/README.md
  79. +17
    -0
      examples/aloha/RECORDING.md
  80. +13
    -0
      examples/aloha/benchmark/python/README.md
  81. +535
    -0
      examples/aloha/benchmark/python/dynamixel.py
  82. +229
    -0
      examples/aloha/benchmark/python/robot.py
  83. +30
    -0
      examples/aloha/benchmark/python/teleoperate_real_robot.py
  84. +103
    -0
      examples/aloha/benchmark/ros2/README.md
  85. +9
    -0
      examples/aloha/benchmark/ros2/config/master_modes_left.yaml
  86. +9
    -0
      examples/aloha/benchmark/ros2/config/master_modes_right.yaml
  87. +17
    -0
      examples/aloha/benchmark/ros2/config/puppet_modes_left.yaml
  88. +17
    -0
      examples/aloha/benchmark/ros2/config/puppet_modes_right.yaml
  89. +4
    -0
      examples/aloha/benchmark/ros2/dataflow.yml
  90. +22
    -0
      examples/aloha/benchmark/ros2/setup_ros2.sh
  91. +95
    -0
      examples/aloha/benchmark/ros2/teleop.py
  92. +9
    -0
      examples/aloha/benchmark/rust/README.md
  93. +141
    -0
      examples/aloha/graphs/eval.yml
  94. +38
    -0
      examples/aloha/graphs/gym.yml
  95. +162
    -0
      examples/aloha/graphs/record_2arms_teleop.yml
  96. +32
    -0
      examples/aloha/graphs/record_teleop.yml
  97. +61
    -0
      examples/aloha/graphs/replay.yml
  98. +4
    -0
      examples/aloha/hardware_config/99-fixed-interbotix-udev.rules
  99. +24
    -0
      examples/aloha/hardware_config/99-interbotix-udev.rules
  100. +13
    -0
      examples/aloha/nodes/aloha-client/Cargo.toml

+ 104
- 0
.github/workflows/cargo-release.yml View File

@@ -0,0 +1,104 @@
name: Cargo Release

permissions:
contents: write

on:
release:
types:
- "published"
workflow_dispatch:

jobs:
cargo-release:
name: "Cargo Release"

strategy:
matrix:
platform: [ubuntu-22.04]
fail-fast: false
runs-on: ${{ matrix.platform }}

steps:
- uses: actions/checkout@v3

- uses: r7kamura/rust-problem-matchers@v1.1.0
- name: Free Disk Space (Ubuntu)
uses: jlumbroso/free-disk-space@main
if: runner.os == 'Linux'
with:
# this might remove tools that are actually needed,
# if set to "true" but frees about 6 GB
tool-cache: true

# all of these default to true, but feel free to set to
# "false" if necessary for your workflow
android: true
dotnet: true
haskell: true
large-packages: false
docker-images: true
swap-storage: true

- name: "Publish packages on `crates.io`"
if: runner.os == 'Linux'
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
run: |

# Publishing those crates from outer crates with no dependency to inner crates
# As cargo is going to rebuild the crates based on published dependencies
# we need to publish those outer crates first to be able to test the publication
# of inner crates.
#
# We should preferably test pre-releases before testing releases as
# cargo publish might catch release issues that the workspace manages to fix using
# workspace crates.
publish_if_not_exists() {
local package_name=$1
local version=$(cargo metadata --no-deps --format-version=1 | jq -r '.packages[] | select(.name=="'"$package_name"'") | .version')

if [[ -z $version ]]; then
echo "error: package '$package_name' not found in the workspace."
return 1
fi

if cargo search "$package_name" | grep -q "^$package_name = \"$version\""; then
echo "package '$package_name' version '$version' already exists on crates.io. skipping publish."
else
echo "publishing package '$package_name' version '$version'..."
cargo publish --package "$package_name"
fi
}

# the dora-message package is versioned separately, so this publish command might fail if the version is already published
publish_if_not_exists dora-message

# Publish libraries crates
publish_if_not_exists dora-tracing
publish_if_not_exists dora-metrics
publish_if_not_exists dora-download
publish_if_not_exists dora-core
publish_if_not_exists communication-layer-pub-sub
publish_if_not_exists communication-layer-request-reply
publish_if_not_exists shared-memory-server
publish_if_not_exists dora-arrow-convert

# Publish rust API
publish_if_not_exists dora-operator-api-macros
publish_if_not_exists dora-operator-api-types
publish_if_not_exists dora-operator-api
publish_if_not_exists dora-node-api
publish_if_not_exists dora-operator-api-python
publish_if_not_exists dora-operator-api-c
publish_if_not_exists dora-node-api-c

# Publish binaries crates
publish_if_not_exists dora-coordinator
publish_if_not_exists dora-runtime
publish_if_not_exists dora-daemon
publish_if_not_exists dora-cli

# Publish ROS2 bridge
publish_if_not_exists dora-ros2-bridge-msg-gen
publish_if_not_exists dora-ros2-bridge

+ 34
- 8
.github/workflows/ci.yml View File

@@ -127,6 +127,26 @@ jobs:
- name: "C++ Dataflow example"
timeout-minutes: 15
run: cargo run --example cxx-dataflow
- name: "Install Arrow C++ Library"
timeout-minutes: 10
shell: bash
run: |
if [ "$RUNNER_OS" == "Linux" ]; then
# For Ubuntu
sudo apt-get update
sudo apt-get install -y -V ca-certificates lsb-release wget
wget https://apache.jfrog.io/artifactory/arrow/$(lsb_release --id --short | tr 'A-Z' 'a-z')/apache-arrow-apt-source-latest-$(lsb_release --codename --short).deb
sudo apt-get install -y -V ./apache-arrow-apt-source-latest-$(lsb_release --codename --short).deb
sudo apt-get update
sudo apt-get install -y -V libarrow-dev libarrow-glib-dev
elif [ "$RUNNER_OS" == "macOS" ]; then
# For macOS
brew update
brew install apache-arrow
fi
- name: "C++ Dataflow2 example"
timeout-minutes: 15
run: cargo run --example cxx-arrow-dataflow
- name: "Cmake example"
if: runner.os == 'Linux'
timeout-minutes: 30
@@ -280,7 +300,7 @@ jobs:
run: |
cargo install --path binaries/cli --locked
- name: "Test CLI (Rust)"
timeout-minutes: 30
timeout-minutes: 45
# fail-fast by using bash shell explictly
shell: bash
run: |
@@ -312,7 +332,7 @@ jobs:
enable-cache: true

- name: "Test CLI (Python)"
timeout-minutes: 30
timeout-minutes: 45
# fail-fast by using bash shell explictly
shell: bash
run: |
@@ -366,7 +386,7 @@ jobs:

dora destroy

# Run Python queue latency test
# Run Python queue latency test
echo "Running CI Queue Latency Test"
dora run tests/queue_size_latest_data_python/dataflow.yaml --uv

@@ -374,13 +394,13 @@ jobs:
echo "Running CI Queue + Timeout Test"
dora run tests/queue_size_and_timeout_python/dataflow.yaml --uv

# Run Rust queue latency test
# Run Rust queue latency test
echo "Running CI Queue Size Latest Data Rust Test"
dora build tests/queue_size_latest_data_rust/dataflow.yaml --uv
dora run tests/queue_size_latest_data_rust/dataflow.yaml --uv

- name: "Test CLI (C)"
timeout-minutes: 30
timeout-minutes: 45
# fail-fast by using bash shell explictly
shell: bash
if: runner.os == 'Linux'
@@ -399,7 +419,7 @@ jobs:
dora destroy

- name: "Test CLI (C++)"
timeout-minutes: 30
timeout-minutes: 45
# fail-fast by using bash shell explictly
shell: bash
if: runner.os == 'Linux'
@@ -491,6 +511,8 @@ jobs:
target: aarch64-unknown-linux-musl
- runner: ubuntu-22.04
target: armv7-unknown-linux-musleabihf
- runner: ubuntu-22.04
target: x86_64-pc-windows-gnu
- runner: macos-13
target: aarch64-apple-darwin
- runner: macos-13
@@ -501,9 +523,13 @@ jobs:
- uses: r7kamura/rust-problem-matchers@v1.1.0
- name: "Add toolchains"
run: rustup target add ${{ matrix.platform.target }}
- name: "Build"
- name: Install system-level dependencies
if: runner.target == 'x86_64-pc-windows-gnu'
run: |
sudo apt install g++-mingw-w64-x86-64 gcc-mingw-w64-x86-64
- name: "Check"
uses: actions-rs/cargo@v1
with:
use-cross: true
command: check
args: --target ${{ matrix.platform.target }} -p dora-cli
args: --target ${{ matrix.platform.target }} --all --exclude dora-node-api-python --exclude dora-operator-api-python --exclude dora-ros2-bridge-python

+ 31
- 0
.github/workflows/docker-image.yml View File

@@ -0,0 +1,31 @@
name: Docker Image CI/CD

on:
push:
branches: ["main"]
paths:
- "docker/**"
pull_request:
paths:
- "docker/**"

jobs:
build_and_push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4

- name: "Login to GitHub Container Registry"
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{github.actor}}
password: ${{secrets.GITHUB_TOKEN}}

- name: Build the Docker image
run: |
docker build docker/slim --platform linux/amd64,linux/arm64,linux/arm32v6,linux/arm32v7 -t multi-platform --tag ghcr.io/dora-rs/dora-slim:latest
docker push ghcr.io/dora-rs/dora-slim:latest

+ 10
- 21
.github/workflows/node_hub_test.sh View File

@@ -27,8 +27,8 @@ else
cargo test

pip install "maturin[zig]"
maturin build --zig --release
# If GITHUB_EVENT_NAME is release or workflow_dispatch, publish the wheel
maturin build --zig
# If GITHUB_EVENT_NAME is release or workflow_dispatch, publish the wheel on multiple platforms
if [ "$GITHUB_EVENT_NAME" == "release" ] || [ "$GITHUB_EVENT_NAME" == "workflow_dispatch" ]; then
# Free up ubuntu space
sudo apt-get clean
@@ -37,29 +37,18 @@ else
sudo rm -rf /opt/ghc/

maturin publish --skip-existing --zig
fi

# aarch64-unknown-linux-gnu
rustup target add aarch64-unknown-linux-gnu
maturin build --target aarch64-unknown-linux-gnu --zig --release
# If GITHUB_EVENT_NAME is release or workflow_dispatch, publish the wheel
if [ "$GITHUB_EVENT_NAME" == "release" ] || [ "$GITHUB_EVENT_NAME" == "workflow_dispatch" ]; then
# aarch64-unknown-linux-gnu
rustup target add aarch64-unknown-linux-gnu
maturin publish --target aarch64-unknown-linux-gnu --skip-existing --zig
fi
# armv7-unknown-linux-musleabihf
rustup target add armv7-unknown-linux-musleabihf
maturin build --target armv7-unknown-linux-musleabihf --zig --release
# If GITHUB_EVENT_NAME is release or workflow_dispatch, publish the wheel
if [ "$GITHUB_EVENT_NAME" == "release" ] || [ "$GITHUB_EVENT_NAME" == "workflow_dispatch" ]; then
# armv7-unknown-linux-musleabihf
rustup target add armv7-unknown-linux-musleabihf
# If GITHUB_EVENT_NAME is release or workflow_dispatch, publish the wheel
maturin publish --target armv7-unknown-linux-musleabihf --skip-existing --zig
fi

# x86_64-pc-windows-gnu
rustup target add x86_64-pc-windows-gnu
maturin build --target x86_64-pc-windows-gnu --release
# If GITHUB_EVENT_NAME is release or workflow_dispatch, publish the wheel
if [ "$GITHUB_EVENT_NAME" == "release" ] || [ "$GITHUB_EVENT_NAME" == "workflow_dispatch" ]; then
# x86_64-pc-windows-gnu
rustup target add x86_64-pc-windows-gnu
# If GITHUB_EVENT_NAME is release or workflow_dispatch, publish the wheel
maturin publish --target x86_64-pc-windows-gnu --skip-existing
fi



+ 264
- 158
.github/workflows/release.yml View File

@@ -1,8 +1,43 @@
name: Release
# This file was autogenerated by dist: https://opensource.axo.dev/cargo-dist/
#
# Copyright 2022-2024, axodotdev
# SPDX-License-Identifier: MIT or Apache-2.0
#
# CI that:
#
# * checks for a Git Tag that looks like a release
# * builds artifacts with dist (archives, installers, hashes)
# * uploads those artifacts to temporary workflow zip
# * on success, uploads the artifacts to a GitHub Release
#
# Note that a GitHub Release with this tag is assumed to exist as a draft
# with the appropriate title/body, and will be undrafted for you.

name: Release
permissions:
contents: write
"contents": "write"

# This task will run whenever you push a git tag that looks like a version
# like "1.0.0", "v0.1.0-prerelease.1", "my-app/0.1.0", "releases/v1.0.0", etc.
# Various formats will be parsed into a VERSION and an optional PACKAGE_NAME, where
# PACKAGE_NAME must be the name of a Cargo package in your workspace, and VERSION
# must be a Cargo-style SemVer Version (must have at least major.minor.patch).
#
# If PACKAGE_NAME is specified, then the announcement will be for that
# package (erroring out if it doesn't have the given version or isn't dist-able).
#
# If PACKAGE_NAME isn't specified, then the announcement will be for all
# (dist-able) packages in the workspace with that version (this mode is
# intended for workspaces with only one dist-able package, or with all dist-able
# packages versioned/released in lockstep).
#
# If you push multiple tags at once, separate instances of this workflow will
# spin up, creating an independent announcement for each one. However, GitHub
# will hard limit this to 3 tags per commit, as it will assume more tags is a
# mistake.
#
# If there's a prerelease-style suffix to the version, then the release(s)
# will be marked as a prerelease.
on:
release:
types:
@@ -10,174 +45,245 @@ on:
workflow_dispatch:

jobs:
cargo-release:
name: "Cargo Release"

strategy:
matrix:
platform: [ubuntu-22.04]
fail-fast: false
runs-on: ${{ matrix.platform }}

# Run 'dist plan' (or host) to determine what tasks we need to do
plan:
runs-on: "ubuntu-22.04"
outputs:
val: ${{ steps.plan.outputs.manifest }}
tag: ${{ !github.event.pull_request && github.ref_name || '' }}
tag-flag: ${{ !github.event.pull_request && format('--tag={0}', github.ref_name) || '' }}
publishing: ${{ !github.event.pull_request }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v3

- uses: r7kamura/rust-problem-matchers@v1.1.0
- name: Free Disk Space (Ubuntu)
uses: jlumbroso/free-disk-space@main
if: runner.os == 'Linux'
with:
# this might remove tools that are actually needed,
# if set to "true" but frees about 6 GB
tool-cache: true

# all of these default to true, but feel free to set to
# "false" if necessary for your workflow
android: true
dotnet: true
haskell: true
large-packages: false
docker-images: true
swap-storage: true

- name: "Publish packages on `crates.io`"
if: runner.os == 'Linux'
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Install dist
# we specify bash to get pipefail; it guards against the `curl` command
# failing. otherwise `sh` won't catch that `curl` returned non-0
shell: bash
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.28.0/cargo-dist-installer.sh | sh"
- name: Cache dist
uses: actions/upload-artifact@v4
with:
name: cargo-dist-cache
path: ~/.cargo/bin/dist
# sure would be cool if github gave us proper conditionals...
# so here's a doubly-nested ternary-via-truthiness to try to provide the best possible
# functionality based on whether this is a pull_request, and whether it's from a fork.
# (PRs run on the *source* but secrets are usually on the *target* -- that's *good*
# but also really annoying to build CI around when it needs secrets to work right.)
- id: plan
run: |
dist ${{ (!github.event.pull_request && format('host --steps=create --tag={0}', github.ref_name)) || 'plan' }} --output-format=json > plan-dist-manifest.json
echo "dist ran successfully"
cat plan-dist-manifest.json
echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT"
- name: "Upload dist-manifest.json"
uses: actions/upload-artifact@v4
with:
name: artifacts-plan-dist-manifest
path: plan-dist-manifest.json

# Publishing those crates from outer crates with no dependency to inner crates
# As cargo is going to rebuild the crates based on published dependencies
# we need to publish those outer crates first to be able to test the publication
# of inner crates.
#
# We should preferably test pre-releases before testing releases as
# cargo publish might catch release issues that the workspace manages to fix using
# workspace crates.
publish_if_not_exists() {
local package_name=$1
local version=$(cargo metadata --no-deps --format-version=1 | jq -r '.packages[] | select(.name=="'"$package_name"'") | .version')

if [[ -z $version ]]; then
echo "error: package '$package_name' not found in the workspace."
return 1
fi

if cargo search "$package_name" | grep -q "^$package_name = \"$version\""; then
echo "package '$package_name' version '$version' already exists on crates.io. skipping publish."
else
echo "publishing package '$package_name' version '$version'..."
cargo publish --package "$package_name"
fi
}

# the dora-message package is versioned separately, so this publish command might fail if the version is already published
publish_if_not_exists dora-message

# Publish libraries crates
publish_if_not_exists dora-tracing
publish_if_not_exists dora-metrics
publish_if_not_exists dora-download
publish_if_not_exists dora-core
publish_if_not_exists communication-layer-pub-sub
publish_if_not_exists communication-layer-request-reply
publish_if_not_exists shared-memory-server
publish_if_not_exists dora-arrow-convert

# Publish rust API
publish_if_not_exists dora-operator-api-macros
publish_if_not_exists dora-operator-api-types
publish_if_not_exists dora-operator-api
publish_if_not_exists dora-node-api
publish_if_not_exists dora-operator-api-python
publish_if_not_exists dora-operator-api-c
publish_if_not_exists dora-node-api-c

# Publish binaries crates
publish_if_not_exists dora-coordinator
publish_if_not_exists dora-runtime
publish_if_not_exists dora-daemon
publish_if_not_exists dora-cli

# Publish ROS2 bridge
publish_if_not_exists dora-ros2-bridge-msg-gen
publish_if_not_exists dora-ros2-bridge
unix:
runs-on: ${{ matrix.platform.runner }}
# Build and packages all the platform-specific things
build-local-artifacts:
name: build-local-artifacts (${{ join(matrix.targets, ', ') }})
# Let the initial task tell us to not run (currently very blunt)
needs:
- plan
if: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix.include != null && (needs.plan.outputs.publishing == 'true' || fromJson(needs.plan.outputs.val).ci.github.pr_run_mode == 'upload') }}
strategy:
matrix:
platform:
- runner: ubuntu-22.04
target: x86_64-unknown-linux-gnu
- runner: ubuntu-22.04
target: i686-unknown-linux-gnu
- runner: ubuntu-22.04
target: aarch64-unknown-linux-gnu
- runner: ubuntu-22.04
target: aarch64-unknown-linux-musl
- runner: ubuntu-22.04
target: armv7-unknown-linux-musleabihf
- runner: macos-13
target: aarch64-apple-darwin
- runner: macos-13
target: x86_64-apple-darwin
fail-fast: false
# Target platforms/runners are computed by dist in create-release.
# Each member of the matrix has the following arguments:
#
# - runner: the github runner
# - dist-args: cli flags to pass to dist
# - install-dist: expression to run to install dist on the runner
#
# Typically there will be:
# - 1 "global" task that builds universal installers
# - N "local" tasks that build each platform's binaries and platform-specific installers
matrix: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix }}
runs-on: ${{ matrix.runner }}
container: ${{ matrix.container && matrix.container.image || null }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BUILD_MANIFEST_NAME: target/distrib/${{ join(matrix.targets, '-') }}-dist-manifest.json
steps:
- uses: actions/checkout@v3
- uses: r7kamura/rust-problem-matchers@v1.1.0
- name: "Add toolchains"
run: rustup target add ${{ matrix.platform.target }}
- name: "Build"
uses: actions-rs/cargo@v1
with:
use-cross: true
command: build
args: --release --target ${{ matrix.platform.target }} -p dora-cli
- name: "Archive"
run: zip -j -r ${{ matrix.platform.target }}.zip target/${{ matrix.platform.target }}/release/dora

- name: "Upload asset"
uses: actions/upload-release-asset@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: enable windows longpaths
run: |
git config --global core.longpaths true
- uses: actions/checkout@v4
with:
upload_url: ${{ github.event.release.upload_url }}
asset_path: ${{ matrix.platform.target }}.zip
asset_name: dora-${{ github.ref_name }}-${{ matrix.platform.target }}.zip
asset_content_type: application/zip

windows-release:
name: "Windows Release"

strategy:
matrix:
platform:
- runner: windows-2022
target: x86_64-pc-windows-msvc
submodules: recursive
- name: Install Rust non-interactively if not already installed
if: ${{ matrix.container }}
run: |
if ! command -v cargo > /dev/null 2>&1; then
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
fi
- name: Install dist
run: ${{ matrix.install_dist.run }}
# Get the dist-manifest
- name: Fetch local artifacts
uses: actions/download-artifact@v4
with:
pattern: artifacts-*
path: target/distrib/
merge-multiple: true
- name: Install dependencies
run: |
${{ matrix.packages_install }}
- name: Build artifacts
run: |
# Actually do builds and make zips and whatnot
dist build ${{ needs.plan.outputs.tag-flag }} --print=linkage --output-format=json ${{ matrix.dist_args }} > dist-manifest.json
echo "dist ran successfully"
- id: cargo-dist
name: Post-build
# We force bash here just because github makes it really hard to get values up
# to "real" actions without writing to env-vars, and writing to env-vars has
# inconsistent syntax between shell and powershell.
shell: bash
run: |
# Parse out what we just built and upload it to scratch storage
echo "paths<<EOF" >> "$GITHUB_OUTPUT"
dist print-upload-files-from-manifest --manifest dist-manifest.json >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"

fail-fast: false
runs-on: ${{ matrix.platform.runner }}
cp dist-manifest.json "$BUILD_MANIFEST_NAME"
- name: "Upload artifacts"
uses: actions/upload-artifact@v4
with:
name: artifacts-build-local-${{ join(matrix.targets, '_') }}
path: |
${{ steps.cargo-dist.outputs.paths }}
${{ env.BUILD_MANIFEST_NAME }}

# Build and package all the platform-agnostic(ish) things
build-global-artifacts:
needs:
- plan
- build-local-artifacts
runs-on: "ubuntu-22.04"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Install cached dist
uses: actions/download-artifact@v4
with:
name: cargo-dist-cache
path: ~/.cargo/bin/
- run: chmod +x ~/.cargo/bin/dist
# Get all the local artifacts for the global tasks to use (for e.g. checksums)
- name: Fetch local artifacts
uses: actions/download-artifact@v4
with:
pattern: artifacts-*
path: target/distrib/
merge-multiple: true
- id: cargo-dist
shell: bash
run: |
dist build ${{ needs.plan.outputs.tag-flag }} --output-format=json "--artifacts=global" > dist-manifest.json
echo "dist ran successfully"

- uses: r7kamura/rust-problem-matchers@v1.1.0
# Parse out what we just built and upload it to scratch storage
echo "paths<<EOF" >> "$GITHUB_OUTPUT"
jq --raw-output ".upload_files[]" dist-manifest.json >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"

- name: "Build binaries"
timeout-minutes: 60
run: "cargo build --release -p dora-cli"
cp dist-manifest.json "$BUILD_MANIFEST_NAME"
- name: "Upload artifacts"
uses: actions/upload-artifact@v4
with:
name: artifacts-build-global
path: |
${{ steps.cargo-dist.outputs.paths }}
${{ env.BUILD_MANIFEST_NAME }}
# Determines if we should publish/announce
host:
needs:
- plan
- build-local-artifacts
- build-global-artifacts
# Only run if we're "publishing", and only if local and global didn't fail (skipped is fine)
if: ${{ always() && needs.plan.outputs.publishing == 'true' && (needs.build-global-artifacts.result == 'skipped' || needs.build-global-artifacts.result == 'success') && (needs.build-local-artifacts.result == 'skipped' || needs.build-local-artifacts.result == 'success') }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
runs-on: "ubuntu-22.04"
outputs:
val: ${{ steps.host.outputs.manifest }}
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Install cached dist
uses: actions/download-artifact@v4
with:
name: cargo-dist-cache
path: ~/.cargo/bin/
- run: chmod +x ~/.cargo/bin/dist
# Fetch artifacts from scratch-storage
- name: Fetch artifacts
uses: actions/download-artifact@v4
with:
pattern: artifacts-*
path: target/distrib/
merge-multiple: true
- id: host
shell: bash
run: |
dist host ${{ needs.plan.outputs.tag-flag }} --steps=upload --steps=release --output-format=json > dist-manifest.json
echo "artifacts uploaded and released successfully"
cat dist-manifest.json
echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT"
- name: "Upload dist-manifest.json"
uses: actions/upload-artifact@v4
with:
# Overwrite the previous copy
name: artifacts-dist-manifest
path: dist-manifest.json
# Create a GitHub Release while uploading all files to it
- name: "Download GitHub Artifacts"
uses: actions/download-artifact@v4
with:
pattern: artifacts-*
path: artifacts
merge-multiple: true
- name: Cleanup
run: |
# Remove the granular manifests
rm -f artifacts/*-dist-manifest.json
- name: Create GitHub Release
env:
PRERELEASE_FLAG: "${{ fromJson(steps.host.outputs.manifest).announcement_is_prerelease && '--prerelease' || '' }}"
RELEASE_COMMIT: "${{ github.sha }}"
run: |
# If we're editing a release in place, we need to upload things ahead of time
gh release upload "${{ needs.plan.outputs.tag }}" artifacts/*

- name: Create Archive (Windows)
if: runner.os == 'Windows'
shell: powershell
run: Compress-Archive -Path target/release/dora.exe -DestinationPath archive.zip
gh release edit "${{ needs.plan.outputs.tag }}" --target "$RELEASE_COMMIT" $PRERELEASE_FLAG --draft=false

- name: "Upload release asset"
uses: actions/upload-release-asset@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
announce:
needs:
- plan
- host
# use "always() && ..." to allow us to wait for all publish jobs while
# still allowing individual publish jobs to skip themselves (for prereleases).
# "host" however must run to completion, no skipping allowed!
if: ${{ always() && needs.host.result == 'success' }}
runs-on: "ubuntu-22.04"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v4
with:
upload_url: ${{ github.event.release.upload_url }}
asset_path: archive.zip
asset_name: dora-${{ github.ref_name }}-${{ matrix.platform.target }}.zip
asset_content_type: application/zip
submodules: recursive

+ 3
- 0
.gitmodules View File

@@ -1,3 +1,6 @@
[submodule "node-hub/dora-rdt-1b/dora_rdt_1b/RoboticsDiffusionTransformer"]
path = node-hub/dora-rdt-1b/dora_rdt_1b/RoboticsDiffusionTransformer
url = https://github.com/thu-ml/RoboticsDiffusionTransformer
[submodule "node-hub/dora-magma/dora_magma/Magma"]
path = node-hub/dora-magma/dora_magma/Magma
url = https://github.com/microsoft/Magma

+ 3679
- 1910
Cargo.lock
File diff suppressed because it is too large
View File


+ 11
- 0
Cargo.toml View File

@@ -36,10 +36,12 @@ members = [
"node-hub/openai-proxy-server",
"node-hub/dora-kit-car",
"node-hub/dora-object-to-pose",
"node-hub/dora-mistral-rs",
"libraries/extensions/ros2-bridge",
"libraries/extensions/ros2-bridge/msg-gen",
"libraries/extensions/ros2-bridge/python",
"tests/queue_size_latest_data_rust/receive_data",

]

[workspace.package]
@@ -144,6 +146,10 @@ path = "examples/rust-dataflow-url/run.rs"
name = "cxx-dataflow"
path = "examples/c++-dataflow/run.rs"

[[example]]
name = "cxx-arrow-dataflow"
path = "examples/c++-arrow-dataflow/run.rs"

[[example]]
name = "python-dataflow"
path = "examples/python-dataflow/run.rs"
@@ -173,3 +179,8 @@ path = "examples/cmake-dataflow/run.rs"
name = "cxx-ros2-dataflow"
path = "examples/c++-ros2-dataflow/run.rs"
required-features = ["ros2-examples"]

# The profile that 'dist' will build with
[profile.dist]
inherits = "release"
lto = "thin"

+ 60
- 124
README.md View File

@@ -89,130 +89,66 @@

> Feel free to modify this README with your own nodes so that it benefits the community.

### Camera

| Title | Support | Description | Downloads | License | Release |
| ---------------------------------------------------------------------------------------- | ------------------ | ----------------------------------- | ----------------------------------------------------------------- | -------------------------------------------------------------- | -------------------------------------------------------------- |
| [PyOrbbeckSDK](https://github.com/dora-rs/dora/blob/main/node-hub/dora-pyorbbecksdk) | 📐 | Image and depth from Orbbeck Camera | ![Downloads](https://img.shields.io/pypi/dm/dora-pyorbbecksdk) | ![License](https://img.shields.io/pypi/l/dora-pyorbbecksdk) | ![Release](https://img.shields.io/pypi/v/dora-pyorbbecksdk) |
| [PyRealsense](https://github.com/dora-rs/dora/blob/main/node-hub/dora-pyrealsense) | Linux🆗 <br> Mac🛠️ | Image and depth from Realsense | ![Downloads](https://img.shields.io/pypi/dm/dora-pyrealsense) | ![License](https://img.shields.io/pypi/l/dora-pyrealsense) | ![Release](https://img.shields.io/pypi/v/dora-pyrealsense) |
| [Video Capture](https://github.com/dora-rs/dora/blob/main/node-hub/opencv-video-capture) | ✅ | Image stream from Camera | ![Downloads](https://img.shields.io/pypi/dm/opencv-video-capture) | ![License](https://img.shields.io/pypi/l/opencv-video-capture) | ![Release](https://img.shields.io/pypi/v/opencv-video-capture) |

### Peripheral

| Title | Support | Description | Downloads | License | Release |
| ----------------------------------------------------------------------------------- | ------- | ------------------------- | ------------------------------------------------------------ | --------------------------------------------------------- | --------------------------------------------------------- |
| [Keyboard](https://github.com/dora-rs/dora/blob/main/node-hub/dora-keyboard) | ✅ | Keyboard char listener | ![Downloads](https://img.shields.io/pypi/dm/dora-keyboard) | ![License](https://img.shields.io/pypi/l/dora-keyboard) | ![Release](https://img.shields.io/pypi/v/dora-keyboard) |
| [Microphone](https://github.com/dora-rs/dora/blob/main/node-hub/dora-microphone) | ✅ | Audio from microphone | ![Downloads](https://img.shields.io/pypi/dm/dora-microphone) | ![License](https://img.shields.io/pypi/l/dora-microphone) | ![Release](https://img.shields.io/pypi/v/dora-microphone) |
| [PyAudio(Speaker)](https://github.com/dora-rs/dora/blob/main/node-hub/dora-pyaudio) | ✅ | Output audio from speaker | ![Downloads](https://img.shields.io/pypi/dm/dora-pyaudio) | ![License](https://img.shields.io/pypi/l/dora-pyaudio) | ![Release](https://img.shields.io/pypi/v/dora-pyaudio) |

### Actuator

| Title | Support | Description | Downloads | License | Release |
| ---------------------------------------------------------------------------------------- | ------- | ---------------- | --------- | ------- | ------- |
| [Feetech](https://github.com/dora-rs/dora-lerobot/blob/main/node-hub/feetech-client) | 📐 | Feetech Client | | | |
| [Dynamixel](https://github.com/dora-rs/dora-lerobot/blob/main/node-hub/dynamixel-client) | 📐 | Dynamixel Client | | | |

### Chassis

| Title | Support | Description | Downloads | License | Release |
| ------------------------------------------------------------------------------- | ------- | ------------------- | --------------------------------------------------------- | ------------------------------------------------------ | ------------------------------------------------------ |
| [Agilex - UGV](https://github.com/dora-rs/dora/blob/main/node-hub/dora-ugv) | 🆗 | Robomaster Client | ![Downloads](https://img.shields.io/pypi/dm/dora-ugv) | ![License](https://img.shields.io/pypi/l/dora-ugv) | ![Release](https://img.shields.io/pypi/v/dora-ugv) |
| [DJI - Robomaster S1](https://huggingface.co/datasets/dora-rs/dora-robomaster) | 📐 | Robomaster Client | | | |
| [Dora Kit Car](https://github.com/dora-rs/dora/blob/main/node-hub/dora-kit-car) | 🆗 | Open Source Chassis | ![Downloads](https://img.shields.io/pypi/dm/dora-kit-car) | ![License](https://img.shields.io/pypi/l/dora-kit-car) | ![Release](https://img.shields.io/pypi/v/dora-kit-car) |

### Arm

| Title | Support | Description | Downloads | License | Release |
| ------------------------------------------------------------------------------------------------ | ------- | --------------------------------- | ------------------------------------------------------- | ---------------------------------------------------- | ---------------------------------------------------- |
| [Alex Koch - Low Cost Robot](https://github.com/dora-rs/dora-lerobot/blob/main/robots/alexk-lcr) | 📐 | Alex Koch - Low Cost Robot Client | | | |
| [Lebai - LM3](https://github.com/dora-rs/dora-lerobot/blob/main/node-hub/lebai-client) | 📐 | Lebai client | | | |
| [Agilex - Piper](https://github.com/dora-rs/dora/blob/main/node-hub/dora-piper) | 🆗 | Agilex arm client | ![Downloads](https://img.shields.io/pypi/dm/dora-piper) | ![License](https://img.shields.io/pypi/l/dora-piper) | ![Release](https://img.shields.io/pypi/v/dora-piper) |

### Robot

| Title | Support | Description | Downloads | License | Release |
| -------------------------------------------------------------------------------------------- | ------- | --------------- | --------------------------------------------------------- | ------------------------------------------------------ | ------------------------------------------------------ |
| [Pollen - Reachy 1](https://github.com/dora-rs/dora-lerobot/blob/main/node-hub/dora-reachy1) | 📐 | Reachy 1 Client | | | |
| [Pollen - Reachy 2](https://github.com/dora-rs/dora/blob/main/node-hub/dora-reachy2) | 🆗 | Reachy 2 client | ![Downloads](https://img.shields.io/pypi/dm/dora-reachy2) | ![License](https://img.shields.io/pypi/l/dora-reachy2) | ![Release](https://img.shields.io/pypi/v/dora-reachy2) |
| [Trossen - Aloha](https://github.com/dora-rs/dora-lerobot/blob/main/robots/aloha) | 📐 | Aloha client | | | |

### Voice Activity Detection(VAD)

| Title | Support | Description | Downloads | License | Release |
| ------------------------------------------------------------------------- | ------- | ------------------------------- | ----------------------------------------------------- | -------------------------------------------------- | -------------------------------------------------- |
| [Silero VAD](https://github.com/dora-rs/dora/blob/main/node-hub/dora-vad) | ✅ | Silero Voice activity detection | ![Downloads](https://img.shields.io/pypi/dm/dora-vad) | ![License](https://img.shields.io/pypi/l/dora-vad) | ![Release](https://img.shields.io/pypi/v/dora-vad) |

### Speech to Text(STT)

| Title | Support | Description | Downloads | License | Release |
| --------------------------------------------------------------------------------- | ------- | ------------------------ | ---------------------------------------------------------------- | ------------------------------------------------------------- | ------------------------------------------------------------- |
| [Whisper](https://github.com/dora-rs/dora/blob/main/node-hub/dora-distil-whisper) | ✅ | Transcribe audio to text | ![Downloads](https://img.shields.io/pypi/dm/dora-distil-whisper) | ![License](https://img.shields.io/pypi/l/dora-distil-whisper) | ![Release](https://img.shields.io/pypi/v/dora-distil-whisper) |

### Object Detection

| Title | Support | Description | Downloads | License | Release |
| ---------------------------------------------------------------------- | ------- | ---------------- | ------------------------------------------------------ | --------------------------------------------------- | --------------------------------------------------- |
| [Yolov8](https://github.com/dora-rs/dora/blob/main/node-hub/dora-yolo) | ✅ | Object detection | ![Downloads](https://img.shields.io/pypi/dm/dora-yolo) | ![License](https://img.shields.io/pypi/l/dora-yolo) | ![Release](https://img.shields.io/pypi/v/dora-yolo) |

### Segmentation

| Title | Support | Description | Downloads | License | Release |
| -------------------------------------------------------------------- | ------------------- | ---------------- | ------------------------------------------------------ | --------------------------------------------------- | --------------------------------------------------- |
| [SAM2](https://github.com/dora-rs/dora/blob/main/node-hub/dora-sam2) | Cuda✅ <br> Metal🛠️ | Segment Anything | ![Downloads](https://img.shields.io/pypi/dm/dora-sam2) | ![License](https://img.shields.io/pypi/l/dora-sam2) | ![Release](https://img.shields.io/pypi/v/dora-sam2) |

### Large Language Model(LLM)

| Title | Support | Description | Downloads | License | Release |
| ----------------------------------------------------------------------- | ------- | ------------------------------- | ------------------------------------------------------ | --------------------------------------------------- | --------------------------------------------------- |
| [Qwen2.5](https://github.com/dora-rs/dora/blob/main/node-hub/dora-qwen) | ✅ | Large Language Model using Qwen | ![Downloads](https://img.shields.io/pypi/dm/dora-qwen) | ![License](https://img.shields.io/pypi/l/dora-qwen) | ![Release](https://img.shields.io/pypi/v/dora-qwen) |

### Vision Language Model(VLM)

| Title | Support | Description | Downloads | License | Release |
| -------------------------------------------------------------------------------- | ------- | -------------------------------------- | ------------------------------------------------------------ | --------------------------------------------------------- | --------------------------------------------------------- |
| [Qwen2.5-vl](https://github.com/dora-rs/dora/blob/main/node-hub/dora-qwen2-5-vl) | ✅ | Vision Language Model using Qwen2.5 VL | ![Downloads](https://img.shields.io/pypi/dm/dora-qwen2-5-vl) | ![License](https://img.shields.io/pypi/l/dora-qwen2-5-vl) | ![Release](https://img.shields.io/pypi/v/dora-qwen2-5-vl) |
| [InternVL](https://github.com/dora-rs/dora/blob/main/node-hub/dora-internvl) | 🆗 | InternVL is a vision language model | ![Downloads](https://img.shields.io/pypi/dm/dora-internvl) | ![License](https://img.shields.io/pypi/l/dora-internvl) | ![Release](https://img.shields.io/pypi/v/dora-internvl) |

### Vision Language Action(VLA)

| Title | Support | Description | Downloads | License | Release |
| ------------------------------------------------------------------------ | ------- | ------------------------------------------------ | -------------------------------------------------------- | ----------------------------------------------------- | ----------------------------------------------------- |
| [RDT-1B](https://github.com/dora-rs/dora/blob/main/node-hub/dora-rdt-1b) | 🆗 | Infer policy using Robotic Diffusion Transformer | ![Downloads](https://img.shields.io/pypi/dm/dora-rdt-1b) | ![License](https://img.shields.io/pypi/l/dora-rdt-1b) | ![Release](https://img.shields.io/pypi/v/dora-rdt-1b) |

### Translation

| Title | Support | Description | Downloads | License | Release |
| --------------------------------------------------------------------------------------- | ------- | ------------------------------- | --------------------------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
| [ArgosTranslate](https://github.com/dora-rs/dora/blob/main/node-hub/dora-argotranslate) | 🆗 | Open Source translation engine | ![Downloads](https://img.shields.io/pypi/dm/dora-argotranslate) | ![License](https://img.shields.io/pypi/l/dora-argotranslate) | ![Release](https://img.shields.io/pypi/v/dora-argotranslate) |
| [Opus MT](https://github.com/dora-rs/dora/blob/main/node-hub/dora-opus) | 🆗 | Translate text between language | ![Downloads](https://img.shields.io/pypi/dm/dora-opus) | ![License](https://img.shields.io/pypi/l/dora-opus) | ![Release](https://img.shields.io/pypi/v/dora-opus) |

### Text to Speech(TTS)

| Title | Support | Description | Downloads | License | Release |
| -------------------------------------------------------------------------------- | ------- | ------------------------ | ------------------------------------------------------------ | --------------------------------------------------------- | --------------------------------------------------------- |
| [Kokoro TTS](https://github.com/dora-rs/dora/blob/main/node-hub/dora-kokoro-tts) | ✅ | Efficient Text to Speech | ![Downloads](https://img.shields.io/pypi/dm/dora-kokoro-tts) | ![License](https://img.shields.io/pypi/l/dora-kokoro-tts) | ![Release](https://img.shields.io/pypi/v/dora-kokoro-tts) |

### Recorder

| Title | Support | Description | Downloads | License | Release |
| --------------------------------------------------------------------------------------------------- | ------- | -------------------------------- | ------------------------------------------------------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------- |
| [Llama Factory Recorder](https://github.com/dora-rs/dora/blob/main/node-hub/llama-factory-recorder) | 🆗 | Record data to train LLM and VLM | ![Downloads](https://img.shields.io/pypi/dm/llama-factory-recorder) | ![License](https://img.shields.io/pypi/l/llama-factory-recorder) | ![Release](https://img.shields.io/pypi/v/llama-factory-recorder) |
| [LeRobot Recorder](https://github.com/dora-rs/dora-lerobot/blob/main/node-hub/lerobot-dashboard) | 📐 | LeRobot Recorder helper | | | |

### Visualization

| Title | Support | Description | Downloads | License | Release |
| ---------------------------------------------------------------------- | ------- | -------------------------------- | ------------------------------------------------------- | ----------------------------------------------------- | ----------------------------------------------------- |
| [Plot](https://github.com/dora-rs/dora/blob/main/node-hub/opencv-plot) | ✅ | Simple OpenCV plot visualization | ![Downloads](https://img.shields.io/pypi/dm/dora-yolo) | ![License](https://img.shields.io/pypi/l/opencv-plot) | ![Release](https://img.shields.io/pypi/v/opencv-plot) |
| [Rerun](https://github.com/dora-rs/dora/blob/main/node-hub/dora-rerun) | ✅ | Visualization tool | ![Downloads](https://img.shields.io/pypi/dm/dora-rerun) | ![License](https://img.shields.io/pypi/l/dora-rerun) | ![Release](https://img.shields.io/pypi/v/dora-rerun) |

### Simulator

| Title | Support | Description | Downloads | License | Release |
| ---------------------------------------------------------------------------------- | ------- | ------------------------------------ | --------- | ------- | ------- |
| [Mujoco](https://github.com/dora-rs/dora-lerobot/blob/main/node-hub/mujoco-client) | 📐 | Mujoco Simulator | | | |
| [Carla](https://github.com/dora-rs/dora-drives) | 📐 | Carla Simulator | | | |
| [Gymnasium](https://github.com/dora-rs/dora-lerobot/blob/main/gym_dora) | 📐 | Experimental OpenAI Gymnasium bridge | | | |
| Type | Title | Support | Description | Downloads | License |
| ----------------------------- | --------------------------------------------------------------------------------------------------- | ------------------- | ------------------------------------------------ | ----------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| Camera | [PyOrbbeckSDK](https://github.com/dora-rs/dora/blob/main/node-hub/dora-pyorbbecksdk) | 📐 | Image and depth from Orbbeck Camera | ![Downloads](https://img.shields.io/pypi/dm/dora-pyorbbecksdk?label=%20) | ![License](https://img.shields.io/pypi/l/dora-pyorbbecksdk?label=%20) |
| Camera | [PyRealsense](https://github.com/dora-rs/dora/blob/main/node-hub/dora-pyrealsense) | Linux🆗 <br> Mac🛠️ | Image and depth from Realsense | ![Downloads](https://img.shields.io/pypi/dm/dora-pyrealsense?label=%20) | ![License](https://img.shields.io/pypi/l/dora-pyrealsense?label=%20) |
| Camera | [OpenCV Video Capture](https://github.com/dora-rs/dora/blob/main/node-hub/opencv-video-capture) | ✅ | Image stream from OpenCV Camera | ![Downloads](https://img.shields.io/pypi/dm/opencv-video-capture?label=%20) | ![License](https://img.shields.io/pypi/l/opencv-video-capture?label=%20) |
| Peripheral | [Keyboard](https://github.com/dora-rs/dora/blob/main/node-hub/dora-keyboard) | ✅ | Keyboard char listener | ![Downloads](https://img.shields.io/pypi/dm/dora-keyboard?label=%20) | ![License](https://img.shields.io/pypi/l/dora-keyboard?label=%20) |
| Peripheral | [Microphone](https://github.com/dora-rs/dora/blob/main/node-hub/dora-microphone) | ✅ | Audio from microphone | ![Downloads](https://img.shields.io/pypi/dm/dora-microphone?label=%20) | ![License](https://img.shields.io/pypi/l/dora-microphone?label=%20) |
| Peripheral | [PyAudio(Speaker)](https://github.com/dora-rs/dora/blob/main/node-hub/dora-pyaudio) | ✅ | Output audio from speaker | ![Downloads](https://img.shields.io/pypi/dm/dora-pyaudio?label=%20) | ![License](https://img.shields.io/pypi/l/dora-pyaudio?label=%20) |
| Actuator | [Feetech](https://github.com/dora-rs/dora-lerobot/blob/main/node-hub/feetech-client) | 📐 | Feetech Client | | |
| Actuator | [Dynamixel](https://github.com/dora-rs/dora-lerobot/blob/main/node-hub/dynamixel-client) | 📐 | Dynamixel Client | | |
| Chassis | [Agilex - UGV](https://github.com/dora-rs/dora/blob/main/node-hub/dora-ugv) | 🆗 | Robomaster Client | ![Downloads](https://img.shields.io/pypi/dm/dora-ugv?label=%20) | ![License](https://img.shields.io/pypi/l/dora-ugv?label=%20) |
| Chassis | [DJI - Robomaster S1](https://huggingface.co/datasets/dora-rs/dora-robomaster) | 📐 | Robomaster Client | | |
| Chassis | [Dora Kit Car](https://github.com/dora-rs/dora/blob/main/node-hub/dora-kit-car) | 🆗 | Open Source Chassis | ![Downloads](https://img.shields.io/pypi/dm/dora-kit-car?label=%20) | ![License](https://img.shields.io/pypi/l/dora-kit-car?label=%20) |
| Arm | [Alex Koch - Low Cost Robot](https://github.com/dora-rs/dora-lerobot/blob/main/robots/alexk-lcr) | 📐 | Alex Koch - Low Cost Robot Client | | |
| Arm | [Lebai - LM3](https://github.com/dora-rs/dora-lerobot/blob/main/node-hub/lebai-client) | 📐 | Lebai client | | |
| Arm | [Agilex - Piper](https://github.com/dora-rs/dora/blob/main/node-hub/dora-piper) | 🆗 | Agilex arm client | ![Downloads](https://img.shields.io/pypi/dm/dora-piper?label=%20) | ![License](https://img.shields.io/pypi/l/dora-piper?label=%20) |
| Robot | [Pollen - Reachy 1](https://github.com/dora-rs/dora-lerobot/blob/main/node-hub/dora-reachy1) | 📐 | Reachy 1 Client | | |
| Robot | [Pollen - Reachy 2](https://github.com/dora-rs/dora/blob/main/node-hub/dora-reachy2) | 🆗 | Reachy 2 client | ![Downloads](https://img.shields.io/pypi/dm/dora-reachy2?label=%20) | ![License](https://img.shields.io/pypi/l/dora-reachy2?label=%20) |
| Robot | [Trossen - Aloha](https://github.com/dora-rs/dora-lerobot/blob/main/robots/aloha) | 📐 | Aloha client | | |
| Voice Activity Detection(VAD) | [Silero VAD](https://github.com/dora-rs/dora/blob/main/node-hub/dora-vad) | ✅ | Silero Voice activity detection | ![Downloads](https://img.shields.io/pypi/dm/dora-vad?label=%20) | ![License](https://img.shields.io/pypi/l/dora-vad?label=%20) |
| Speech to Text(STT) | [Whisper](https://github.com/dora-rs/dora/blob/main/node-hub/dora-distil-whisper) | ✅ | Transcribe audio to text | ![Downloads](https://img.shields.io/pypi/dm/dora-distil-whisper?label=%20) | ![License](https://img.shields.io/pypi/l/dora-distil-whisper?label=%20) |
| Object Detection | [Yolov8](https://github.com/dora-rs/dora/blob/main/node-hub/dora-yolo) | ✅ | Object detection | ![Downloads](https://img.shields.io/pypi/dm/dora-yolo?label=%20) | ![License](https://img.shields.io/pypi/l/dora-yolo?label=%20) |
| Segmentation | [SAM2](https://github.com/dora-rs/dora/blob/main/node-hub/dora-sam2) | Cuda✅ <br> Metal🛠️ | Segment Anything | ![Downloads](https://img.shields.io/pypi/dm/dora-sam2?label=%20) | ![License](https://img.shields.io/pypi/l/dora-sam2?label=%20) |
| Large Language Model(LLM) | [Qwen2.5](https://github.com/dora-rs/dora/blob/main/node-hub/dora-qwen) | ✅ | Large Language Model using Qwen | ![Downloads](https://img.shields.io/pypi/dm/dora-qwen?label=%20) | ![License](https://img.shields.io/pypi/l/dora-qwen?label=%20) |
| Vision Language Model(VLM) | [Qwen2.5-vl](https://github.com/dora-rs/dora/blob/main/node-hub/dora-qwen2-5-vl) | ✅ | Vision Language Model using Qwen2.5 VL | ![Downloads](https://img.shields.io/pypi/dm/dora-qwen2-5-vl?label=%20) | ![License](https://img.shields.io/pypi/l/dora-qwen2-5-vl?label=%20) |
| Vision Language Model(VLM) | [InternVL](https://github.com/dora-rs/dora/blob/main/node-hub/dora-internvl) | 🆗 | InternVL is a vision language model | ![Downloads](https://img.shields.io/pypi/dm/dora-internvl?label=%20) | ![License](https://img.shields.io/pypi/l/dora-internvl?label=%20) |
| Vision Language Action(VLA) | [RDT-1B](https://github.com/dora-rs/dora/blob/main/node-hub/dora-rdt-1b) | 🆗 | Infer policy using Robotic Diffusion Transformer | ![Downloads](https://img.shields.io/pypi/dm/dora-rdt-1b?label=%20) | ![License](https://img.shields.io/pypi/l/dora-rdt-1b?label=%20) |
| Translation | [ArgosTranslate](https://github.com/dora-rs/dora/blob/main/node-hub/dora-argotranslate) | 🆗 | Open Source translation engine | ![Downloads](https://img.shields.io/pypi/dm/dora-argotranslate?label=%20) | ![License](https://img.shields.io/pypi/l/dora-argotranslate?label=%20) |
| Translation | [Opus MT](https://github.com/dora-rs/dora/blob/main/node-hub/dora-opus) | 🆗 | Translate text between language | ![Downloads](https://img.shields.io/pypi/dm/dora-opus?label=%20) | ![License](https://img.shields.io/pypi/l/dora-opus?label=%20) |
| Text to Speech(TTS) | [Kokoro TTS](https://github.com/dora-rs/dora/blob/main/node-hub/dora-kokoro-tts) | ✅ | Efficient Text to Speech | ![Downloads](https://img.shields.io/pypi/dm/dora-kokoro-tts?label=%20) | ![License](https://img.shields.io/pypi/l/dora-kokoro-tts?label=%20) |
| Recorder | [Llama Factory Recorder](https://github.com/dora-rs/dora/blob/main/node-hub/llama-factory-recorder) | 🆗 | Record data to train LLM and VLM | ![Downloads](https://img.shields.io/pypi/dm/llama-factory-recorder?label=%20) | ![License](https://img.shields.io/pypi/l/llama-factory-recorder?label=%20) |
| Recorder | [LeRobot Recorder](https://github.com/dora-rs/dora-lerobot/blob/main/node-hub/lerobot-dashboard) | 📐 | LeRobot Recorder helper | | |
| Visualization | [Plot](https://github.com/dora-rs/dora/blob/main/node-hub/opencv-plot) | ✅ | Simple OpenCV plot visualization | ![Downloads](https://img.shields.io/pypi/dm/dora-yolo?label=%20) | ![License](https://img.shields.io/pypi/l/opencv-plot?label=%20) |
| Visualization | [Rerun](https://github.com/dora-rs/dora/blob/main/node-hub/dora-rerun) | ✅ | Visualization tool | ![Downloads](https://img.shields.io/pypi/dm/dora-rerun?label=%20) | ![License](https://img.shields.io/pypi/l/dora-rerun?label=%20) |
| Simulator | [Mujoco](https://github.com/dora-rs/dora-lerobot/blob/main/node-hub/mujoco-client) | 📐 | Mujoco Simulator | | |
| Simulator | [Carla](https://github.com/dora-rs/dora-drives) | 📐 | Carla Simulator | | |
| Simulator | [Gymnasium](https://github.com/dora-rs/dora-lerobot/blob/main/gym_dora) | 📐 | Experimental OpenAI Gymnasium bridge | | |

## Examples

| Type | Title | Description | Last Commit |
| -------------- | ------------------------------------------------------------------------------------------------------------ | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| Audio | [Speech to Text(STT)](https://github.com/dora-rs/dora/blob/main/examples/speech-to-text) | Transform speech to text. | ![License](https://img.shields.io/github/last-commit/dora-rs/dora?path=examples%2Fspeech-to-text&label=%20) |
| Audio | [Translation](https://github.com/dora-rs/dora/blob/main/examples/translation) | Translate audio in real time. | ![License](https://img.shields.io/github/last-commit/dora-rs/dora?path=examples%2Ftranslation&label=%20) |
| Vision | [Vision Language Model(VLM)](https://github.com/dora-rs/dora/blob/main/examples/vlm) | Use a VLM to understand images. | ![License](https://img.shields.io/github/last-commit/dora-rs/dora?path=examples%2Fvlm&label=%20) |
| Vision | [YOLO](https://github.com/dora-rs/dora/blob/main/examples/python-dataflow) | Use YOLO to detect object within image. | ![License](https://img.shields.io/github/last-commit/dora-rs/dora?path=examples%2Fpython-dataflow&label=%20) |
| Vision | [Camera](https://github.com/dora-rs/dora/blob/main/examples/camera) | Simple webcam plot example | ![License](https://img.shields.io/github/last-commit/dora-rs/dora?path=examples%2Fcamera&label=%20) |
| Model Training | [Piper RDT](https://github.com/dora-rs/dora/blob/main/examples/piper) | Piper RDT Pipeline | ![License](https://img.shields.io/github/last-commit/dora-rs/dora?path=examples%2Fpiper&label=%20) |
| Model Training | [LeRobot - Alexander Koch](https://raw.githubusercontent.com/dora-rs/dora-lerobot/refs/heads/main/README.md) | Training Alexander Koch Low Cost Robot with LeRobot | ![License](https://img.shields.io/github/last-commit/dora-rs/dora-lerobot?path=robots&label=%20) |
| ROS2 | [C++ ROS2 Example](https://github.com/dora-rs/dora/blob/main/examples/c++-ros2-dataflow) | Example using C++ ROS2 | ![License](https://img.shields.io/github/last-commit/dora-rs/dora?path=examples%2Fc%2b%2b-ros2-dataflow&label=%20) |
| ROS2 | [Rust ROS2 Example](https://github.com/dora-rs/dora/blob/main/examples/rust-ros2-dataflow) | Example using Rust ROS2 | ![License](https://img.shields.io/github/last-commit/dora-rs/dora?path=examples%2Frust-ros2-dataflow&label=%20) |
| ROS2 | [Python ROS2 Example](https://github.com/dora-rs/dora/blob/main/examples/python-ros2-dataflow) | Example using Python ROS2 | ![License](https://img.shields.io/github/last-commit/dora-rs/dora?path=examples%2Fpython-ros2-dataflow&label=%20) |
| Benchmark | [GPU Benchmark](https://github.com/dora-rs/dora/blob/main/examples/cuda-benchmark) | GPU Benchmark of dora-rs | ![License](https://img.shields.io/github/last-commit/dora-rs/dora?path=examples%2Fcuda-benchmark&label=%20) |
| Benchmark | [CPU Benchmark](https://github.com/dora-rs/dora-benchmark/blob/main) | CPU Benchmark of dora-rs | ![License](https://img.shields.io/github/last-commit/dora-rs/dora-benchmark?path=dora-rs&label=%20) |
| Tutorial | [Rust Example](https://github.com/dora-rs/dora/blob/main/examples/rust-dataflow) | Example using Rust | ![License](https://img.shields.io/github/last-commit/dora-rs/dora?path=examples%2Frust-dataflow&label=%20) |
| Tutorial | [Python Example](https://github.com/dora-rs/dora/blob/main/examples/python-dataflow) | Example using Python | ![License](https://img.shields.io/github/last-commit/dora-rs/dora?path=examples%2Fpython-dataflow&label=%20) |
| Tutorial | [CMake Example](https://github.com/dora-rs/dora/blob/main/examples/cmake-dataflow) | Example using CMake | ![License](https://img.shields.io/github/last-commit/dora-rs/dora?path=examples%2Fcmake-dataflow&label=%20) |
| Tutorial | [C Example](https://github.com/dora-rs/dora/blob/main/examples/c-dataflow) | Example with C node | ![License](https://img.shields.io/github/last-commit/dora-rs/dora?path=examples%2Fc-dataflow&label=%20) |
| Tutorial | [CUDA Example](https://github.com/dora-rs/dora/blob/main/examples/cuda-benchmark) | Example using CUDA Zero Copy | ![License](https://img.shields.io/github/last-commit/dora-rs/dora?path=examples%2Fcuda-benchmark&label=%20) |
| Tutorial | [C++ Example](https://github.com/dora-rs/dora/blob/main/examples/c++-dataflow) | Example with C++ node | ![License](https://img.shields.io/github/last-commit/dora-rs/dora?path=examples%2Fc%2b%2b-dataflow&label=%20) |

## Getting Started



+ 1
- 0
_typos.toml View File

@@ -1,3 +1,4 @@
[default.extend-identifiers]
# *sigh* this just isn't worth the cost of fixing
DeviceNDArray = "DeviceNDArray"
Feedforward_2nd_Gain = "Feedforward_2nd_Gain"

+ 1
- 0
apis/c++/node/Cargo.toml View File

@@ -32,6 +32,7 @@ dora-ros2-bridge = { workspace = true, optional = true }
futures-lite = { version = "2.2" }
serde = { version = "1.0.164", features = ["derive"], optional = true }
serde-big-array = { version = "0.5.1", optional = true }
arrow = { workspace = true, features = ["ffi"] }

[build-dependencies]
cxx-build = "1.0.73"


+ 20
- 12
apis/c++/node/build.rs View File

@@ -9,10 +9,7 @@ fn main() {
println!("cargo:rerun-if-changed=src/lib.rs");

// rename header files
let src_dir = target_dir()
.join("cxxbridge")
.join("dora-node-api-cxx")
.join("src");
let src_dir = origin_dir();
let target_dir = src_dir.parent().unwrap();
std::fs::copy(src_dir.join("lib.rs.h"), target_dir.join("dora-node-api.h")).unwrap();
std::fs::copy(
@@ -28,8 +25,8 @@ fn main() {
bridge_files.clear();
}

fn target_dir() -> PathBuf {
std::env::var("CARGO_TARGET_DIR")
fn origin_dir() -> PathBuf {
let default_target = std::env::var("CARGO_TARGET_DIR")
.map(PathBuf::from)
.unwrap_or_else(|_| {
let root = Path::new(env!("CARGO_MANIFEST_DIR"))
@@ -37,12 +34,26 @@ fn target_dir() -> PathBuf {
.nth(3)
.unwrap();
root.join("target")
})
});
let cross_target = default_target
.join(std::env::var("TARGET").unwrap())
.join("cxxbridge")
.join("dora-node-api-cxx")
.join("src");

if cross_target.exists() {
cross_target
} else {
default_target
.join("cxxbridge")
.join("dora-node-api-cxx")
.join("src")
}
}

#[cfg(feature = "ros2-bridge")]
mod ros2 {
use super::target_dir;
use super::origin_dir;
use std::{
io::{BufRead, BufReader},
path::{Component, Path, PathBuf},
@@ -113,10 +124,7 @@ mod ros2 {
.join("ros2_bindings.rs.cc");

// copy message files to target directory
let target_path = target_dir()
.join("cxxbridge")
.join("dora-node-api-cxx")
.join("dora-ros2-bindings.h");
let target_path = origin_dir().parent().unwrap().join("dora-ros2-bindings.h");

std::fs::copy(&header_path, &target_path).unwrap();
println!("cargo:rerun-if-changed={}", header_path.display());


+ 100
- 0
apis/c++/node/src/lib.rs View File

@@ -71,9 +71,26 @@ mod ffi {

fn is_dora(self: &CombinedEvent) -> bool;
fn downcast_dora(event: CombinedEvent) -> Result<Box<DoraEvent>>;

unsafe fn send_arrow_output(
output_sender: &mut Box<OutputSender>,
id: String,
array_ptr: *mut u8,
schema_ptr: *mut u8,
) -> DoraResult;

unsafe fn event_as_arrow_input(
event: Box<DoraEvent>,
out_array: *mut u8,
out_schema: *mut u8,
) -> DoraResult;
}
}

mod arrow_ffi {
pub use arrow::ffi::{FFI_ArrowArray, FFI_ArrowSchema};
}

#[cfg(feature = "ros2-bridge")]
pub mod ros2 {
pub use dora_ros2_bridge::*;
@@ -161,6 +178,48 @@ fn event_as_input(event: Box<DoraEvent>) -> eyre::Result<ffi::DoraInput> {
})
}

unsafe fn event_as_arrow_input(
event: Box<DoraEvent>,
out_array: *mut u8,
out_schema: *mut u8,
) -> ffi::DoraResult {
// Cast to Arrow FFI types
let out_array = out_array as *mut arrow::ffi::FFI_ArrowArray;
let out_schema = out_schema as *mut arrow::ffi::FFI_ArrowSchema;

let Some(Event::Input {
id: _,
metadata: _,
data,
}) = event.0
else {
return ffi::DoraResult {
error: "Not an input event".to_string(),
};
};

if out_array.is_null() || out_schema.is_null() {
return ffi::DoraResult {
error: "Received null output pointer".to_string(),
};
}

let array_data = data.to_data();

match arrow::ffi::to_ffi(&array_data.clone()) {
Ok((ffi_array, ffi_schema)) => {
std::ptr::write(out_array, ffi_array);
std::ptr::write(out_schema, ffi_schema);
ffi::DoraResult {
error: String::new(),
}
}
Err(e) => ffi::DoraResult {
error: format!("Error exporting Arrow array to C++: {:?}", e),
},
}
}

pub struct OutputSender(dora_node_api::DoraNode);

fn send_output(sender: &mut Box<OutputSender>, id: String, data: &[u8]) -> ffi::DoraResult {
@@ -180,6 +239,47 @@ pub struct MergedEvents {
events: Option<Box<dyn Stream<Item = MergedEvent<ExternalEvent>> + Unpin>>,
next_id: u32,
}
unsafe fn send_arrow_output(
sender: &mut Box<OutputSender>,
id: String,
array_ptr: *mut u8,
schema_ptr: *mut u8,
) -> ffi::DoraResult {
let array_ptr = array_ptr as *mut arrow::ffi::FFI_ArrowArray;
let schema_ptr = schema_ptr as *mut arrow::ffi::FFI_ArrowSchema;

if array_ptr.is_null() || schema_ptr.is_null() {
return ffi::DoraResult {
error: "Received null Arrow array or schema pointer".to_string(),
};
}

let array = std::ptr::read(array_ptr);
let schema = std::ptr::read(schema_ptr);

std::ptr::write(array_ptr, std::mem::zeroed());
std::ptr::write(schema_ptr, std::mem::zeroed());

match arrow::ffi::from_ffi(array, &schema) {
Ok(array_data) => {
let arrow_array = arrow::array::make_array(array_data);
let result = sender
.0
.send_output(id.into(), Default::default(), arrow_array);
match result {
Ok(()) => ffi::DoraResult {
error: String::new(),
},
Err(err) => ffi::DoraResult {
error: format!("{err:?}"),
},
}
}
Err(e) => ffi::DoraResult {
error: format!("Error importing array from C++: {:?}", e),
},
}
}

impl MergedEvents {
fn next(&mut self) -> MergedDoraEvent {


+ 6
- 1
apis/python/node/Cargo.toml View File

@@ -10,8 +10,10 @@ repository.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[features]
default = ["tracing", "telemetry"]
default = ["tracing", "metrics", "telemetry", "async"]
tracing = ["dora-node-api/tracing"]
metrics = ["dora-node-api/metrics"]
async = ["pyo3/experimental-async"]
telemetry = ["dora-runtime/telemetry"]

[dependencies]
@@ -22,11 +24,14 @@ eyre = "0.6"
serde_yaml = "0.8.23"
flume = "0.10.14"
dora-runtime = { workspace = true, features = ["tracing", "metrics", "python"] }
dora-daemon = { workspace = true }
dora-download = { workspace = true }
arrow = { workspace = true, features = ["pyarrow"] }
pythonize = { workspace = true }
futures = "0.3.28"
dora-ros2-bridge-python = { workspace = true }
# pyo3_special_method_derive = "0.4.2"
tokio = { version = "1.24.2", features = ["rt"] }

[lib]
name = "dora"


+ 1
- 1
apis/python/node/dora/__init__.pyi View File

@@ -124,7 +124,7 @@ class Ros2Context:
```
"""

def __init__(self, ros_paths: typing.List[str]=None) -> None:
def __init__(self, ros_paths: list[str]=None) -> None:
"""ROS2 Context holding all messages definition for receiving and sending messages to ROS2.

By default, Ros2Context will use env `AMENT_PREFIX_PATH` to search for message definition.


+ 3
- 7
apis/python/node/dora/cuda.py View File

@@ -52,8 +52,7 @@ def ipc_buffer_to_ipc_handle(handle_buffer: pa.array) -> cuda.IpcMemHandle:
```
"""
handle_buffer = handle_buffer.buffers()[1]
ipc_handle = pa.cuda.IpcMemHandle.from_buffer(handle_buffer)
return ipc_handle
return pa.cuda.IpcMemHandle.from_buffer(handle_buffer)


def cudabuffer_to_numba(buffer: cuda.CudaBuffer, metadata: dict) -> DeviceNDArray:
@@ -76,8 +75,7 @@ def cudabuffer_to_numba(buffer: cuda.CudaBuffer, metadata: dict) -> DeviceNDArra
shape = metadata["shape"]
strides = metadata["strides"]
dtype = metadata["dtype"]
device_arr = DeviceNDArray(shape, strides, dtype, gpu_data=buffer.to_numba())
return device_arr
return DeviceNDArray(shape, strides, dtype, gpu_data=buffer.to_numba())


def cudabuffer_to_torch(buffer: cuda.CudaBuffer, metadata: dict) -> torch.Tensor:
@@ -97,6 +95,4 @@ def cudabuffer_to_torch(buffer: cuda.CudaBuffer, metadata: dict) -> torch.Tensor
torch_tensor = cudabuffer_to_torch(cudabuffer, event["metadata"]) # on cuda
```
"""
device_arr = cudabuffer_to_numba(buffer, metadata)
torch_tensor = torch.as_tensor(device_arr, device="cuda")
return torch_tensor
return torch.as_tensor(cudabuffer_to_numba(buffer, metadata), device="cuda")

+ 1
- 2
apis/python/node/generate_stubs.py View File

@@ -287,8 +287,7 @@ def arguments_stub(
param_names = list(real_parameters.keys())
if param_names and param_names[0] == "self":
del param_names[0]
for name, t in zip(param_names, builtin[0]):
parsed_param_types[name] = t
parsed_param_types = {name: t for name, t in zip(param_names, builtin[0])}

# Types from comment
for match in re.findall(


+ 3
- 2
apis/python/node/pyproject.toml View File

@@ -12,12 +12,13 @@ readme = "README.md"
dependencies = ['pyarrow']

[dependency-groups]
dev = ["pytest >=8.1.1", "ruff >=0.9.1"]
dev = ["pytest >=7.1.1", "ruff >=0.9.1"]

[tool.maturin]
features = ["pyo3/extension-module"]

[tool.ruff.lint]
extend-select = [
"D", # pydocstyle
"D", # pydocstyle
"UP",
]

+ 111
- 15
apis/python/node/src/lib.rs View File

@@ -1,10 +1,15 @@
#![allow(clippy::borrow_deref_ref)] // clippy warns about code generated by #[pymethods]

use std::env::current_dir;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;

use arrow::pyarrow::{FromPyArrow, ToPyArrow};
use dora_daemon::Daemon;
use dora_download::download_file;
use dora_node_api::dora_core::config::NodeId;
use dora_node_api::dora_core::descriptor::source_is_url;
use dora_node_api::merged::{MergeExternalSend, MergedEvent};
use dora_node_api::{DataflowId, DoraNode, EventStream};
use dora_operator_api_python::{pydict_to_metadata, DelayedCleanup, NodeCleanupHandle, PyEvent};
@@ -52,14 +57,14 @@ impl Node {
let dataflow_id = *node.dataflow_id();
let node_id = node.id().clone();
let node = DelayedCleanup::new(node);
let events = DelayedCleanup::new(events);
let events = events;
let cleanup_handle = NodeCleanupHandle {
_handles: Arc::new((node.handle(), events.handle())),
_handles: Arc::new(node.handle()),
};
Ok(Node {
events: Events {
inner: EventsInner::Dora(events),
cleanup_handle,
_cleanup_handle: cleanup_handle,
},
dataflow_id,
node_id,
@@ -102,6 +107,43 @@ impl Node {
}
}

/// `.recv_async()` gives you the next input that the node has received asynchronously.
/// It does not blocks until the next event becomes available.
/// You can use timeout in seconds to return if no input is available.
/// It will return an Error if the timeout is reached.
/// It will return `None` when all senders has been dropped.
///
/// warning::
/// This feature is experimental as pyo3 async (rust-python FFI) is still in development.
///
/// ```python
/// event = await node.recv_async()
/// ```
///
/// You can also iterate over the event stream with a loop
///
/// :type timeout: float, optional
/// :rtype: dict
#[pyo3(signature = (timeout=None))]
#[allow(clippy::should_implement_trait)]
pub async fn recv_async(&mut self, timeout: Option<f32>) -> PyResult<Option<Py<PyDict>>> {
let event = self
.events
.recv_async_timeout(timeout.map(Duration::from_secs_f32))
.await;
if let Some(event) = event {
// Get python
Python::with_gil(|py| {
let dict = event
.to_py_dict(py)
.context("Could not convert event into a dict")?;
Ok(Some(dict))
})
} else {
Ok(None)
}
}

/// You can iterate over the event stream with a loop
///
/// ```python
@@ -218,9 +260,9 @@ impl Node {
value
.to_pyarrow(py)
.context("failed to convert value to pyarrow")
.unwrap_or_else(|err| PyErr::from(err).to_object(py))
.unwrap_or_else(|err| err_to_pyany(err, py))
}),
Err(err) => Python::with_gil(|py| PyErr::from(err).to_object(py)),
Err(err) => Python::with_gil(|py| err_to_pyany(err, py)),
}
});
futures::pin_mut!(s);
@@ -239,32 +281,48 @@ impl Node {
}
}

fn err_to_pyany(err: eyre::Report, gil: Python<'_>) -> Py<PyAny> {
PyErr::from(err)
.into_pyobject(gil)
.unwrap_or_else(|infallible| match infallible {})
.into_any()
.unbind()
}

struct Events {
inner: EventsInner,
cleanup_handle: NodeCleanupHandle,
_cleanup_handle: NodeCleanupHandle,
}

impl Events {
fn recv(&mut self, timeout: Option<Duration>) -> Option<PyEvent> {
let event = match &mut self.inner {
EventsInner::Dora(events) => match timeout {
Some(timeout) => events.recv_timeout(timeout).map(MergedEvent::Dora),
None => events.recv().map(MergedEvent::Dora),
},
EventsInner::Merged(events) => futures::executor::block_on(events.next()),
};
event.map(|event| PyEvent { event })
}

async fn recv_async_timeout(&mut self, timeout: Option<Duration>) -> Option<PyEvent> {
let event = match &mut self.inner {
EventsInner::Dora(events) => match timeout {
Some(timeout) => events
.get_mut()
.recv_timeout(timeout)
.recv_async_timeout(timeout)
.await
.map(MergedEvent::Dora),
None => events.get_mut().recv().map(MergedEvent::Dora),
None => events.recv_async().await.map(MergedEvent::Dora),
},
EventsInner::Merged(events) => futures::executor::block_on(events.next()),
EventsInner::Merged(events) => events.next().await,
};
event.map(|event| PyEvent {
event,
_cleanup: Some(self.cleanup_handle.clone()),
})
event.map(|event| PyEvent { event })
}
}

enum EventsInner {
Dora(DelayedCleanup<EventStream>),
Dora(EventStream),
Merged(Box<dyn Stream<Item = MergedEvent<PyObject>> + Unpin + Send + Sync>),
}

@@ -302,11 +360,49 @@ pub fn start_runtime() -> eyre::Result<()> {
dora_runtime::main().wrap_err("Dora Runtime raised an error.")
}

pub fn resolve_dataflow(dataflow: String) -> eyre::Result<PathBuf> {
let dataflow = if source_is_url(&dataflow) {
// try to download the shared library
let target_path = current_dir().context("Could not access the current dir")?;
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.context("tokio runtime failed")?;
rt.block_on(async { download_file(&dataflow, &target_path).await })
.wrap_err("failed to download dataflow yaml file")?
} else {
PathBuf::from(dataflow)
};
Ok(dataflow)
}

/// Run a Dataflow
///
/// :rtype: None
#[pyfunction]
#[pyo3(signature = (dataflow_path, uv=None))]
pub fn run(dataflow_path: String, uv: Option<bool>) -> eyre::Result<()> {
let dataflow_path = resolve_dataflow(dataflow_path).context("could not resolve dataflow")?;
let rt = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.context("tokio runtime failed")?;
let result = rt.block_on(Daemon::run_dataflow(&dataflow_path, uv.unwrap_or_default()))?;
match result.is_ok() {
true => Ok(()),
false => Err(eyre::eyre!(
"Dataflow failed to run with error: {:?}",
result.node_results
)),
}
}

#[pymodule]
fn dora(_py: Python, m: Bound<'_, PyModule>) -> PyResult<()> {
dora_ros2_bridge_python::create_dora_ros2_bridge_module(&m)?;

m.add_function(wrap_pyfunction!(start_runtime, &m)?)?;
m.add_function(wrap_pyfunction!(run, &m)?)?;
m.add_class::<Node>()?;
m.setattr("__version__", env!("CARGO_PKG_VERSION"))?;
m.setattr("__author__", "Dora-rs Authors")?;


+ 48
- 14
apis/python/operator/src/lib.rs View File

@@ -19,14 +19,13 @@ use pyo3::{
/// Dora Event
pub struct PyEvent {
pub event: MergedEvent<PyObject>,
pub _cleanup: Option<NodeCleanupHandle>,
}

/// Keeps the dora node alive until all event objects have been dropped.
#[derive(Clone)]
#[pyclass]
pub struct NodeCleanupHandle {
pub _handles: Arc<(CleanupHandle<DoraNode>, CleanupHandle<EventStream>)>,
pub _handles: Arc<CleanupHandle<DoraNode>>,
}

/// Owned type with delayed cleanup (using `handle` method).
@@ -81,15 +80,41 @@ impl PyEvent {
pub fn to_py_dict(self, py: Python<'_>) -> PyResult<Py<PyDict>> {
let mut pydict = HashMap::new();
match &self.event {
MergedEvent::Dora(_) => pydict.insert("kind", "dora".to_object(py)),
MergedEvent::External(_) => pydict.insert("kind", "external".to_object(py)),
MergedEvent::Dora(_) => pydict.insert(
"kind",
"dora"
.into_pyobject(py)
.context("Failed to create pystring")?
.unbind()
.into(),
),
MergedEvent::External(_) => pydict.insert(
"kind",
"external"
.into_pyobject(py)
.context("Failed to create pystring")?
.unbind()
.into(),
),
};
match &self.event {
MergedEvent::Dora(event) => {
if let Some(id) = Self::id(event) {
pydict.insert("id", id.into_py(py));
pydict.insert(
"id",
id.into_pyobject(py)
.context("Failed to create id pyobject")?
.into(),
);
}
pydict.insert("type", Self::ty(event).to_object(py));
pydict.insert(
"type",
Self::ty(event)
.into_pyobject(py)
.context("Failed to create event pyobject")?
.unbind()
.into(),
);

if let Some(value) = self.value(py)? {
pydict.insert("value", value);
@@ -98,7 +123,14 @@ impl PyEvent {
pydict.insert("metadata", metadata);
}
if let Some(error) = Self::error(event) {
pydict.insert("error", error.to_object(py));
pydict.insert(
"error",
error
.into_pyobject(py)
.context("Failed to create error pyobject")?
.unbind()
.into(),
);
}
}
MergedEvent::External(event) => {
@@ -106,11 +138,10 @@ impl PyEvent {
}
}

if let Some(cleanup) = self._cleanup.clone() {
pydict.insert("_cleanup", cleanup.into_py(py));
}

Ok(pydict.into_py_dict_bound(py).unbind())
Ok(pydict
.into_py_dict(py)
.context("Failed to create py_dict")?
.unbind())
}

fn ty(event: &Event) -> &str {
@@ -148,7 +179,10 @@ impl PyEvent {
Event::Input { metadata, .. } => Ok(Some(
metadata_to_pydict(metadata, py)
.context("Issue deserializing metadata")?
.to_object(py),
.into_pyobject(py)
.context("Failed to create metadata_to_pydice")?
.unbind()
.into(),
)),
_ => Ok(None),
}
@@ -218,7 +252,7 @@ pub fn metadata_to_pydict<'a>(
metadata: &'a Metadata,
py: Python<'a>,
) -> Result<pyo3::Bound<'a, PyDict>> {
let dict = PyDict::new_bound(py);
let dict = PyDict::new(py);
for (k, v) in metadata.parameters.iter() {
match v {
Parameter::Bool(bool) => dict


+ 4
- 4
apis/rust/node/Cargo.toml View File

@@ -8,8 +8,9 @@ license.workspace = true
repository.workspace = true

[features]
default = ["tracing"]
default = ["tracing", "metrics"]
tracing = ["dep:dora-tracing"]
metrics = ["dep:dora-metrics"]

[dependencies]
dora-core = { workspace = true }
@@ -22,6 +23,7 @@ flume = "0.10.14"
bincode = "1.3.3"
shared_memory_extended = "0.13.0"
dora-tracing = { workspace = true, optional = true }
dora-metrics = { workspace = true, optional = true }
arrow = { workspace = true }
futures = "0.3.28"
futures-concurrency = "7.3.0"
@@ -29,6 +31,4 @@ futures-timer = "3.0.2"
dora-arrow-convert = { workspace = true }
aligned-vec = "0.5.0"
serde_json = "1.0.86"

[dev-dependencies]
tokio = { version = "1.24.2", features = ["rt"] }
tokio = { version = "1.24.2", features = ["rt", "rt-multi-thread"] }

+ 43
- 0
apis/rust/node/src/node/mod.rs View File

@@ -31,8 +31,11 @@ use std::{
};
use tracing::{info, warn};

#[cfg(feature = "metrics")]
use dora_metrics::init_meter_provider;
#[cfg(feature = "tracing")]
use dora_tracing::set_up_tracing;
use tokio::runtime::{Handle, Runtime};

pub mod arrow_utils;
mod control_channel;
@@ -40,6 +43,11 @@ mod drop_stream;

pub const ZERO_COPY_THRESHOLD: usize = 4096;

enum TokioRuntime {
Runtime(Runtime),
Handle(Handle),
}

pub struct DoraNode {
id: NodeId,
dataflow_id: DataflowId,
@@ -53,6 +61,7 @@ pub struct DoraNode {

dataflow_descriptor: Descriptor,
warned_unknown_output: BTreeSet<DataId>,
_rt: TokioRuntime,
}

impl DoraNode {
@@ -133,6 +142,39 @@ impl DoraNode {
let clock = Arc::new(uhlc::HLC::default());
let input_config = run_config.inputs.clone();

let rt = match Handle::try_current() {
Ok(handle) => TokioRuntime::Handle(handle),
Err(_) => TokioRuntime::Runtime(
tokio::runtime::Builder::new_multi_thread()
.worker_threads(2)
.enable_all()
.build()
.context("tokio runtime failed")?,
),
};

let id = format!("{}/{}", dataflow_id, node_id);

#[cfg(feature = "metrics")]
match &rt {
TokioRuntime::Runtime(rt) => rt.spawn(async {
if let Err(e) = init_meter_provider(id)
.await
.context("failed to init metrics provider")
{
warn!("could not create metric provider with err: {:#?}", e);
}
}),
TokioRuntime::Handle(handle) => handle.spawn(async {
if let Err(e) = init_meter_provider(id)
.await
.context("failed to init metrics provider")
{
warn!("could not create metric provider with err: {:#?}", e);
}
}),
};

let event_stream = EventStream::init(
dataflow_id,
&node_id,
@@ -159,6 +201,7 @@ impl DoraNode {
cache: VecDeque::new(),
dataflow_descriptor,
warned_unknown_output: BTreeSet::new(),
_rt: rt,
};
Ok((node, event_stream))
}


+ 1
- 0
benches/llms/.gitignore View File

@@ -0,0 +1 @@
*.csv

+ 10
- 0
benches/llms/README.md View File

@@ -0,0 +1,10 @@
# Benchmark LLM Speed

Use the following command to run the benchmark:

```bash
dora build transformers.yaml --uv
dora run transformers.yaml --uv
```

You should see a benchmark details.

+ 28
- 0
benches/llms/llama_cpp_python.yaml View File

@@ -0,0 +1,28 @@
nodes:
- id: benchmark_script
path: ../mllm/benchmark_script.py
inputs:
text: llm/text
outputs:
- text
env:
TEXT: "Please only generate the following output: This is a test"
TEXT_TRUTH: "This is a test"

- id: llm
build: pip install -e ../../node-hub/dora-llama-cpp-python
path: dora-llama-cpp-python
inputs:
text:
source: benchmark_script/text
queue-size: 10
outputs:
- text
env:
MODEL_NAME_OR_PATH: "Qwen/Qwen2.5-0.5B-Instruct-GGUF"
MODEL_FILE_PATTERN: "*fp16.gguf"
SYSTEM_PROMPT: "You're a very succinct AI assistant with short answers."
MAX_TOKENS: "512"
N_GPU_LAYERS: "35" # Enable GPU acceleration
N_THREADS: "16" # CPU threads
CONTEXT_SIZE: "4096" # Maximum context window

+ 19
- 0
benches/llms/mistralrs.yaml View File

@@ -0,0 +1,19 @@
nodes:
- id: benchmark_script
path: ../mllm/benchmark_script.py
inputs:
text: llm/text
outputs:
- text
env:
TEXT: "Please only generate the following output: This is a test"
TEXT_TRUTH: "This is a test"

- id: llm
build: |
cargo build -p dora-mistral-rs --release
path: ../../target/release/dora-mistral-rs
inputs:
text: benchmark_script/text
outputs:
- text

+ 20
- 0
benches/llms/phi4.yaml View File

@@ -0,0 +1,20 @@
nodes:
- id: benchmark_script
path: ../mllm/benchmark_script.py
inputs:
text: llm/text
outputs:
- text
env:
TEXT: "Please only generate the following output: This is a test"
TEXT_TRUTH: "This is a test"

- id: llm
build: |
pip install flash-attn --no-build-isolation
pip install -e ../../node-hub/dora-phi4
path: dora-phi4
inputs:
text: benchmark_script/text
outputs:
- text

+ 19
- 0
benches/llms/qwen2.5.yaml View File

@@ -0,0 +1,19 @@
nodes:
- id: benchmark_script
path: ../mllm/benchmark_script.py
inputs:
text: llm/text
outputs:
- text
env:
TEXT: "Please only generate the following output: This is a test"
TEXT_TRUTH: "This is a test"

- id: llm
build: |
pip install -e ../../node-hub/dora-qwen
path: dora-qwen
inputs:
text: benchmark_script/text
outputs:
- text

+ 20
- 0
benches/llms/transformers.yaml View File

@@ -0,0 +1,20 @@
nodes:
- id: benchmark_script
path: ../mllm/benchmark_script.py
inputs:
text: llm/text
outputs:
- text
env:
TEXT: "Please only generate the following output: This is a test"
TEXT_TRUTH: "This is a test"

- id: llm
build: pip install -e ../../node-hub/dora-transformers
path: dora-transformers
inputs:
text: benchmark_script/text
outputs:
- text
env:
MODEL_NAME: "Qwen/Qwen2.5-0.5B-Instruct" # Model from Hugging Face

+ 1
- 0
benches/mllm/.gitignore View File

@@ -0,0 +1 @@
*.csv

+ 10
- 0
benches/mllm/README.md View File

@@ -0,0 +1,10 @@
# Benchmark LLM Speed

Use the following command to run the benchmark:

```bash
dora build transformers.yaml --uv
dora run transformers.yaml --uv
```

You should see a benchmark details.

+ 220
- 0
benches/mllm/benchmark_script.py View File

@@ -0,0 +1,220 @@
"""TODO: Add docstring."""

import argparse
import ast

# Create an empty csv file with header in the current directory if file does not exist
import csv
import os
import time
from io import BytesIO

import cv2
import librosa
import numpy as np
import pyarrow as pa
import requests
from dora import Node
from PIL import Image

CAT_URL = "https://i.ytimg.com/vi/fzzjgBAaWZw/hqdefault.jpg"


def get_cat_image():
"""
Get a cat image as a numpy array.

:return: Cat image as a numpy array.
"""
# Fetch the image from the URL
response = requests.get(CAT_URL)
response.raise_for_status()

# Open the image using PIL

image = Image.open(BytesIO(response.content))
# Convert the image to a numpy array

image_array = np.array(image)
cv2.resize(image_array, (640, 480))
# Convert RGB to BGR for

return image_array


AUDIO_URL = "https://github.com/dora-rs/dora-rs.github.io/raw/refs/heads/main/static/Voicy_C3PO%20-Don't%20follow%20me.mp3"


def get_c3po_audio():
"""
Download the C-3PO audio and load it into a NumPy array using librosa.
"""
# Download the audio file
response = requests.get(AUDIO_URL)
if response.status_code != 200:
raise Exception(
f"Failed to download audio file. Status code: {response.status_code}"
)

# Save the audio file temporarily
temp_audio_file = "temp_audio.mp3"
with open(temp_audio_file, "wb") as f:
f.write(response.content)

# Load the audio file into a NumPy array using librosa
audio_data, sample_rate = librosa.load(temp_audio_file, sr=None)

# Optionally, you can remove the temporary file after loading

os.remove(temp_audio_file)

return audio_data, sample_rate


def write_to_csv(filename, header, row):
"""
Create a CSV file with a header if it does not exist, and write a row to it.
If the file exists, append the row to the file.

:param filename: Name of the CSV file.
:param header: List of column names to use as the header.
:param row: List of data to write as a row in the CSV file.
"""
file_exists = os.path.exists(filename)

with open(
filename, mode="a" if file_exists else "w", newline="", encoding="utf8"
) as file:
writer = csv.writer(file)

# Write the header if the file is being created
if not file_exists:
writer.writerow(header)
print(f"File '{filename}' created with header: {header}")

# Write the row
writer.writerow(row)
print(f"Row written to '{filename}': {row}")


def main():
# Handle dynamic nodes, ask for the name of the node in the dataflow, and the same values as the ENV variables.
"""TODO: Add docstring."""
parser = argparse.ArgumentParser(description="Simple arrow sender")

parser.add_argument(
"--name",
type=str,
required=False,
help="The name of the node in the dataflow.",
default="pyarrow-sender",
)
parser.add_argument(
"--text",
type=str,
required=False,
help="Arrow Data as string.",
default=None,
)

args = parser.parse_args()

text = os.getenv("TEXT", args.text)
text_truth = os.getenv("TEXT_TRUTH", args.text)

cat = get_cat_image()
audio, sample_rate = get_c3po_audio()
if text is None:
raise ValueError(
"No data provided. Please specify `TEXT` environment argument or as `--text` argument",
)
try:
text = ast.literal_eval(text)
except Exception: # noqa
print("Passing input as string")

if isinstance(text, (str, int, float)):
text = pa.array([text])
else:
text = pa.array(text) # initialize pyarrow array
node = Node(
args.name,
) # provide the name to connect to the dataflow if dynamic node
name = node.dataflow_descriptor()["nodes"][1]["path"]

durations = []
speed = []
for _ in range(10):
node.send_output(
"image",
pa.array(cat.ravel()),
{"encoding": "rgb8", "width": cat.shape[1], "height": cat.shape[0]},
)
node.send_output(
"audio",
pa.array(audio.ravel()),
{"sample_rate": sample_rate},
)
time.sleep(0.1)
start_time = time.time()
node.send_output("text", text)
event = node.next()
duration = time.time() - start_time
if event is not None and event["type"] == "INPUT":
received_text = event["value"][0].as_py()
tokens = event["metadata"].get("tokens", 6)
assert text_truth in received_text, (
f"Expected '{text_truth}', got {received_text}"
)
durations.append(duration)
speed.append(tokens / duration)
time.sleep(0.1)
durations = np.array(durations)
speed = np.array(speed)
print(
f"\nAverage duration: {sum(durations) / len(durations)}"
+ f"\nMax duration: {max(durations)}"
+ f"\nMin duration: {min(durations)}"
+ f"\nMedian duration: {np.median(durations)}"
+ f"\nMedian frequency: {1 / np.median(durations)}"
+ f"\nAverage speed: {sum(speed) / len(speed)}"
+ f"\nMax speed: {max(speed)}"
+ f"\nMin speed: {min(speed)}"
+ f"\nMedian speed: {np.median(speed)}"
+ f"\nTotal tokens: {tokens}"
)
write_to_csv(
"benchmark.csv",
[
"path",
"date",
"average_duration(s)",
"max_duration(s)",
"min_duration(s)",
"median_duration(s)",
"median_frequency(Hz)",
"average_speed(tok/s)",
"max_speed(tok/s)",
"min_speed(tok/s)",
"median_speed(tok/s)",
"total_tokens",
],
[
name,
time.strftime("%Y-%m-%d %H:%M:%S"),
sum(durations) / len(durations),
max(durations),
min(durations),
np.median(durations),
1 / np.median(durations),
sum(speed) / len(speed),
max(speed),
min(speed),
np.median(speed),
tokens,
],
)


if __name__ == "__main__":
main()

+ 23
- 0
benches/mllm/phi4.yaml View File

@@ -0,0 +1,23 @@
nodes:
- id: benchmark_script
path: benchmark_script.py
inputs:
text: llm/text
outputs:
- text
- image
- audio
env:
TEXT: "Please only generate the following output: This is a cat"
TEXT_TRUTH: "This is a cat"

- id: llm
build: |
pip install -e ../../node-hub/dora-phi4
path: dora-phi4
inputs:
text: benchmark_script/text
image: benchmark_script/image
audio: benchmark_script/audio
outputs:
- text

+ 1
- 0
benches/vlm/.gitignore View File

@@ -0,0 +1 @@
*.csv

+ 10
- 0
benches/vlm/README.md View File

@@ -0,0 +1,10 @@
# Benchmark LLM Speed

Use the following command to run the benchmark:

```bash
dora build transformers.yaml --uv
dora run transformers.yaml --uv
```

You should see a benchmark details.

+ 20
- 0
benches/vlm/magma.yaml View File

@@ -0,0 +1,20 @@
nodes:
- id: benchmark_script
path: ../mllm/benchmark_script.py
inputs:
text: llm/text
outputs:
- text
- image
env:
TEXT: "Please only generate the following output: This is a cat"
TEXT_TRUTH: "This is a cat"

- id: llm
build: pip install -e ../../node-hub/dora-magma
path: dora-magma
inputs:
text: benchmark_script/text
image: benchmark_script/image
outputs:
- text

+ 22
- 0
benches/vlm/phi4.yaml View File

@@ -0,0 +1,22 @@
nodes:
- id: benchmark_script
path: ../mllm/benchmark_script.py
inputs:
text: llm/text
outputs:
- text
- image
env:
TEXT: "Please only generate the following output: This is a cat"
TEXT_TRUTH: "This is a cat"

- id: llm
build: |
pip install flash-attn --no-build-isolation
pip install -e ../../node-hub/dora-phi4
path: dora-phi4
inputs:
text: benchmark_script/text
image: benchmark_script/image
outputs:
- text

+ 22
- 0
benches/vlm/qwen2.5vl.yaml View File

@@ -0,0 +1,22 @@
nodes:
- id: benchmark_script
path: ../mllm/benchmark_script.py
inputs:
text: vlm/text
outputs:
- text
- image
env:
TEXT: "Please only generate the following output: This is a cat"
TEXT_TRUTH: "This is a cat"

- id: vlm
# Comment flash_attn if not on cuda hardware
build: |
pip install -e ../../node-hub/dora-qwen2-5-vl
path: dora-qwen2-5-vl
inputs:
image: benchmark_script/image
text: benchmark_script/text
outputs:
- text

+ 10
- 0
binaries/cli/Cargo.toml View File

@@ -50,6 +50,12 @@ tabwriter = "1.4.0"
log = { version = "0.4.21", features = ["serde"] }
colored = "2.1.0"
env_logger = "0.11.3"
self_update = { version = "0.27.0", features = [
"rustls",
"archive-zip",
"archive-tar",
"compression-flate2",
], default-features = false }
pyo3 = { workspace = true, features = [
"extension-module",
"abi3",
@@ -60,3 +66,7 @@ pyo3 = { workspace = true, features = [
name = "dora_cli"
path = "src/lib.rs"
crate-type = ["lib", "cdylib"]

[package.metadata.dist]
# Enable dora-cli binary to be distributed
dist = true

+ 6
- 0
binaries/cli/build.rs View File

@@ -0,0 +1,6 @@
fn main() {
println!(
"cargo:rustc-env=TARGET={}",
std::env::var("TARGET").unwrap()
);
}

+ 1
- 0
binaries/cli/pyproject.toml View File

@@ -16,4 +16,5 @@ features = ["python", "pyo3/extension-module"]
[tool.ruff.lint]
extend-select = [
"D", # pydocstyle
"UP"
]

+ 23
- 19
binaries/cli/src/build.rs View File

@@ -23,39 +23,43 @@ pub fn build(dataflow: String, uv: bool) -> eyre::Result<()> {
match node.kind()? {
dora_core::descriptor::NodeKind::Standard(_) => {
if let Some(build) = &node.build {
run_build_command(build, working_dir, uv).with_context(|| {
format!("build command failed for standard node `{}`", node.id)
})?
run_build_command(build, working_dir, uv, node.env.clone()).with_context(
|| format!("build command failed for standard node `{}`", node.id),
)?
}
}
dora_core::descriptor::NodeKind::Runtime(runtime_node) => {
for operator in &runtime_node.operators {
if let Some(build) = &operator.config.build {
run_build_command(build, working_dir, uv).with_context(|| {
format!(
"build command failed for operator `{}/{}`",
node.id, operator.id
)
})?;
run_build_command(build, working_dir, uv, node.env.clone()).with_context(
|| {
format!(
"build command failed for operator `{}/{}`",
node.id, operator.id
)
},
)?;
}
}
}
dora_core::descriptor::NodeKind::Custom(custom_node) => {
if let Some(build) = &custom_node.build {
run_build_command(build, working_dir, uv).with_context(|| {
format!("build command failed for custom node `{}`", node.id)
})?
run_build_command(build, working_dir, uv, node.env.clone()).with_context(
|| format!("build command failed for custom node `{}`", node.id),
)?
}
}
dora_core::descriptor::NodeKind::Operator(operator) => {
if let Some(build) = &operator.config.build {
run_build_command(build, working_dir, uv).with_context(|| {
format!(
"build command failed for operator `{}/{}`",
node.id,
operator.id.as_ref().unwrap_or(&default_op_id)
)
})?
run_build_command(build, working_dir, uv, node.env.clone()).with_context(
|| {
format!(
"build command failed for operator `{}/{}`",
node.id,
operator.id.as_ref().unwrap_or(&default_op_id)
)
},
)?
}
}
}


+ 71
- 1
binaries/cli/src/lib.rs View File

@@ -240,6 +240,20 @@ enum Command {
#[clap(long)]
quiet: bool,
},

Self_ {
#[clap(subcommand)]
command: SelfSubCommand,
},
}

#[derive(Debug, clap::Subcommand)]
enum SelfSubCommand {
Update {
/// Only check for updates without installing
#[clap(long)]
check_only: bool,
},
}

#[derive(Debug, clap::Args)]
@@ -312,7 +326,8 @@ fn run(args: Args) -> eyre::Result<()> {
.context("failed to set up tracing subscriber")?;
}
Command::Run { .. } => {
set_up_tracing_opts("run", Some("info"), None)
let log_level = std::env::var("RUST_LOG").ok().or(Some("info".to_string()));
set_up_tracing_opts("run", log_level.as_deref(), None)
.context("failed to set up tracing subscriber")?;
}
_ => {
@@ -537,6 +552,61 @@ fn run(args: Args) -> eyre::Result<()> {
.context("failed to run dora-daemon")?
}
Command::Runtime => dora_runtime::main().context("Failed to run dora-runtime")?,
Command::Self_ { command } => match command {
SelfSubCommand::Update { check_only } => {
println!("Checking for updates...");

#[cfg(target_os = "linux")]
let bin_path_in_archive = format!("dora-cli-{}/dora", env!("TARGET"));
#[cfg(target_os = "macos")]
let bin_path_in_archive = format!("dora-cli-{}/dora", env!("TARGET"));
#[cfg(target_os = "windows")]
let bin_path_in_archive = String::from("dora.exe");

let status = self_update::backends::github::Update::configure()
.repo_owner("dora-rs")
.repo_name("dora")
.bin_path_in_archive(&bin_path_in_archive)
.bin_name("dora")
.show_download_progress(true)
.current_version(env!("CARGO_PKG_VERSION"))
.build()?;

if check_only {
// Only check if an update is available
match status.get_latest_release() {
Ok(release) => {
let current_version = self_update::cargo_crate_version!();
if current_version != release.version {
println!(
"An update is available: {}. Run 'dora self update' to update",
release.version
);
} else {
println!(
"Dora CLI is already at the latest version: {}",
current_version
);
}
}
Err(e) => println!("Failed to check for updates: {}", e),
}
} else {
// Perform the actual update
match status.update() {
Ok(update_status) => match update_status {
self_update::Status::UpToDate(version) => {
println!("Dora CLI is already at the latest version: {}", version);
}
self_update::Status::Updated(version) => {
println!("Successfully updated Dora CLI to version: {}", version);
}
},
Err(e) => println!("Failed to update: {}", e),
}
}
}
},
};

Ok(())


+ 2
- 1
binaries/cli/src/template/python/__node-name__/pyproject.toml View File

@@ -17,5 +17,6 @@ __node-name__ = "__node_name__.main:main"

[tool.ruff.lint]
extend-select = [
"D",
"D", # pydocstyle
"UP"
]

+ 1
- 0
binaries/cli/src/template/python/listener/listener-template.py View File

@@ -2,6 +2,7 @@

from dora import Node


def main():
"""Listen for input events and print received messages."""
node = Node()


+ 1
- 0
binaries/cli/src/template/python/talker/talker-template.py View File

@@ -3,6 +3,7 @@
import pyarrow as pa
from dora import Node


def main():
"""Process node input events and send speech output."""
node = Node()


+ 6
- 3
binaries/daemon/src/spawn.rs View File

@@ -21,7 +21,7 @@ use dora_message::{
common::{LogLevel, LogMessage},
daemon_to_coordinator::{DataMessage, NodeExitStatus, Timestamped},
daemon_to_node::{NodeConfig, RuntimeConfig},
descriptor::GitRepoRev,
descriptor::{EnvValue, GitRepoRev},
DataflowId,
};
use dora_node_api::{
@@ -107,7 +107,8 @@ impl Spawner<'_> {
.await?
}
dora_message::descriptor::NodeSource::GitBranch { repo, rev } => {
self.spawn_git_node(&n, repo, rev, logger).await?
self.spawn_git_node(&n, repo, rev, logger, &node.env)
.await?
}
};
let Some(mut command) = command else {
@@ -512,6 +513,7 @@ impl Spawner<'_> {
repo_addr: &String,
rev: &Option<GitRepoRev>,
logger: &mut NodeLogger<'_>,
node_env: &Option<BTreeMap<String, EnvValue>>,
) -> Result<Option<tokio::process::Command>, eyre::Error> {
let dataflow_id = self.dataflow_id;
let repo_url = Url::parse(repo_addr).context("failed to parse git repository URL")?;
@@ -604,8 +606,9 @@ impl Spawner<'_> {
let build = build.to_owned();
let clone_dir = clone_dir.clone();
let uv = self.uv;
let node_env = node_env.clone();
let task = tokio::task::spawn_blocking(move || {
run_build_command(&build, &clone_dir, uv).context("build command failed")
run_build_command(&build, &clone_dir, uv, node_env).context("build command failed")
});
task.await??;
}


+ 14
- 12
binaries/runtime/src/operator/python.rs View File

@@ -10,6 +10,7 @@ use dora_node_api::{merged::MergedEvent, Event, Parameter};
use dora_operator_api_python::PyEvent;
use dora_operator_api_types::DoraStatus;
use eyre::{bail, eyre, Context, Result};
use pyo3::ffi::c_str;
use pyo3::{
pyclass,
types::{IntoPyDict, PyAnyMethods, PyDict, PyDictMethods, PyTracebackMethods},
@@ -23,7 +24,7 @@ use tokio::sync::{mpsc::Sender, oneshot};
use tracing::{error, field, span, warn};

fn traceback(err: pyo3::PyErr) -> eyre::Report {
let traceback = Python::with_gil(|py| err.traceback_bound(py).and_then(|t| t.format().ok()));
let traceback = Python::with_gil(|py| err.traceback(py).and_then(|t| t.format().ok()));
if let Some(traceback) = traceback {
eyre::eyre!("{traceback}\n{err}")
} else {
@@ -75,9 +76,7 @@ pub fn run(
let parent_path = parent_path
.to_str()
.ok_or_else(|| eyre!("module path is not valid utf8"))?;
let sys = py
.import_bound("sys")
.wrap_err("failed to import `sys` module")?;
let sys = py.import("sys").wrap_err("failed to import `sys` module")?;
let sys_path = sys
.getattr("path")
.wrap_err("failed to import `sys.path` module")?;
@@ -89,14 +88,16 @@ pub fn run(
.wrap_err("failed to append module path to python search path")?;
}

let module = py.import_bound(module_name).map_err(traceback)?;
let module = py.import(module_name).map_err(traceback)?;
let operator_class = module
.getattr("Operator")
.wrap_err("no `Operator` class found in module")?;

let locals = [("Operator", operator_class)].into_py_dict_bound(py);
let locals = [("Operator", operator_class)]
.into_py_dict(py)
.context("Failed to create py_dict")?;
let operator = py
.eval_bound("Operator()", None, Some(&locals))
.eval(c_str!("Operator()"), None, Some(&locals))
.map_err(traceback)?;
operator.setattr(
"dataflow_descriptor",
@@ -141,11 +142,11 @@ pub fn run(
})?;
// Reload module
let module = py
.import_bound(module_name)
.import(module_name)
.map_err(traceback)
.wrap_err(format!("Could not retrieve {module_name} while reloading"))?;
let importlib = py
.import_bound("importlib")
.import("importlib")
.wrap_err("failed to import `importlib` module")?;
let module = importlib
.call_method("reload", (module,), None)
@@ -155,9 +156,11 @@ pub fn run(
.wrap_err("no `Operator` class found in module")?;

// Create a new reloaded operator
let locals = [("Operator", reloaded_operator_class)].into_py_dict_bound(py);
let locals = [("Operator", reloaded_operator_class)]
.into_py_dict(py)
.context("Failed to create py_dict")?;
let operator: Py<pyo3::PyAny> = py
.eval_bound("Operator()", None, Some(&locals))
.eval(c_str!("Operator()"), None, Some(&locals))
.map_err(traceback)
.wrap_err("Could not initialize reloaded operator")?
.into();
@@ -214,7 +217,6 @@ pub fn run(

let py_event = PyEvent {
event: MergedEvent::Dora(event),
_cleanup: None,
}
.to_py_dict(py)
.context("Could not convert event to pydict bound")?;


+ 42
- 0
dist-workspace.toml View File

@@ -0,0 +1,42 @@
[workspace]
members = ["cargo:binaries/cli"]

# Config for 'dist'
[dist]
# The preferred dist version to use in CI (Cargo.toml SemVer syntax)
cargo-dist-version = "0.28.0"
# CI backends to support
ci = "github"
# The installers to generate for each app
installers = ["shell", "powershell"]
# Target platforms to build apps for (Rust target-triple syntax)
targets = [
"aarch64-apple-darwin",
"aarch64-unknown-linux-gnu",
"x86_64-apple-darwin",
"x86_64-unknown-linux-gnu",
"x86_64-pc-windows-msvc",
]
# Path that installers should place binaries in
install-path = "~/.dora/bin"
# Whether to install an updater program
install-updater = false
# Build only the required packages, and individually
precise-builds = true
# Whether to consider the binaries in a package for distribution (defaults true)
dist = false
# Whether dist should create a Github Release or use an existing draft
create-release = false
# The archive format to use for non-windows builds (defaults .tar.xz)
unix-archive = ".tar.gz"
# Skip checking whether the specified configuration files are up to date
allow-dirty = ["ci"]

[dist.github-custom-runners]
x86_64-pc-windows-msvc = "windows-2022"
x86_64-unknown-linux-gnu = "ubuntu-22.04"
aarch64-unknown-linux-gnu = "ubuntu-22.04"
global = "ubuntu-22.04"

[dist.bin-aliases]
dora-cli = ["dora"]

+ 32
- 0
docker/slim/Dockerfile View File

@@ -0,0 +1,32 @@
FROM python:3.12-slim

# Install system dependencies
RUN apt-get update && apt-get install -y \
git \
curl \
build-essential \
pkg-config \
libssl-dev \
ffmpeg \
libsm6 \
libxext6 \
&& rm -rf /var/lib/apt/lists/*

# Install Rust (required for Dora)
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
ENV PATH="/root/.cargo/bin:${PATH}"

# Install uv
RUN pip install --no-cache-dir uv

# Install latest Dora
RUN pip install --no-cache-dir dora-rs-cli

# Create a working directory
WORKDIR /app

# Set environment variables
ENV PYTHONUNBUFFERED=1

# Default command when container starts
CMD ["bash"]

+ 51
- 0
docker/slim/README.md View File

@@ -0,0 +1,51 @@
# slim Dora Docker Environment

This Dockerfile provides a slim environment for running Dora applications with Python and uv package manager.

## What's Included

- Python 3.12
- Rust (required for Dora)
- uv package manager
- Latest Dora release

## Building the Image

```bash
docker build . -t dora-slim
```

## Running the Container

```bash
docker run -it --rm --device=/dev/video0 dora-slim
```

## Running not in interactive

```bash
docker run --rm dora-slim dora --help
```

## Running with privilege as well as USB connection

```bash
docker run --rm --device=/dev/video0 dora-slim dora --help
```

## Usage

Once inside the container, you can:

```bash
## Create a virtual environment
uv venv --seed -p 3.11

## Install nodes dependencies of a remote graph
dora build https://raw.githubusercontent.com/dora-rs/dora/refs/heads/main/examples/object-detection/yolo.yml --uv

## Run yolo graph
dora run yolo.yml --uv
```

This container is designed to provide a consistent environment for Dora development without requiring complex setup on the host machine.

+ 40
- 0
examples/alexk-lcr/ASSEMBLING.md View File

@@ -0,0 +1,40 @@
# Dora pipeline Robots

AlexK Low Cost Robot is a low-cost robotic arm that can be teleoperated using a similar arm. This repository contains
the Dora pipeline to manipulate the arms, the camera, and record/replay episodes with LeRobot.

## Assembling

**Please read the instructions carefully before buying or printing the parts.**

You will need to get the parts for a Follower arm and a Leader:

- [AlexK Follower Low Cost Robot](https://github.com/AlexanderKoch-Koch/low_cost_robot/?tab=readme-ov-file#follower-arm)
- [AlexK Leader Low Cost Robot](https://github.com/AlexanderKoch-Koch/low_cost_robot/?tab=readme-ov-file#follower-arm)

You **must** assemble the arm **with the extension** to be able to do some of the tasks.

You then need to print the Follower arm and the Leader arm. The STL files are:

- [AlexK Follower Low Cost Robot](https://github.com/AlexanderKoch-Koch/low_cost_robot/tree/main/hardware/follower/stl)
- [AlexK Leader Low Cost Robot](https://github.com/AlexanderKoch-Koch/low_cost_robot/tree/main/hardware/leader/stl)

Some parts **must** be replaced by the ones in this repository:

- [Dora-LeRobot Base Leader Low Cost Robot](assets/stl/LEADER_Base.stl)

If you struggle buying XL330 Frame or XL330/XL430 Idler Wheel, here are STL files that can be printed instead:

- [XL330 Frame](assets/stl/XL330_Frame.stl)
- [XL330 Idler Wheel](assets/stl/XL330_Idler_Wheel.stl)
- [XL430 Idler Wheel](assets/stl/XL430_Idler_Wheel.stl)

Please then follow the [YouTube Tutorial by Alexander Koch](https://youtu.be/RckrXOEoWrk?si=ZXDnnlF6BQd_o7v8) to
assemble the arm correctly.
Note that the tutorial is for the arm without the extension, so you will have to adapt the assembly.

Then you can place the two cameras on your desk, following this [image]()

## License

This library is licensed under the [Apache License 2.0](../../LICENSE).

+ 90
- 0
examples/alexk-lcr/CONFIGURING.md View File

@@ -0,0 +1,90 @@
# Dora pipeline Robots

AlexK Low Cost Robot is a low-cost robotic arm that can be teleoperated using a similar arm. This repository contains
the Dora pipeline to manipulate the arms, the camera, and record/replay episodes with LeRobot.

## Configuring

Once you have assembled the robot, and installed the required software, you can configure the robot. It's essential to
configure it
correctly for the robot to work as expected. Here are the reasons why you need to configure the robot:

- You may have done some 'mistakes' during the assembly, like inverting the motors, or changing the offsets by rotating
the motors before assembling the robot. So your configuration will be different from the one we used to record the
data set.
- The recording pipeline needs to know the position of the motors to record the data set correctly. If the motors are
calibrated differently, the data set will be incorrect.

**Please read the instructions carefully before configuring the robot.**

The first thing to do is to configure the Servo BUS:

- Setting all the servos to the same baud rate (1M).
- Setting the ID of the servos from the base (1) to the gripper (6) for the Follower and Leader arms.

Those steps can be done using the official wizard provided by the
manufacturer [ROBOTICS](https://emanual.robotis.com/docs/en/software/dynamixel/dynamixel_wizard2/).

After that, you need to configure the homing offsets and drive mode to have the same behavior for every user. We
recommend using our on-board tool to set all of that automatically:

- Connect the Follower arm to your computer.
- Retrieve the device port from the official wizard.
- Run the configuration tool with the following command and follow the instructions:

```bash
cd dora/

# If you are using a custom environment, you will have to activate it before running the command
source [your_custom_env_bin]/activate

# If you followed the installation instructions, you can run the following command
source venv/bin/activate # On Linux
source venv/Scripts/activate # On Windows bash
venv\Scripts\activate.bat # On Windows cmd
venv\Scripts\activate.ps1 # On Windows PowerShell

python ./examples/alexk-lcr/configure.py --port /dev/ttyUSB0 --follower --left # (or right)
```

**Note:** change `/dev/ttyUSB0` to the device port you retrieved from the official wizard (like `COM3` on Windows).
**Note:** The configuration tool will disable all torque so you can move the arm freely to the Position 1.
**Note:** You will be asked to set the arm in two different positions. The two positions are:

![image](https://github.com/Hennzau/Hennzau/blob/main/assets/Koch_arm_positions.png)

**Node:** You will be asked the path of the configuration file, you can press enter to use the default one.

- Repeat the same steps for the Leader arm:

```bash
python ./examples/alexk-lcr/configure.py --port /dev/ttyUSB1 --leader --left # (or right)
```

**Note:** change `/dev/ttyUSB1` to the device port you retrieved from the official wizard (like `COM4` on Windows).
**Note:** The configuration tool will disable all torque so you can move the arm freely to the Position 1.
**Node:** You will be asked the path of the configuration file, you can press enter to use the default one.

After following the guide, you should have the following configuration:

![image](https://github.com/Hennzau/Hennzau/blob/main/assets/Koch_arm_wanted_configuration.png)

This configuration has to be exported into environment variables inside the graph file. Here is an example of the
configuration:

```YAML
nodes:
- id: lcr-follower
env:
PORT: /dev/ttyUSB0
CONFIG: ../configs/follower.left.json # relative path to `./examples/alexk-lcr/configs/follower.json`

- id: lcr-to-lcr
env:
LEADER_CONTROL: ../configs/leader.left.json
FOLLOWER_CONTROL: ../configs/follower.left.json
```

## License

This library is licensed under the [Apache License 2.0](../../LICENSE).

+ 82
- 0
examples/alexk-lcr/INSTALLATION.md View File

@@ -0,0 +1,82 @@
# Dora pipeline Robots

AlexK Low Cost Robot is a low-cost robotic arm that can be teleoperated using a similar arm. This repository contains
the Dora pipeline to manipulate the arms, the camera, and record/replay episodes with LeRobot.

## Installation

Dataflow-oriented robotic application (Dora) is a framework that makes creation of robotic applications fast and simple.
See [Dora repository](https://github.com/dora-rs/dora).

**Please read the instructions carefully before installing the required software and environment to run the robot.**

You must install Dora before attempting to run the AlexK Low Cost Robot pipeline. Here are the steps to install Dora:

- Install Rust by following the instructions at [Rustup](https://rustup.rs/). (You may need to install Visual Studio C++
build tools on Windows.)
- Install Dora by running the following command:

```bash
cargo install dora-cli
```

Now you're ready to run Rust dataflow applications! We decided to only make Python dataflow for AlexK Low Cost Robot, so
you may need to setup your Python environment:

- Install Python 3.12 or later by following the instructions at [Python](https://www.python.org/downloads/).
- Clone this repository by running the following command:

```bash
git clone https://github.com/dora-rs/dora
```

- Open a bash terminal and navigate to the repository by running the following command:

```bash
cd dora
```

- Create a virtual environment by running the following command (you can find where is all your pythons executable with
the command `whereis python3` on Linux, on default for Windows it's located
in `C:\Users\<User>\AppData\Local\Programs\Python\Python3.12\python.exe)`):

```bash
path_to_your_python3_executable -m venv venv
```

- Activate the virtual environment and install the required Python packages by running the following command:

```bash
# If you are using a custom environment, you will have to activate it before running the command
source [your_custom_env_bin]/activate

# If you followed the installation instructions, you can run the following command
source venv/bin/activate # On Linux
source venv/Scripts/activate # On Windows bash
venv\Scripts\activate.bat # On Windows cmd
venv\Scripts\activate.ps1 # On Windows PowerShell

pip install -r examples/alexk-lcr/requirements.txt
```

If you want to install the required Python packages in development mode, you can run the following command, but you will
have to avoid using `dora build` during execution procedure:

```bash
pip install -r examples/alexk-lcr/development.txt # You **MUST** be inside dora to run this command
```

**Note**: You're totally free to use your own Python environment, a Conda environment, or whatever you prefer, you will
have to activate
your custom python environment before running `dora up && dora start [graph].yml`.

In order to record episodes, you need ffmpeg installed on your system. You can download it from
the [official website](https://ffmpeg.org/download.html). If you're on Windows, you can download the latest build
from [here](https://www.gyan.dev/ffmpeg/builds/). You can
extract the zip file and add the `bin` folder to your PATH.
If you're on Linux, you can install ffmpeg using the package manager of your distribution. (
e.g `sudo apt install ffmpeg` on Ubuntu, `brew install ffmpeg` on macOS)

## License

This library is licensed under the [Apache License 2.0](../../LICENSE).

+ 174
- 0
examples/alexk-lcr/README.md View File

@@ -0,0 +1,174 @@
# Dora pipeline Robots

AlexK Low Cost Robot is a low-cost robotic arm that can be teleoperated using a similar arm. This repository contains
the Dora pipeline to record episodes for LeRobot.

## Assembling

Check the [ASSEMBLING.md](ASSEMBLING.md) file for instructions on how to assemble the robot from scratch using the
provided parts from the [AlexK Low Cost Robot](https://github.com/AlexanderKoch-Koch/low_cost_robot)

## Installation

Check the [INSTALLATION.md](INSTALLATION.md) file for instructions on how to install the required software and
environment
to run the robot.

## Configuring

Check the [CONFIGURING.md](CONFIGURING.md) file for instructions on how to configure the robot to record episodes for
LeRobot and teleoperate the robot.

## Recording

It's probably better to check the [examples](#examples) below before trying to record episodes. It will give you a
better
understanding of how Dora works.

Check the [RECORDING.md](RECORDING.md) file for instructions on how to record episodes for LeRobot.

## Examples

There are also some other example applications in the `graphs` folder. Have fun!

Here is a list of the available examples:

- `mono_teleop_real.yml`: A simple real teleoperation pipeline that allows you to control a follower arm using a leader
arm. It
does not record the episodes, so you don't need to have a camera.

You must configure the arms, retrieve the device port, and modify the file `mono_teleop_real.yml` to set the correct
environment variables. (e.g. `PORT` and `CONFIG`, `LEADER_CONTROL` and `FOLLOWER_CONTROL`)

```bash
cd dora/

# If you are using a custom environment, you will have to activate it before running the command
source [your_custom_env_bin]/activate

# If you followed the installation instructions, you can run the following command
source venv/bin/activate # On Linux
source venv/Scripts/activate # On Windows bash
venv\Scripts\activate.bat # On Windows cmd
venv\Scripts\activate.ps1 # On Windows PowerShell

dora build ./examples/alexk-lcr/graphs/mono_teleop_real.yml # Only the first time, it will install all the requirements if needed

dora up
dora start ./examples/alexk-lcr/graphs/mono_teleop_real.yml
```

[![](https://mermaid.ink/img/pako:eNqVUsFOxCAQ_RUy591Urz14MF496W0xZCzTlkihmUI2ZrP_LtDtutomRg4w83jvMcCcoPGaoAZxGa31x6ZHDuL1UTohbMPKEmriJTMuEI_eYqAFar1NskyZ4nvHOPZCKaU9Y1rEIQdvmXu7G8xAfJkzqUSFJUQWVAWoBmOtmar7u4OU17gqPHJaujJtK8R-L8ZorRr9ZILxLgEPGxdaqi_8hYqTWPC1fuMJZsvfFjP6p8H_qv9-7dWHZFHn8UaUijiyCaR-wmsv2EE6f0CjUzecsreE0NNAEuoUauQPCdKdEw9j8C-froE6cKQdsI9dD3WLdkpZHHWq5Mlg-urhipI2wfPz3Gyl585fka3hkA?type=png)](https://mermaid.live/edit#pako:eNqVUsFOxCAQ_RUy591Urz14MF496W0xZCzTlkihmUI2ZrP_LtDtutomRg4w83jvMcCcoPGaoAZxGa31x6ZHDuL1UTohbMPKEmriJTMuEI_eYqAFar1NskyZ4nvHOPZCKaU9Y1rEIQdvmXu7G8xAfJkzqUSFJUQWVAWoBmOtmar7u4OU17gqPHJaujJtK8R-L8ZorRr9ZILxLgEPGxdaqi_8hYqTWPC1fuMJZsvfFjP6p8H_qv9-7dWHZFHn8UaUijiyCaR-wmsv2EE6f0CjUzecsreE0NNAEuoUauQPCdKdEw9j8C-froE6cKQdsI9dD3WLdkpZHHWq5Mlg-urhipI2wfPz3Gyl585fka3hkA)

- `bi_teleop_real.yml`: A simple real tele operation pipeline that allows you to control two follower arm using two
leader arm
(left and right). It does not record the episodes, so you don't need to have a camera.

You must configure the arms, retrieve the device port, and modify the file `bi_teleop_real.yml` to set the correct
environment variables. (e.g. `PORT` and `CONFIG`)

```bash
cd dora/

# If you are using a custom environment, you will have to activate it before running the command
source [your_custom_env_bin]/activate

# If you followed the installation instructions, you can run the following command
source venv/bin/activate # On Linux
source venv/Scripts/activate # On Windows bash
venv\Scripts\activate.bat # On Windows cmd
venv\Scripts\activate.ps1 # On Windows PowerShell

dora build ./examples/alexk-lcr/graphs/bi_teleop_real.yml # Only the first time, it will install all the requirements if needed

dora up
dora start ./examples/alexk-lcr/graphs/bi_teleop_real.yml
```

[![](https://mermaid.ink/img/pako:eNqlVMFugzAM_ZUo51ZsVw47TLvutN2aKsqIgWghQSZRNVX99yWhtAXBNjoOxrz4vdgmzpEWVgLNKTk_pbaHohboyPszM4ToArmG0gUjJOAIUsYBtlYLByO8tDqoXINRVfVUoMdmFPqFq0TnPyoUbU0459KiCC-yi84-Mm5XnWoAzzYGJS9FERIJWQKyRmmtuuzxYcfYxc9SHBjJTDLzDLLdktZrzVvbKaesCcDTjy0a6kjMgSQ6MuALSkud7XeYivXo36TuKGv6O6eykV5ZcUMPOR1QOeBjeFF1XVLLx2l9t385huv6PSt2T23zA_Sflk916YaGjBqhZJj9Y9yHUVdDA4zmwZUCPxll5hTihHf27csUNHfoYUPR-qqmeSl0F758K0M-L0qEMWwuKEjlLL72V0u6YU7fOOqbHg?type=png)](https://mermaid.live/edit#pako:eNqlVMFugzAM_ZUo51ZsVw47TLvutN2aKsqIgWghQSZRNVX99yWhtAXBNjoOxrz4vdgmzpEWVgLNKTk_pbaHohboyPszM4ToArmG0gUjJOAIUsYBtlYLByO8tDqoXINRVfVUoMdmFPqFq0TnPyoUbU0459KiCC-yi84-Mm5XnWoAzzYGJS9FERIJWQKyRmmtuuzxYcfYxc9SHBjJTDLzDLLdktZrzVvbKaesCcDTjy0a6kjMgSQ6MuALSkud7XeYivXo36TuKGv6O6eykV5ZcUMPOR1QOeBjeFF1XVLLx2l9t385huv6PSt2T23zA_Sflk916YaGjBqhZJj9Y9yHUVdDA4zmwZUCPxll5hTihHf27csUNHfoYUPR-qqmeSl0F758K0M-L0qEMWwuKEjlLL72V0u6YU7fOOqbHg)

- `mono_teleop_simu.yml`: A simple simulation tele operation pipeline that allows you to control a simulated follower
arm using a leader arm. It does not record the episodes, so you don't need to have a camera.

You must configure the arms, retrieve the device port, and modify the file `mono_teleop_simu.yml` to set the correct
environment variables. (e.g. `PORT` and `CONFIG`)

```bash
cd dora/


# If you are using a custom environment, you will have to activate it before running the command
source [your_custom_env_bin]/activate

# If you followed the installation instructions, you can run the following command
source venv/bin/activate # On Linux
source venv/Scripts/activate # On Windows bash
venv\Scripts\activate.bat # On Windows cmd
venv\Scripts\activate.ps1 # On Windows PowerShell

dora build ./examples/alexk-lcr/graphs/mono_teleop_simu.yml # Only the first time, it will install all the requirements if needed

dora up
dora start ./examples/alexk-lcr/graphs/mono_teleop_simu.yml
```

[![](https://mermaid.ink/img/pako:eNp1UstuwyAQ_JUV50Rurz70UPXaU3sLFdqatY2CwcKgqIry711w4ubhcoDdYWZ3eBxF4zWJWsB5tNYfmh5DhM9X6QBsE5Ql1BQumXGRwugtRrpArbcsy5QpfXcBxx6UUtoH5AV2OfjK3OvdaAYK5zmTSlRYAFlQFaAajLVmqp6fdlIucVV45LR0Zbp1AdstRNPsAScYk7Vq9JOJxjveeFk50Jxl1UJk5Yw-au-Ov2a1lFpt_HdR_yuL9TXBXffM7TxedWHXh2AiqVv4sZbYCG47oNH88sdcW4rY00BS1BxqDHsppDsxD1P0Hz-uEXUMiTYi-NT1om7RTpylUbOTN4P8rMOCkjbRh_f5Y5X_dfoF5ZjY9g?type=png)](https://mermaid.live/edit#pako:eNp1UstuwyAQ_JUV50Rurz70UPXaU3sLFdqatY2CwcKgqIry711w4ubhcoDdYWZ3eBxF4zWJWsB5tNYfmh5DhM9X6QBsE5Ql1BQumXGRwugtRrpArbcsy5QpfXcBxx6UUtoH5AV2OfjK3OvdaAYK5zmTSlRYAFlQFaAajLVmqp6fdlIucVV45LR0Zbp1AdstRNPsAScYk7Vq9JOJxjveeFk50Jxl1UJk5Yw-au-Ov2a1lFpt_HdR_yuL9TXBXffM7TxedWHXh2AiqVv4sZbYCG47oNH88sdcW4rY00BS1BxqDHsppDsxD1P0Hz-uEXUMiTYi-NT1om7RTpylUbOTN4P8rMOCkjbRh_f5Y5X_dfoF5ZjY9g)

- `mono_teleop_real_and_simu.yml`: A simple real and simulation tele operation pipeline that allows you to control a
simulated and real follower arm using a real leader arm. It does not record the episodes, so you don't need to have a
camera.

You must configure the arms, retrieve the device port, and modify the file `mono_teleop_real_and_simu.yml` to set the
correct
environment variables. (e.g. `PORT` and `CONFIG`)

```bash
cd dora/


# If you are using a custom environment, you will have to activate it before running the command
source [your_custom_env_bin]/activate

# If you followed the installation instructions, you can run the following command
source venv/bin/activate # On Linux
source venv/Scripts/activate # On Windows bash
venv\Scripts\activate.bat # On Windows cmd
venv\Scripts\activate.ps1 # On Windows PowerShell

dora build ./examples/alexk-lcr/graphs/mono_teleop_real_and_simu.yml # Only the first time, it will install all the requirements if needed

dora up
dora start ./examples/alexk-lcr/graphs/mono_teleop_real_and_simu.yml
```

[![](https://mermaid.ink/img/pako:eNqdU8luwyAQ_RXEOZHbqw89VL321N5ChajBMQqLxaKoivLvHXCM3IS0lX3Aw-O9YRbmhDvLBW4xuny9ssduYC6g92diEFKdo0owLty8kyYIN1rFgpih3iqQVSnUSx2veRfQx8-9Y-OAKKXcOgY_tEvGRxIsT4PUoJrWRMpWZiGUBE0GGi2Vkr55fNgRUuwm84ThxOSlEgrablGQ3QExj8aoFB2tl0FaAwdPlRLM4qQrVNAWpzf6StEml9cuJvRfDm5SgPQKf9mSWoXyvdVUf2lmEu0tW4gg4qOT0Oaf8D1fq3Muz2hdLn_Kc_fvqmrBrK5FVuMNhhg0kxxm75TuIDgMQguCWzA5cweCiTkDj8Vg375Mh9vgothgZ-N-wG3PlIddHDlE9CIZzIouqOAyWPc6jXae8PM3I_doSQ?type=png)](https://mermaid.live/edit#pako:eNqdU8luwyAQ_RXEOZHbqw89VL321N5ChajBMQqLxaKoivLvHXCM3IS0lX3Aw-O9YRbmhDvLBW4xuny9ssduYC6g92diEFKdo0owLty8kyYIN1rFgpih3iqQVSnUSx2veRfQx8-9Y-OAKKXcOgY_tEvGRxIsT4PUoJrWRMpWZiGUBE0GGi2Vkr55fNgRUuwm84ThxOSlEgrablGQ3QExj8aoFB2tl0FaAwdPlRLM4qQrVNAWpzf6StEml9cuJvRfDm5SgPQKf9mSWoXyvdVUf2lmEu0tW4gg4qOT0Oaf8D1fq3Muz2hdLn_Kc_fvqmrBrK5FVuMNhhg0kxxm75TuIDgMQguCWzA5cweCiTkDj8Vg375Mh9vgothgZ-N-wG3PlIddHDlE9CIZzIouqOAyWPc6jXae8PM3I_doSQ)

- `mono_replay_real.yml`: A simple real replay pipeline that allows you to replay a recorded episode.

You must configure the dataset path and episode index in the file `mono_replay_real.yml` to set the correct
environment variables. (e.g. `PATH`, `EPISODE`). You must also configure the follower arm, retrieve the device port, and
modify the file `mono_replay_real.yml` to set the correct environment variables. (e.g. `PORT` and `CONFIG`)

```bash
cd dora/

# If you are using a custom environment, you will have to activate it before running the command
source [your_custom_env_bin]/activate

# If you followed the installation instructions, you can run the following command
source venv/bin/activate # On Linux
source venv/Scripts/activate # On Windows bash
venv\Scripts\activate.bat # On Windows cmd
venv\Scripts\activate.ps1 # On Windows PowerShell

dora build ./examples/alexk-lcr/graphs/mono_replay_real.yml # Only the first time, it will install all the requirements if needed

dora up
dora start ./examples/alexk-lcr/graphs/mono_replay_real.yml
```

[![](https://mermaid.ink/img/pako:eNptkbFuAyEMhl_F8pzohmw3dKiydmq3UCH38N2hcoB8oCiK8u4BmkZNWwbz8_PZCPuMQzCMPcJtjS4ch5kkwduz8gDC0dFJD86yT9Vwg-gxuIKxKL_mj0kozqC1NkGobHCo4r2yP2-TXVhusUJNNQqgJnTN6BbrnF273e6g1F13jWNvlG_h_wzYbiFm53QMq002-GI8_f3Ag9FyvnFa4Sg2sZ4C_ary-GvcYHl5IWtK5861qMI088IK-yINyadC5S-Fo5zC68kP2CfJvEEJeZqxH8mt5ZSjocR7S6VNy91lY1OQl6_BtPlcrmjBlKg?type=png)](https://mermaid.live/edit#pako:eNptkbFuAyEMhl_F8pzohmw3dKiydmq3UCH38N2hcoB8oCiK8u4BmkZNWwbz8_PZCPuMQzCMPcJtjS4ch5kkwduz8gDC0dFJD86yT9Vwg-gxuIKxKL_mj0kozqC1NkGobHCo4r2yP2-TXVhusUJNNQqgJnTN6BbrnF273e6g1F13jWNvlG_h_wzYbiFm53QMq002-GI8_f3Ag9FyvnFa4Sg2sZ4C_ary-GvcYHl5IWtK5861qMI088IK-yINyadC5S-Fo5zC68kP2CfJvEEJeZqxH8mt5ZSjocR7S6VNy91lY1OQl6_BtPlcrmjBlKg)

## License

This library is licensed under the [Apache License 2.0](../../LICENSE).

+ 80
- 0
examples/alexk-lcr/RECORDING.md View File

@@ -0,0 +1,80 @@
# Dora pipeline Robots

AlexK Low Cost Robot is a low-cost robotic arm that can be teleoperated using a similar arm. This repository contains
the Dora pipeline to manipulate the arms, the camera, and record/replay episodes with LeRobot.

## Recording

This section explains how to record episodes for LeRobot using the AlexK Low Cost Robot.

Recording is the process of tele operating the robot and saving the episodes to a dataset. The dataset is used to train
the robot to perform tasks autonomously.

To record episodes with Dora, you have to configure the Dataflow `record_mono_teleop_real.yml` file to integrate the
arms and the camera. The graph file is located in the `graphs` folder.

Make sure to:

- Adjust the serial ports of `lcr-leader` and `lcr-follower` in the `record_mono_teleop_real.yml` file.
- Adjust the camera PATH in the `record_mono_teleop_real.yml` file.
- Adjust image and video WIDTH and HEIGHT in the `record_mono_teleop_real.yml` file, if needed.
- Adjust recording framerate with your camera framerate in the `record_mono_teleop_real.yml` file.
- Adjust CONFIG path environment variables in the `record_mono_teleop_real.yml` file for both arms if needed.
- Adjust `LEADER_CONTROL` and `FOLLOWER_CONTROL` environment variables in the `record_mono_teleop_real.yml` file if
needed.

You can now start the Dora pipeline to record episodes for LeRobot:

```bash
cd dora

# If you are using a custom environment, you will have to activate it before running the command
source [your_custom_env_bin]/activate

# If you followed the installation instructions, you can run the following command
source venv/bin/activate # On Linux
source venv/Scripts/activate # On Windows bash
venv\Scripts\activate.bat # On Windows cmd
venv\Scripts\activate.ps1 # On Windows PowerShell

dora build ./examples/alexk-lcr/graphs/record_mono_teleop_real.yml # Only the first time, it will install all the requirements if needed

dora up
dora start ./examples/alexk-lcr/graphs/record_mono_teleop_real.yml
```

Then, you can tele operate the follower with the leader. A window will pop up showing the camera feed, and some text.

1. Press space to start/stop recording
2. Press return if you want to tell the recording that you failed the current episode, or the previous episode if you
have not started the current one
3. Close the window to stop the recording
4. Write down the location of the logs (e.g `018fc3a8-3b76-70f5-84a2-22b84df24739`), this is where the
dataset (and logs) are stored.

You can now use our script to convert the logs to an understandable dataset:

```bash
cd dora

# If you are using a custom environment, you will have to activate it before running the command
source [your_custom_env_bin]/activate

# If you followed the installation instructions, you can run the following command
source venv/bin/activate # On Linux
source venv/Scripts/activate # On Windows bash
venv\Scripts\activate.bat # On Windows cmd
venv\Scripts\activate.ps1 # On Windows PowerShell

python ./datasets/build_dataset.py --record-path [path_to_recorded_logs] --dataset-name [dataset_name] --framerate [framerate]
```

**Note:** On default, the framerate is 30. If you have recorded with a different framerate, you will have to adjust it.

## The dora graph

[![](https://mermaid.ink/img/pako:eNqdVMFu2zAM_RVB57berjn0MOzaU3eLCoGR6ESobBqSnK4o-u-j5NizE6Np64NBPfE9Us-03qQhi3IjxempPb2YA4Qk_vxSrRDeBO0RLIZx5dqEoSMPCUeoJs-0IYU6bM1RG2gwQAaOziJpBmkUwUA7SjqgoWAzYinAabmtZgulnlQb-90-QHcQWuuyp7XY5uApU-e7yXHN0zsnlahkDSWqAlSN897F6uePrVJTXJU8bLmf8jpvU9zeiuTMs4Aout573VF0yVHLG_crLo2WZN6UytwRv-Sv-DpInksM6HWBi_75YFPy_JN99aQL7rJwJu8JZiRWeQkuoV7C3-jDNbDHQryYsZWzdi7ywGXyKeQ2Lf4t_IuRXAhm-v9an83lQiXgj1an4Xirc34-hNMx1ymf8RfMZOn85_m6L1fZNTiPtsxxifRVjYV9C7doFzEcIbd-V8B4x57qvltt5YNfai4U02DSQkDeSLa8AWf5onvLckqmAzao5IZDC-FZSdW-cx70iR5fWyM3KfR4IwP1-4Pc1OAjr_rOsvxvB3zlNBOK1iUKD8M9Wq7T93-SiOfx?type=png)](https://mermaid.live/edit#pako:eNqdVMFu2zAM_RVB57berjn0MOzaU3eLCoGR6ESobBqSnK4o-u-j5NizE6Np64NBPfE9Us-03qQhi3IjxempPb2YA4Qk_vxSrRDeBO0RLIZx5dqEoSMPCUeoJs-0IYU6bM1RG2gwQAaOziJpBmkUwUA7SjqgoWAzYinAabmtZgulnlQb-90-QHcQWuuyp7XY5uApU-e7yXHN0zsnlahkDSWqAlSN897F6uePrVJTXJU8bLmf8jpvU9zeiuTMs4Aout573VF0yVHLG_crLo2WZN6UytwRv-Sv-DpInksM6HWBi_75YFPy_JN99aQL7rJwJu8JZiRWeQkuoV7C3-jDNbDHQryYsZWzdi7ywGXyKeQ2Lf4t_IuRXAhm-v9an83lQiXgj1an4Xirc34-hNMx1ymf8RfMZOn85_m6L1fZNTiPtsxxifRVjYV9C7doFzEcIbd-V8B4x57qvltt5YNfai4U02DSQkDeSLa8AWf5onvLckqmAzao5IZDC-FZSdW-cx70iR5fWyM3KfR4IwP1-4Pc1OAjr_rOsvxvB3zlNBOK1iUKD8M9Wq7T93-SiOfx)

## License

This library is licensed under the [Apache License 2.0](../../LICENSE).

+ 135
- 0
examples/alexk-lcr/assets/simulation/lift_cube.xml View File

@@ -0,0 +1,135 @@
<mujoco model="bd1 scene">
<option timestep="0.005"/>

<compiler angle="radian" autolimits="true"/>

<asset>
<mesh name="base" file="base.stl"/>
<mesh name="dc11_a01_spacer_dummy" file="dc11_a01_spacer_dummy.stl"/>
<mesh name="dc11_a01_dummy" file="dc11_a01_dummy.stl"/>
<mesh name="rotation_connector" file="rotation_connector.stl"/>
<mesh name="arm_connector" file="arm_connector.stl"/>
<mesh name="dc15_a01_horn_idle2_dummy" file="dc15_a01_horn_idle2_dummy.stl"/>
<mesh name="dc15_a01_case_m_dummy" file="dc15_a01_case_m_dummy.stl"/>
<mesh name="dc15_a01_case_f_dummy" file="dc15_a01_case_f_dummy.stl"/>
<mesh name="dc15_a01_horn_dummy" file="dc15_a01_horn_dummy.stl"/>
<mesh name="dc15_a01_case_b_dummy" file="dc15_a01_case_b_dummy.stl"/>
<mesh name="connector" file="connector.stl"/>
<mesh name="shoulder_rotation" file="shoulder_rotation.stl"/>
<mesh name="static_side" file="static_side.stl"/>
<mesh name="moving_side" file="moving_side.stl"/>
<texture type="skybox" builtin="gradient" rgb1="0.3 0.5 0.7" rgb2="0 0 0" width="512" height="3072" />
<texture type="2d" name="groundplane" builtin="checker" mark="edge" rgb1="0.2 0.3 0.4" rgb2="0.1 0.2 0.3" markrgb="0.8 0.8 0.8" width="300" height="300" />
<material name="groundplane" texture="groundplane" texuniform="true" texrepeat="5 5" reflectance="0.2" />
</asset>

<visual>
<headlight diffuse="0.6 0.6 0.6" ambient="0.3 0.3 0.3" specular="0 0 0" />
<rgba haze="0.15 0.25 0.35 1" />
<global azimuth="150" elevation="-20" offheight="640" />
</visual>

<worldbody>
<light pos="0 0 3" dir="0 0 -1" directional="false" />
<body name="floor">
<geom pos="0 0 0" name="floor" size="0 0 .125" type="plane" material="groundplane" conaffinity="1" contype="1" />
</body>

<body name="cube" pos="0.1 0.1 0.01">
<freejoint name="cube"/>
<inertial pos="0 0 0" mass="0.1" diaginertia="0.00001125 0.00001125 0.00001125"/>
<geom friction="0.5" condim="3" pos="0 0 0" size="0.015 0.015 0.015" type="box" name="cube" rgba="0.5 0 0 1" priority="1"/>
</body>


<camera name="camera_front" pos="0.049 0.888 0.317" xyaxes="-0.998 0.056 -0.000 -0.019 -0.335 0.942"/>
<camera name="camera_top" pos="0 0 1" euler="0 0 0" mode="fixed"/>
<camera name="camera_vizu" pos="-0.1 0.6 0.3" quat="-0.15 -0.1 0.6 1"/>
<geom pos="0.0401555 -0.0353754 -0.0242427" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="base"/>
<geom pos="0.0511555 0.0406246 0.0099573" quat="0 0 0 1" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="0.0291555 0.000624643 0.0099573" quat="0 0 0 1" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="0.0511555 0.000624643 -0.0184427" quat="0 0 1 0" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="0.0401555 0.0326246 -0.0042427" type="mesh" rgba="0.498039 0.498039 0.498039 1" mesh="dc11_a01_dummy"/>
<geom pos="0.0291555 0.000624643 -0.0184427" quat="0 0.707107 0.707107 0" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="0.0291555 0.0406246 0.0099573" quat="0.707107 0 0 -0.707107" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="0.0511555 0.0406246 -0.0184427" quat="0 0.707107 -0.707107 0" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="0.0291555 0.0406246 -0.0184427" quat="0 -1 0 0" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="0.0511555 0.000624643 0.0099573" quat="0.707107 0 0 0.707107" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>

<body name="pitch1_assembly" pos="0.0401555 0.0326246 0.0166573">

<inertial pos="-0.000767103 -0.0121505 0.0134241" quat="0.498429 0.53272 -0.473938 0.493113" mass="0.0606831" diaginertia="1.86261e-05 1.72746e-05 1.11693e-05"/>
<joint name="shoulder_pan_joint" pos="0 0 0" axis="0 0 1" range="-3.14159 3.14159" damping="0.1" actuatorfrcrange="-0.5 0.5" actuatorfrclimited="true"/>
<geom pos="0 0 -0.0209" type="mesh" rgba="0.231373 0.380392 0.705882 1" mesh="rotation_connector"/>
<geom pos="-0.014 0.008 0.0264" quat="0 -0.707107 0 0.707107" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="-0.014 -0.032 0.0044" quat="0 -0.707107 0 0.707107" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="0.0144 -0.032 0.0264" quat="0.707107 0 0.707107 0" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="0.0002 0 0.0154" quat="0.707107 0 -0.707107 0" type="mesh" rgba="0.498039 0.498039 0.498039 1" mesh="dc11_a01_dummy"/>
<geom pos="0.0144 -0.032 0.0044" quat="0.5 0.5 0.5 0.5" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="-0.014 0.008 0.0044" quat="0.5 0.5 -0.5 -0.5" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="0.0144 0.008 0.0264" quat="0.5 -0.5 0.5 -0.5" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="0.0144 0.008 0.0044" quat="0 0.707107 0 0.707107" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="-0.014 -0.032 0.0264" quat="0.5 -0.5 -0.5 0.5" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>

<body name="pitch2_assembly" pos="-0.0188 0 0.0154" quat="0 0.707107 0 0.707107">

<inertial pos="0.0766242 -0.00031229 0.0187402" quat="0.52596 0.513053 0.489778 0.469319" mass="0.0432446" diaginertia="7.21796e-05 7.03107e-05 1.07533e-05"/>
<joint name="shoulder_lift_joint" pos="0 0 0" axis="0 0 1" range="-1.5708 1.22173" damping="0.1" actuatorfrcrange="-0.5 0.5" actuatorfrclimited="true"/>
<geom pos="0 0 0.019" quat="0.5 -0.5 -0.5 -0.5" type="mesh" rgba="0.980392 0.713726 0.00392157 1" mesh="arm_connector"/>
<geom pos="0.1083 -0.0148 0.03035" quat="1 0 0 0" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc15_a01_horn_idle2_dummy"/>
<geom pos="0.1083 -0.0148 0.01075" quat="0 -1 0 0" type="mesh" rgba="0.615686 0.811765 0.929412 1" mesh="dc15_a01_case_m_dummy"/>
<geom pos="0.1083 -0.0148 0.01075" quat="0 -1 0 0" type="mesh" rgba="0.980392 0.713726 0.00392157 1" mesh="dc15_a01_case_f_dummy"/>
<geom pos="0.1083 -0.0148 0.00715" quat="0 0 0.707107 -0.707107" type="mesh" rgba="0.972549 0.529412 0.00392157 1" mesh="dc15_a01_horn_dummy"/>
<geom pos="0.1083 -0.0148 0.03025" quat="0 -1 0 0" type="mesh" rgba="0.498039 0.498039 0.498039 1" mesh="dc15_a01_case_b_dummy"/>

<body name="pitch3_assembly" pos="0.1083 -0.0148 0.00425" quat="0.707107 0 0 0.707107">
<inertial pos="-0.0551014 -0.00287792 0.0144813" quat="0.500323 0.499209 0.499868 0.5006" mass="0.0788335" diaginertia="6.80912e-05 6.45748e-05 9.84479e-06"/>
<joint name="elbow_flex_joint" pos="0 0 0" axis="0 0 1" range="-1.48353 1.74533" damping="0.1" actuatorfrcrange="-0.5 0.5" actuatorfrclimited="true"/>
<geom pos="-0.00863031 0.00847376 0.0145" quat="0.5 0.5 0.5 -0.5" type="mesh" rgba="0.615686 0.811765 0.929412 1" mesh="connector"/>
<geom pos="-0.100476 -0.00269986 0.02635" quat="0.707107 0 0 -0.707107" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc15_a01_horn_idle2_dummy"/>
<geom pos="-0.100476 -0.00269986 0.00675" quat="0 -0.707107 0.707107 0" type="mesh" rgba="0.615686 0.811765 0.929412 1" mesh="dc15_a01_case_m_dummy"/>
<geom pos="-0.100476 -0.00269986 0.00675" quat="0 -0.707107 0.707107 0" type="mesh" rgba="0.980392 0.713726 0.00392157 1" mesh="dc15_a01_case_f_dummy"/>
<geom pos="-0.100476 -0.00269986 0.00315" quat="0.5 -0.5 -0.5 0.5" type="mesh" rgba="0.972549 0.529412 0.00392157 1" mesh="dc15_a01_horn_dummy"/>
<geom pos="-0.100476 -0.00269986 0.02625" quat="0 -0.707107 0.707107 0" type="mesh" rgba="0.498039 0.498039 0.498039 1" mesh="dc15_a01_case_b_dummy"/>

<body name="effector_roll_assembly" pos="-0.100476 -0.00269986 0.02925" quat="0 -0.707107 -0.707107 0">
<inertial pos="-1.65017e-05 -0.02659 0.0195388" quat="0.936813 0.349829 -0.00055331 -0.000300569" mass="0.0240506" diaginertia="6.03208e-06 4.12894e-06 3.3522e-06"/>
<joint name="wrist_flex_joint" pos="0 0 0" axis="0 0 1" range="-1.91986 1.91986" damping="0.1" actuatorfrcrange="-0.5 0.5" actuatorfrclimited="true"/>
<geom pos="-0.0109998 -0.0190002 0.039" quat="0.707107 -0.707107 0 0" type="mesh" rgba="0.615686 0.811765 0.929412 1" mesh="shoulder_rotation"/>
<geom pos="-7.44154e-06 -0.0385002 0.0133967" quat="0 0 0.707107 -0.707107" type="mesh" rgba="0.615686 0.811765 0.929412 1" mesh="dc15_a01_case_m_dummy"/>
<geom pos="-7.44154e-06 -0.0385002 0.0133967" quat="0 0 0.707107 -0.707107" type="mesh" rgba="0.980392 0.713726 0.00392157 1" mesh="dc15_a01_case_f_dummy"/>
<geom pos="-7.44154e-06 -0.0421002 0.0133967" quat="0 1 0 0" type="mesh" rgba="0.972549 0.529412 0.00392157 1" mesh="dc15_a01_horn_dummy"/>
<geom pos="-7.44154e-06 -0.0190002 0.0133967" quat="0 0 0.707107 -0.707107" type="mesh" rgba="0.498039 0.498039 0.498039 1" mesh="dc15_a01_case_b_dummy"/>

<body name="gripper_assembly" pos="-7.44154e-06 -0.0450002 0.0133967" quat="0.5 -0.5 -0.5 -0.5">
<inertial pos="-0.00548595 -0.000433143 -0.0190793" quat="0.700194 0.164851 0.167361 0.674197" mass="0.0360627" diaginertia="1.3261e-05 1.231e-05 5.3532e-06"/>
<joint name="wrist_roll_joint" pos="0 0 0" axis="0 0 1" range="-2.96706 2.96706" damping="0.1" actuatorfrcrange="-0.5 0.5" actuatorfrclimited="true"/>
<geom pos="-0.00075 -0.01475 -0.02" quat="0.707107 -0.707107 0 0" type="mesh" rgba="0.917647 0.917647 0.917647 1" mesh="static_side"/>
<geom pos="0.00755 0.01135 -0.013" quat="0.5 -0.5 0.5 0.5" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc15_a01_horn_idle2_dummy"/>
<geom pos="0.00755 -0.00825 -0.013" quat="0.5 0.5 0.5 -0.5" type="mesh" rgba="0.615686 0.811765 0.929412 1" mesh="dc15_a01_case_m_dummy"/>
<geom pos="0.00755 -0.00825 -0.013" quat="0.5 0.5 0.5 -0.5" type="mesh" rgba="0.980392 0.713726 0.00392157 1" mesh="dc15_a01_case_f_dummy"/>
<geom pos="0.00755 -0.01185 -0.013" quat="0 -0.707107 0 -0.707107" type="mesh" rgba="0.972549 0.529412 0.00392157 1" mesh="dc15_a01_horn_dummy"/>
<geom pos="0.00755 0.01125 -0.013" quat="0.5 0.5 0.5 -0.5" type="mesh" rgba="0.498039 0.498039 0.498039 1" mesh="dc15_a01_case_b_dummy"/>

<body name="moving_side" pos="0.00755 -0.01475 -0.013" quat="0.707107 -0.707107 0 0">
<inertial pos="-0.000395599 0.022415 0.0145636" quat="0.722353 0.689129 0.0389102 0.0423547" mass="0.0089856" diaginertia="3.28451e-06 2.24898e-06 1.41539e-06"/>
<joint name="gripper_joint" pos="0 0 0" axis="0 0 1" range="-1.74533 0.0523599" damping="0.1" actuatorfrcrange="-0.5 0.5" actuatorfrclimited="true"/>
<geom pos="-0.00838199 -0.000256591 -0.003" type="mesh" rgba="0.768627 0.886275 0.952941 1" mesh="moving_side"/>
</body>
</body>
</body>
</body>
</body>
</body>
</worldbody>

<actuator>
<position joint="shoulder_pan_joint" ctrlrange="-3.14159 3.14159" ctrllimited="true" kp="20" />
<position joint="shoulder_lift_joint" ctrlrange="-1.5708 1.22173" ctrllimited="true" kp="20" />
<position joint="elbow_flex_joint" ctrlrange="-1.48353 1.74533" ctrllimited="true" kp="20" />
<position joint="wrist_flex_joint" ctrlrange="-1.91986 1.91986" ctrllimited="true" kp="20" />
<position joint="wrist_roll_joint" ctrlrange="-2.96706 2.96706" ctrllimited="true" kp="20" />
<position joint="gripper_joint" ctrlrange="-1.74533 0.0523599" ctrllimited="true" kp="20" />
</actuator>
</mujoco>

+ 135
- 0
examples/alexk-lcr/assets/simulation/pick_place_cube.xml View File

@@ -0,0 +1,135 @@
<mujoco model="bd1 scene">
<option timestep="0.005"/>

<compiler angle="radian" autolimits="true"/>

<asset>
<mesh name="base" file="base.stl"/>
<mesh name="dc11_a01_spacer_dummy" file="dc11_a01_spacer_dummy.stl"/>
<mesh name="dc11_a01_dummy" file="dc11_a01_dummy.stl"/>
<mesh name="rotation_connector" file="rotation_connector.stl"/>
<mesh name="arm_connector" file="arm_connector.stl"/>
<mesh name="dc15_a01_horn_idle2_dummy" file="dc15_a01_horn_idle2_dummy.stl"/>
<mesh name="dc15_a01_case_m_dummy" file="dc15_a01_case_m_dummy.stl"/>
<mesh name="dc15_a01_case_f_dummy" file="dc15_a01_case_f_dummy.stl"/>
<mesh name="dc15_a01_horn_dummy" file="dc15_a01_horn_dummy.stl"/>
<mesh name="dc15_a01_case_b_dummy" file="dc15_a01_case_b_dummy.stl"/>
<mesh name="connector" file="connector.stl"/>
<mesh name="shoulder_rotation" file="shoulder_rotation.stl"/>
<mesh name="static_side" file="static_side.stl"/>
<mesh name="moving_side" file="moving_side.stl"/>
<texture type="skybox" builtin="gradient" rgb1="0.3 0.5 0.7" rgb2="0 0 0" width="512" height="3072" />
<texture type="2d" name="groundplane" builtin="checker" mark="edge" rgb1="0.2 0.3 0.4" rgb2="0.1 0.2 0.3" markrgb="0.8 0.8 0.8" width="300" height="300" />
<material name="groundplane" texture="groundplane" texuniform="true" texrepeat="5 5" reflectance="0.2" />
</asset>

<visual>
<headlight diffuse="0.6 0.6 0.6" ambient="0.3 0.3 0.3" specular="0 0 0" />
<rgba haze="0.15 0.25 0.35 1" />
<global azimuth="150" elevation="-20" offheight="640" />
</visual>

<worldbody>
<light pos="0 0 3" dir="0 0 -1" directional="false" />
<body name="floor">
<geom pos="0 0 0" name="floor" size="0 0 .125" type="plane" material="groundplane" conaffinity="1" contype="1" />
</body>

<body name="cube" pos="0.1 0.1 0.01">
<freejoint name="cube"/>
<inertial pos="0 0 0" mass="0.1" diaginertia="0.00001125 0.00001125 0.00001125"/>
<geom friction="0.5" condim="3" pos="0 0 0" size="0.015 0.015 0.015" type="box" name="cube" rgba="0.5 0 0 1" priority="1"/>
</body>


<camera name="camera_front" pos="0.049 0.888 0.317" xyaxes="-0.998 0.056 -0.000 -0.019 -0.335 0.942"/>
<camera name="camera_top" pos="0 0 1" euler="0 0 0" mode="fixed"/>
<camera name="camera_vizu" pos="-0.1 0.6 0.3" quat="-0.15 -0.1 0.6 1"/>
<geom pos="0.0401555 -0.0353754 -0.0242427" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="base"/>
<geom pos="0.0511555 0.0406246 0.0099573" quat="0 0 0 1" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="0.0291555 0.000624643 0.0099573" quat="0 0 0 1" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="0.0511555 0.000624643 -0.0184427" quat="0 0 1 0" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="0.0401555 0.0326246 -0.0042427" type="mesh" rgba="0.498039 0.498039 0.498039 1" mesh="dc11_a01_dummy"/>
<geom pos="0.0291555 0.000624643 -0.0184427" quat="0 0.707107 0.707107 0" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="0.0291555 0.0406246 0.0099573" quat="0.707107 0 0 -0.707107" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="0.0511555 0.0406246 -0.0184427" quat="0 0.707107 -0.707107 0" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="0.0291555 0.0406246 -0.0184427" quat="0 -1 0 0" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="0.0511555 0.000624643 0.0099573" quat="0.707107 0 0 0.707107" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>

<body name="pitch1_assembly" pos="0.0401555 0.0326246 0.0166573">

<inertial pos="-0.000767103 -0.0121505 0.0134241" quat="0.498429 0.53272 -0.473938 0.493113" mass="0.0606831" diaginertia="1.86261e-05 1.72746e-05 1.11693e-05"/>
<joint name="shoulder_pan_joint" pos="0 0 0" axis="0 0 1" range="-3.14159 3.14159" damping="0.1" actuatorfrcrange="-0.5 0.5" actuatorfrclimited="true"/>
<geom pos="0 0 -0.0209" type="mesh" rgba="0.231373 0.380392 0.705882 1" mesh="rotation_connector"/>
<geom pos="-0.014 0.008 0.0264" quat="0 -0.707107 0 0.707107" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="-0.014 -0.032 0.0044" quat="0 -0.707107 0 0.707107" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="0.0144 -0.032 0.0264" quat="0.707107 0 0.707107 0" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="0.0002 0 0.0154" quat="0.707107 0 -0.707107 0" type="mesh" rgba="0.498039 0.498039 0.498039 1" mesh="dc11_a01_dummy"/>
<geom pos="0.0144 -0.032 0.0044" quat="0.5 0.5 0.5 0.5" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="-0.014 0.008 0.0044" quat="0.5 0.5 -0.5 -0.5" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="0.0144 0.008 0.0264" quat="0.5 -0.5 0.5 -0.5" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="0.0144 0.008 0.0044" quat="0 0.707107 0 0.707107" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="-0.014 -0.032 0.0264" quat="0.5 -0.5 -0.5 0.5" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>

<body name="pitch2_assembly" pos="-0.0188 0 0.0154" quat="0 0.707107 0 0.707107">

<inertial pos="0.0766242 -0.00031229 0.0187402" quat="0.52596 0.513053 0.489778 0.469319" mass="0.0432446" diaginertia="7.21796e-05 7.03107e-05 1.07533e-05"/>
<joint name="shoulder_lift_joint" pos="0 0 0" axis="0 0 1" range="-1.5708 1.22173" damping="0.1" actuatorfrcrange="-0.5 0.5" actuatorfrclimited="true"/>
<geom pos="0 0 0.019" quat="0.5 -0.5 -0.5 -0.5" type="mesh" rgba="0.980392 0.713726 0.00392157 1" mesh="arm_connector"/>
<geom pos="0.1083 -0.0148 0.03035" quat="1 0 0 0" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc15_a01_horn_idle2_dummy"/>
<geom pos="0.1083 -0.0148 0.01075" quat="0 -1 0 0" type="mesh" rgba="0.615686 0.811765 0.929412 1" mesh="dc15_a01_case_m_dummy"/>
<geom pos="0.1083 -0.0148 0.01075" quat="0 -1 0 0" type="mesh" rgba="0.980392 0.713726 0.00392157 1" mesh="dc15_a01_case_f_dummy"/>
<geom pos="0.1083 -0.0148 0.00715" quat="0 0 0.707107 -0.707107" type="mesh" rgba="0.972549 0.529412 0.00392157 1" mesh="dc15_a01_horn_dummy"/>
<geom pos="0.1083 -0.0148 0.03025" quat="0 -1 0 0" type="mesh" rgba="0.498039 0.498039 0.498039 1" mesh="dc15_a01_case_b_dummy"/>

<body name="pitch3_assembly" pos="0.1083 -0.0148 0.00425" quat="0.707107 0 0 0.707107">
<inertial pos="-0.0551014 -0.00287792 0.0144813" quat="0.500323 0.499209 0.499868 0.5006" mass="0.0788335" diaginertia="6.80912e-05 6.45748e-05 9.84479e-06"/>
<joint name="elbow_flex_joint" pos="0 0 0" axis="0 0 1" range="-1.48353 1.74533" damping="0.1" actuatorfrcrange="-0.5 0.5" actuatorfrclimited="true"/>
<geom pos="-0.00863031 0.00847376 0.0145" quat="0.5 0.5 0.5 -0.5" type="mesh" rgba="0.615686 0.811765 0.929412 1" mesh="connector"/>
<geom pos="-0.100476 -0.00269986 0.02635" quat="0.707107 0 0 -0.707107" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc15_a01_horn_idle2_dummy"/>
<geom pos="-0.100476 -0.00269986 0.00675" quat="0 -0.707107 0.707107 0" type="mesh" rgba="0.615686 0.811765 0.929412 1" mesh="dc15_a01_case_m_dummy"/>
<geom pos="-0.100476 -0.00269986 0.00675" quat="0 -0.707107 0.707107 0" type="mesh" rgba="0.980392 0.713726 0.00392157 1" mesh="dc15_a01_case_f_dummy"/>
<geom pos="-0.100476 -0.00269986 0.00315" quat="0.5 -0.5 -0.5 0.5" type="mesh" rgba="0.972549 0.529412 0.00392157 1" mesh="dc15_a01_horn_dummy"/>
<geom pos="-0.100476 -0.00269986 0.02625" quat="0 -0.707107 0.707107 0" type="mesh" rgba="0.498039 0.498039 0.498039 1" mesh="dc15_a01_case_b_dummy"/>

<body name="effector_roll_assembly" pos="-0.100476 -0.00269986 0.02925" quat="0 -0.707107 -0.707107 0">
<inertial pos="-1.65017e-05 -0.02659 0.0195388" quat="0.936813 0.349829 -0.00055331 -0.000300569" mass="0.0240506" diaginertia="6.03208e-06 4.12894e-06 3.3522e-06"/>
<joint name="wrist_flex_joint" pos="0 0 0" axis="0 0 1" range="-1.91986 1.91986" damping="0.1" actuatorfrcrange="-0.5 0.5" actuatorfrclimited="true"/>
<geom pos="-0.0109998 -0.0190002 0.039" quat="0.707107 -0.707107 0 0" type="mesh" rgba="0.615686 0.811765 0.929412 1" mesh="shoulder_rotation"/>
<geom pos="-7.44154e-06 -0.0385002 0.0133967" quat="0 0 0.707107 -0.707107" type="mesh" rgba="0.615686 0.811765 0.929412 1" mesh="dc15_a01_case_m_dummy"/>
<geom pos="-7.44154e-06 -0.0385002 0.0133967" quat="0 0 0.707107 -0.707107" type="mesh" rgba="0.980392 0.713726 0.00392157 1" mesh="dc15_a01_case_f_dummy"/>
<geom pos="-7.44154e-06 -0.0421002 0.0133967" quat="0 1 0 0" type="mesh" rgba="0.972549 0.529412 0.00392157 1" mesh="dc15_a01_horn_dummy"/>
<geom pos="-7.44154e-06 -0.0190002 0.0133967" quat="0 0 0.707107 -0.707107" type="mesh" rgba="0.498039 0.498039 0.498039 1" mesh="dc15_a01_case_b_dummy"/>

<body name="gripper_assembly" pos="-7.44154e-06 -0.0450002 0.0133967" quat="0.5 -0.5 -0.5 -0.5">
<inertial pos="-0.00548595 -0.000433143 -0.0190793" quat="0.700194 0.164851 0.167361 0.674197" mass="0.0360627" diaginertia="1.3261e-05 1.231e-05 5.3532e-06"/>
<joint name="wrist_roll_joint" pos="0 0 0" axis="0 0 1" range="-2.96706 2.96706" damping="0.1" actuatorfrcrange="-0.5 0.5" actuatorfrclimited="true"/>
<geom pos="-0.00075 -0.01475 -0.02" quat="0.707107 -0.707107 0 0" type="mesh" rgba="0.917647 0.917647 0.917647 1" mesh="static_side"/>
<geom pos="0.00755 0.01135 -0.013" quat="0.5 -0.5 0.5 0.5" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc15_a01_horn_idle2_dummy"/>
<geom pos="0.00755 -0.00825 -0.013" quat="0.5 0.5 0.5 -0.5" type="mesh" rgba="0.615686 0.811765 0.929412 1" mesh="dc15_a01_case_m_dummy"/>
<geom pos="0.00755 -0.00825 -0.013" quat="0.5 0.5 0.5 -0.5" type="mesh" rgba="0.980392 0.713726 0.00392157 1" mesh="dc15_a01_case_f_dummy"/>
<geom pos="0.00755 -0.01185 -0.013" quat="0 -0.707107 0 -0.707107" type="mesh" rgba="0.972549 0.529412 0.00392157 1" mesh="dc15_a01_horn_dummy"/>
<geom pos="0.00755 0.01125 -0.013" quat="0.5 0.5 0.5 -0.5" type="mesh" rgba="0.498039 0.498039 0.498039 1" mesh="dc15_a01_case_b_dummy"/>

<body name="moving_side" pos="0.00755 -0.01475 -0.013" quat="0.707107 -0.707107 0 0">
<inertial pos="-0.000395599 0.022415 0.0145636" quat="0.722353 0.689129 0.0389102 0.0423547" mass="0.0089856" diaginertia="3.28451e-06 2.24898e-06 1.41539e-06"/>
<joint name="gripper_joint" pos="0 0 0" axis="0 0 1" range="-1.74533 0.0523599" damping="0.1" actuatorfrcrange="-0.5 0.5" actuatorfrclimited="true"/>
<geom pos="-0.00838199 -0.000256591 -0.003" type="mesh" rgba="0.768627 0.886275 0.952941 1" mesh="moving_side"/>
</body>
</body>
</body>
</body>
</body>
</body>
</worldbody>

<actuator>
<position joint="shoulder_pan_joint" ctrlrange="-3.14159 3.14159" ctrllimited="true" kp="20" />
<position joint="shoulder_lift_joint" ctrlrange="-1.5708 1.22173" ctrllimited="true" kp="20" />
<position joint="elbow_flex_joint" ctrlrange="-1.48353 1.74533" ctrllimited="true" kp="20" />
<position joint="wrist_flex_joint" ctrlrange="-1.91986 1.91986" ctrllimited="true" kp="20" />
<position joint="wrist_roll_joint" ctrlrange="-2.96706 2.96706" ctrllimited="true" kp="20" />
<position joint="gripper_joint" ctrlrange="-1.74533 0.0523599" ctrllimited="true" kp="20" />
</actuator>
</mujoco>

+ 137
- 0
examples/alexk-lcr/assets/simulation/push_cube.xml View File

@@ -0,0 +1,137 @@
<mujoco model="bd1 scene">
<option timestep="0.005"/>

<compiler angle="radian" autolimits="true"/>

<asset>
<mesh name="base" file="base.stl"/>
<mesh name="dc11_a01_spacer_dummy" file="dc11_a01_spacer_dummy.stl"/>
<mesh name="dc11_a01_dummy" file="dc11_a01_dummy.stl"/>
<mesh name="rotation_connector" file="rotation_connector.stl"/>
<mesh name="arm_connector" file="arm_connector.stl"/>
<mesh name="dc15_a01_horn_idle2_dummy" file="dc15_a01_horn_idle2_dummy.stl"/>
<mesh name="dc15_a01_case_m_dummy" file="dc15_a01_case_m_dummy.stl"/>
<mesh name="dc15_a01_case_f_dummy" file="dc15_a01_case_f_dummy.stl"/>
<mesh name="dc15_a01_horn_dummy" file="dc15_a01_horn_dummy.stl"/>
<mesh name="dc15_a01_case_b_dummy" file="dc15_a01_case_b_dummy.stl"/>
<mesh name="connector" file="connector.stl"/>
<mesh name="shoulder_rotation" file="shoulder_rotation.stl"/>
<mesh name="static_side" file="static_side.stl"/>
<mesh name="moving_side" file="moving_side.stl"/>
<texture type="skybox" builtin="gradient" rgb1="0.3 0.5 0.7" rgb2="0 0 0" width="512" height="3072" />
<texture type="2d" name="groundplane" builtin="checker" mark="edge" rgb1="0.2 0.3 0.4" rgb2="0.1 0.2 0.3" markrgb="0.8 0.8 0.8" width="300" height="300" />
<material name="groundplane" texture="groundplane" texuniform="true" texrepeat="5 5" reflectance="0.2" />
</asset>

<visual>
<headlight diffuse="0.6 0.6 0.6" ambient="0.3 0.3 0.3" specular="0 0 0" />
<rgba haze="0.15 0.25 0.35 1" />
<global azimuth="150" elevation="-20" offheight="640" />
</visual>

<worldbody>
<light pos="0 0 3" dir="0 0 -1" directional="false" />
<body name="floor">
<geom pos="0 0 0" name="floor" size="0 0 .125" type="plane" material="groundplane" conaffinity="1" contype="1" />
</body>

<body name="cube" pos="0.1 0.1 0.01">
<freejoint name="cube"/>
<inertial pos="0 0 0" mass="0.1" diaginertia="0.00001125 0.00001125 0.00001125"/>
<geom friction="0.5" condim="3" pos="0 0 0" size="0.015 0.015 0.015" type="box" name="cube" rgba="0.5 0 0 1" priority="1"/>
</body>

<camera name="camera_front" pos="0.049 0.888 0.317" xyaxes="-0.998 0.056 -0.000 -0.019 -0.335 0.942"/>
<camera name="camera_top" pos="0 0 1" euler="0 0 0" mode="fixed"/>
<camera name="camera_vizu" pos="-0.1 0.6 0.3" quat="-0.15 -0.1 0.6 1"/>
<geom pos="0.0401555 -0.0353754 -0.0242427" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="base"/>
<geom pos="0.0511555 0.0406246 0.0099573" quat="0 0 0 1" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="0.0291555 0.000624643 0.0099573" quat="0 0 0 1" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="0.0511555 0.000624643 -0.0184427" quat="0 0 1 0" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="0.0401555 0.0326246 -0.0042427" type="mesh" rgba="0.498039 0.498039 0.498039 1" mesh="dc11_a01_dummy"/>
<geom pos="0.0291555 0.000624643 -0.0184427" quat="0 0.707107 0.707107 0" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="0.0291555 0.0406246 0.0099573" quat="0.707107 0 0 -0.707107" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="0.0511555 0.0406246 -0.0184427" quat="0 0.707107 -0.707107 0" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="0.0291555 0.0406246 -0.0184427" quat="0 -1 0 0" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="0.0511555 0.000624643 0.0099573" quat="0.707107 0 0 0.707107" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>

<body name="pitch1_assembly" pos="0.0401555 0.0326246 0.0166573">

<inertial pos="-0.000767103 -0.0121505 0.0134241" quat="0.498429 0.53272 -0.473938 0.493113" mass="0.0606831" diaginertia="1.86261e-05 1.72746e-05 1.11693e-05"/>
<joint name="shoulder_pan_joint" pos="0 0 0" axis="0 0 1" range="-3.14159 3.14159" damping="0.1" actuatorfrcrange="-0.5 0.5" actuatorfrclimited="true"/>
<geom pos="0 0 -0.0209" type="mesh" rgba="0.231373 0.380392 0.705882 1" mesh="rotation_connector"/>
<geom pos="-0.014 0.008 0.0264" quat="0 -0.707107 0 0.707107" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="-0.014 -0.032 0.0044" quat="0 -0.707107 0 0.707107" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="0.0144 -0.032 0.0264" quat="0.707107 0 0.707107 0" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="0.0002 0 0.0154" quat="0.707107 0 -0.707107 0" type="mesh" rgba="0.498039 0.498039 0.498039 1" mesh="dc11_a01_dummy"/>
<geom pos="0.0144 -0.032 0.0044" quat="0.5 0.5 0.5 0.5" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="-0.014 0.008 0.0044" quat="0.5 0.5 -0.5 -0.5" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="0.0144 0.008 0.0264" quat="0.5 -0.5 0.5 -0.5" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="0.0144 0.008 0.0044" quat="0 0.707107 0 0.707107" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="-0.014 -0.032 0.0264" quat="0.5 -0.5 -0.5 0.5" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>

<body name="pitch2_assembly" pos="-0.0188 0 0.0154" quat="0 0.707107 0 0.707107">

<inertial pos="0.0766242 -0.00031229 0.0187402" quat="0.52596 0.513053 0.489778 0.469319" mass="0.0432446" diaginertia="7.21796e-05 7.03107e-05 1.07533e-05"/>
<joint name="shoulder_lift_joint" pos="0 0 0" axis="0 0 1" range="-1.5708 1.22173" damping="0.1" actuatorfrcrange="-0.5 0.5" actuatorfrclimited="true"/>
<geom pos="0 0 0.019" quat="0.5 -0.5 -0.5 -0.5" type="mesh" rgba="0.980392 0.713726 0.00392157 1" mesh="arm_connector"/>
<geom pos="0.1083 -0.0148 0.03035" quat="1 0 0 0" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc15_a01_horn_idle2_dummy"/>
<geom pos="0.1083 -0.0148 0.01075" quat="0 -1 0 0" type="mesh" rgba="0.615686 0.811765 0.929412 1" mesh="dc15_a01_case_m_dummy"/>
<geom pos="0.1083 -0.0148 0.01075" quat="0 -1 0 0" type="mesh" rgba="0.980392 0.713726 0.00392157 1" mesh="dc15_a01_case_f_dummy"/>
<geom pos="0.1083 -0.0148 0.00715" quat="0 0 0.707107 -0.707107" type="mesh" rgba="0.972549 0.529412 0.00392157 1" mesh="dc15_a01_horn_dummy"/>
<geom pos="0.1083 -0.0148 0.03025" quat="0 -1 0 0" type="mesh" rgba="0.498039 0.498039 0.498039 1" mesh="dc15_a01_case_b_dummy"/>

<body name="pitch3_assembly" pos="0.1083 -0.0148 0.00425" quat="0.707107 0 0 0.707107">
<inertial pos="-0.0551014 -0.00287792 0.0144813" quat="0.500323 0.499209 0.499868 0.5006" mass="0.0788335" diaginertia="6.80912e-05 6.45748e-05 9.84479e-06"/>
<joint name="elbow_flex_joint" pos="0 0 0" axis="0 0 1" range="-1.48353 1.74533" damping="0.1" actuatorfrcrange="-0.5 0.5" actuatorfrclimited="true"/>
<geom pos="-0.00863031 0.00847376 0.0145" quat="0.5 0.5 0.5 -0.5" type="mesh" rgba="0.615686 0.811765 0.929412 1" mesh="connector"/>
<geom pos="-0.100476 -0.00269986 0.02635" quat="0.707107 0 0 -0.707107" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc15_a01_horn_idle2_dummy"/>
<geom pos="-0.100476 -0.00269986 0.00675" quat="0 -0.707107 0.707107 0" type="mesh" rgba="0.615686 0.811765 0.929412 1" mesh="dc15_a01_case_m_dummy"/>
<geom pos="-0.100476 -0.00269986 0.00675" quat="0 -0.707107 0.707107 0" type="mesh" rgba="0.980392 0.713726 0.00392157 1" mesh="dc15_a01_case_f_dummy"/>
<geom pos="-0.100476 -0.00269986 0.00315" quat="0.5 -0.5 -0.5 0.5" type="mesh" rgba="0.972549 0.529412 0.00392157 1" mesh="dc15_a01_horn_dummy"/>
<geom pos="-0.100476 -0.00269986 0.02625" quat="0 -0.707107 0.707107 0" type="mesh" rgba="0.498039 0.498039 0.498039 1" mesh="dc15_a01_case_b_dummy"/>

<body name="effector_roll_assembly" pos="-0.100476 -0.00269986 0.02925" quat="0 -0.707107 -0.707107 0">
<inertial pos="-1.65017e-05 -0.02659 0.0195388" quat="0.936813 0.349829 -0.00055331 -0.000300569" mass="0.0240506" diaginertia="6.03208e-06 4.12894e-06 3.3522e-06"/>
<joint name="wrist_flex_joint" pos="0 0 0" axis="0 0 1" range="-1.91986 1.91986" damping="0.1" actuatorfrcrange="-0.5 0.5" actuatorfrclimited="true"/>
<geom pos="-0.0109998 -0.0190002 0.039" quat="0.707107 -0.707107 0 0" type="mesh" rgba="0.615686 0.811765 0.929412 1" mesh="shoulder_rotation"/>
<geom pos="-7.44154e-06 -0.0385002 0.0133967" quat="0 0 0.707107 -0.707107" type="mesh" rgba="0.615686 0.811765 0.929412 1" mesh="dc15_a01_case_m_dummy"/>
<geom pos="-7.44154e-06 -0.0385002 0.0133967" quat="0 0 0.707107 -0.707107" type="mesh" rgba="0.980392 0.713726 0.00392157 1" mesh="dc15_a01_case_f_dummy"/>
<geom pos="-7.44154e-06 -0.0421002 0.0133967" quat="0 1 0 0" type="mesh" rgba="0.972549 0.529412 0.00392157 1" mesh="dc15_a01_horn_dummy"/>
<geom pos="-7.44154e-06 -0.0190002 0.0133967" quat="0 0 0.707107 -0.707107" type="mesh" rgba="0.498039 0.498039 0.498039 1" mesh="dc15_a01_case_b_dummy"/>

<body name="gripper_assembly" pos="-7.44154e-06 -0.0450002 0.0133967" quat="0.5 -0.5 -0.5 -0.5">
<inertial pos="-0.00548595 -0.000433143 -0.0190793" quat="0.700194 0.164851 0.167361 0.674197" mass="0.0360627" diaginertia="1.3261e-05 1.231e-05 5.3532e-06"/>
<joint name="wrist_roll_joint" pos="0 0 0" axis="0 0 1" range="-2.96706 2.96706" damping="0.1" actuatorfrcrange="-0.5 0.5" actuatorfrclimited="true"/>
<geom pos="-0.00075 -0.01475 -0.02" quat="0.707107 -0.707107 0 0" type="mesh" rgba="0.917647 0.917647 0.917647 1" mesh="static_side"/>
<geom pos="0.00755 0.01135 -0.013" quat="0.5 -0.5 0.5 0.5" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc15_a01_horn_idle2_dummy"/>
<geom pos="0.00755 -0.00825 -0.013" quat="0.5 0.5 0.5 -0.5" type="mesh" rgba="0.615686 0.811765 0.929412 1" mesh="dc15_a01_case_m_dummy"/>
<geom pos="0.00755 -0.00825 -0.013" quat="0.5 0.5 0.5 -0.5" type="mesh" rgba="0.980392 0.713726 0.00392157 1" mesh="dc15_a01_case_f_dummy"/>
<geom pos="0.00755 -0.01185 -0.013" quat="0 -0.707107 0 -0.707107" type="mesh" rgba="0.972549 0.529412 0.00392157 1" mesh="dc15_a01_horn_dummy"/>
<geom pos="0.00755 0.01125 -0.013" quat="0.5 0.5 0.5 -0.5" type="mesh" rgba="0.498039 0.498039 0.498039 1" mesh="dc15_a01_case_b_dummy"/>

<body name="moving_side" pos="0.00755 -0.01475 -0.013" quat="0.707107 -0.707107 0 0">
<inertial pos="-0.000395599 0.022415 0.0145636" quat="0.722353 0.689129 0.0389102 0.0423547" mass="0.0089856" diaginertia="3.28451e-06 2.24898e-06 1.41539e-06"/>
<joint name="gripper_joint" pos="0 0 0" axis="0 0 1" range="-1.74533 0.0523599" damping="0.1" actuatorfrcrange="-0.5 0.5" actuatorfrclimited="true"/>
<geom pos="-0.00838199 -0.000256591 -0.003" type="mesh" rgba="0.768627 0.886275 0.952941 1" mesh="moving_side"/>
</body>
</body>
</body>
</body>
</body>
</body>

<site name="cube_target" pos="1 0 0" size="0.01" rgba="1 0 0 1" />

</worldbody>

<actuator>
<position joint="shoulder_pan_joint" ctrlrange="-3.14159 3.14159" ctrllimited="true" kp="20" />
<position joint="shoulder_lift_joint" ctrlrange="-1.5708 1.22173" ctrllimited="true" kp="20" />
<position joint="elbow_flex_joint" ctrlrange="-1.48353 1.74533" ctrllimited="true" kp="20" />
<position joint="wrist_flex_joint" ctrlrange="-1.91986 1.91986" ctrllimited="true" kp="20" />
<position joint="wrist_roll_joint" ctrlrange="-2.96706 2.96706" ctrllimited="true" kp="20" />
<position joint="gripper_joint" ctrlrange="-1.74533 0.0523599" ctrllimited="true" kp="20" />
</actuator>
</mujoco>

+ 135
- 0
examples/alexk-lcr/assets/simulation/reach_cube.xml View File

@@ -0,0 +1,135 @@
<mujoco model="bd1 scene">
<option timestep="0.005"/>

<compiler angle="radian" autolimits="true"/>

<asset>
<mesh name="base" file="base.stl"/>
<mesh name="dc11_a01_spacer_dummy" file="dc11_a01_spacer_dummy.stl"/>
<mesh name="dc11_a01_dummy" file="dc11_a01_dummy.stl"/>
<mesh name="rotation_connector" file="rotation_connector.stl"/>
<mesh name="arm_connector" file="arm_connector.stl"/>
<mesh name="dc15_a01_horn_idle2_dummy" file="dc15_a01_horn_idle2_dummy.stl"/>
<mesh name="dc15_a01_case_m_dummy" file="dc15_a01_case_m_dummy.stl"/>
<mesh name="dc15_a01_case_f_dummy" file="dc15_a01_case_f_dummy.stl"/>
<mesh name="dc15_a01_horn_dummy" file="dc15_a01_horn_dummy.stl"/>
<mesh name="dc15_a01_case_b_dummy" file="dc15_a01_case_b_dummy.stl"/>
<mesh name="connector" file="connector.stl"/>
<mesh name="shoulder_rotation" file="shoulder_rotation.stl"/>
<mesh name="static_side" file="static_side.stl"/>
<mesh name="moving_side" file="moving_side.stl"/>
<texture type="skybox" builtin="gradient" rgb1="0.3 0.5 0.7" rgb2="0 0 0" width="512" height="3072" />
<texture type="2d" name="groundplane" builtin="checker" mark="edge" rgb1="0.2 0.3 0.4" rgb2="0.1 0.2 0.3" markrgb="0.8 0.8 0.8" width="300" height="300" />
<material name="groundplane" texture="groundplane" texuniform="true" texrepeat="5 5" reflectance="0.2" />
</asset>

<visual>
<headlight diffuse="0.6 0.6 0.6" ambient="0.3 0.3 0.3" specular="0 0 0" />
<rgba haze="0.15 0.25 0.35 1" />
<global azimuth="150" elevation="-20" offheight="640" />
</visual>

<worldbody>
<light pos="0 0 3" dir="0 0 -1" directional="false" />
<body name="floor">
<geom pos="0 0 0" name="floor" size="0 0 .125" type="plane" material="groundplane" conaffinity="1" contype="1" />
</body>

<body name="cube" pos="0.1 0.1 0.01">
<freejoint name="cube"/>
<inertial pos="0 0 0" mass="0.1" diaginertia="0.00001125 0.00001125 0.00001125"/>
<geom friction="0.5" condim="3" pos="0 0 0" size="0.015 0.015 0.015" type="box" name="cube" rgba="0.5 0 0 1" priority="1"/>
</body>


<camera name="camera_front" pos="0.049 0.888 0.317" xyaxes="-0.998 0.056 -0.000 -0.019 -0.335 0.942"/>
<camera name="camera_top" pos="0 0 1" euler="0 0 0" mode="fixed"/>
<camera name="camera_vizu" pos="-0.1 0.6 0.3" quat="-0.15 -0.1 0.6 1"/>
<geom pos="0.0401555 -0.0353754 -0.0242427" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="base"/>
<geom pos="0.0511555 0.0406246 0.0099573" quat="0 0 0 1" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="0.0291555 0.000624643 0.0099573" quat="0 0 0 1" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="0.0511555 0.000624643 -0.0184427" quat="0 0 1 0" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="0.0401555 0.0326246 -0.0042427" type="mesh" rgba="0.498039 0.498039 0.498039 1" mesh="dc11_a01_dummy"/>
<geom pos="0.0291555 0.000624643 -0.0184427" quat="0 0.707107 0.707107 0" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="0.0291555 0.0406246 0.0099573" quat="0.707107 0 0 -0.707107" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="0.0511555 0.0406246 -0.0184427" quat="0 0.707107 -0.707107 0" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="0.0291555 0.0406246 -0.0184427" quat="0 -1 0 0" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="0.0511555 0.000624643 0.0099573" quat="0.707107 0 0 0.707107" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>

<body name="pitch1_assembly" pos="0.0401555 0.0326246 0.0166573">

<inertial pos="-0.000767103 -0.0121505 0.0134241" quat="0.498429 0.53272 -0.473938 0.493113" mass="0.0606831" diaginertia="1.86261e-05 1.72746e-05 1.11693e-05"/>
<joint name="shoulder_pan_joint" pos="0 0 0" axis="0 0 1" range="-3.14159 3.14159" damping="0.1" actuatorfrcrange="-0.5 0.5" actuatorfrclimited="true"/>
<geom pos="0 0 -0.0209" type="mesh" rgba="0.231373 0.380392 0.705882 1" mesh="rotation_connector"/>
<geom pos="-0.014 0.008 0.0264" quat="0 -0.707107 0 0.707107" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="-0.014 -0.032 0.0044" quat="0 -0.707107 0 0.707107" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="0.0144 -0.032 0.0264" quat="0.707107 0 0.707107 0" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="0.0002 0 0.0154" quat="0.707107 0 -0.707107 0" type="mesh" rgba="0.498039 0.498039 0.498039 1" mesh="dc11_a01_dummy"/>
<geom pos="0.0144 -0.032 0.0044" quat="0.5 0.5 0.5 0.5" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="-0.014 0.008 0.0044" quat="0.5 0.5 -0.5 -0.5" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="0.0144 0.008 0.0264" quat="0.5 -0.5 0.5 -0.5" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="0.0144 0.008 0.0044" quat="0 0.707107 0 0.707107" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="-0.014 -0.032 0.0264" quat="0.5 -0.5 -0.5 0.5" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>

<body name="pitch2_assembly" pos="-0.0188 0 0.0154" quat="0 0.707107 0 0.707107">

<inertial pos="0.0766242 -0.00031229 0.0187402" quat="0.52596 0.513053 0.489778 0.469319" mass="0.0432446" diaginertia="7.21796e-05 7.03107e-05 1.07533e-05"/>
<joint name="shoulder_lift_joint" pos="0 0 0" axis="0 0 1" range="-1.5708 1.22173" damping="0.1" actuatorfrcrange="-0.5 0.5" actuatorfrclimited="true"/>
<geom pos="0 0 0.019" quat="0.5 -0.5 -0.5 -0.5" type="mesh" rgba="0.980392 0.713726 0.00392157 1" mesh="arm_connector"/>
<geom pos="0.1083 -0.0148 0.03035" quat="1 0 0 0" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc15_a01_horn_idle2_dummy"/>
<geom pos="0.1083 -0.0148 0.01075" quat="0 -1 0 0" type="mesh" rgba="0.615686 0.811765 0.929412 1" mesh="dc15_a01_case_m_dummy"/>
<geom pos="0.1083 -0.0148 0.01075" quat="0 -1 0 0" type="mesh" rgba="0.980392 0.713726 0.00392157 1" mesh="dc15_a01_case_f_dummy"/>
<geom pos="0.1083 -0.0148 0.00715" quat="0 0 0.707107 -0.707107" type="mesh" rgba="0.972549 0.529412 0.00392157 1" mesh="dc15_a01_horn_dummy"/>
<geom pos="0.1083 -0.0148 0.03025" quat="0 -1 0 0" type="mesh" rgba="0.498039 0.498039 0.498039 1" mesh="dc15_a01_case_b_dummy"/>

<body name="pitch3_assembly" pos="0.1083 -0.0148 0.00425" quat="0.707107 0 0 0.707107">
<inertial pos="-0.0551014 -0.00287792 0.0144813" quat="0.500323 0.499209 0.499868 0.5006" mass="0.0788335" diaginertia="6.80912e-05 6.45748e-05 9.84479e-06"/>
<joint name="elbow_flex_joint" pos="0 0 0" axis="0 0 1" range="-1.48353 1.74533" damping="0.1" actuatorfrcrange="-0.5 0.5" actuatorfrclimited="true"/>
<geom pos="-0.00863031 0.00847376 0.0145" quat="0.5 0.5 0.5 -0.5" type="mesh" rgba="0.615686 0.811765 0.929412 1" mesh="connector"/>
<geom pos="-0.100476 -0.00269986 0.02635" quat="0.707107 0 0 -0.707107" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc15_a01_horn_idle2_dummy"/>
<geom pos="-0.100476 -0.00269986 0.00675" quat="0 -0.707107 0.707107 0" type="mesh" rgba="0.615686 0.811765 0.929412 1" mesh="dc15_a01_case_m_dummy"/>
<geom pos="-0.100476 -0.00269986 0.00675" quat="0 -0.707107 0.707107 0" type="mesh" rgba="0.980392 0.713726 0.00392157 1" mesh="dc15_a01_case_f_dummy"/>
<geom pos="-0.100476 -0.00269986 0.00315" quat="0.5 -0.5 -0.5 0.5" type="mesh" rgba="0.972549 0.529412 0.00392157 1" mesh="dc15_a01_horn_dummy"/>
<geom pos="-0.100476 -0.00269986 0.02625" quat="0 -0.707107 0.707107 0" type="mesh" rgba="0.498039 0.498039 0.498039 1" mesh="dc15_a01_case_b_dummy"/>

<body name="effector_roll_assembly" pos="-0.100476 -0.00269986 0.02925" quat="0 -0.707107 -0.707107 0">
<inertial pos="-1.65017e-05 -0.02659 0.0195388" quat="0.936813 0.349829 -0.00055331 -0.000300569" mass="0.0240506" diaginertia="6.03208e-06 4.12894e-06 3.3522e-06"/>
<joint name="wrist_flex_joint" pos="0 0 0" axis="0 0 1" range="-1.91986 1.91986" damping="0.1" actuatorfrcrange="-0.5 0.5" actuatorfrclimited="true"/>
<geom pos="-0.0109998 -0.0190002 0.039" quat="0.707107 -0.707107 0 0" type="mesh" rgba="0.615686 0.811765 0.929412 1" mesh="shoulder_rotation"/>
<geom pos="-7.44154e-06 -0.0385002 0.0133967" quat="0 0 0.707107 -0.707107" type="mesh" rgba="0.615686 0.811765 0.929412 1" mesh="dc15_a01_case_m_dummy"/>
<geom pos="-7.44154e-06 -0.0385002 0.0133967" quat="0 0 0.707107 -0.707107" type="mesh" rgba="0.980392 0.713726 0.00392157 1" mesh="dc15_a01_case_f_dummy"/>
<geom pos="-7.44154e-06 -0.0421002 0.0133967" quat="0 1 0 0" type="mesh" rgba="0.972549 0.529412 0.00392157 1" mesh="dc15_a01_horn_dummy"/>
<geom pos="-7.44154e-06 -0.0190002 0.0133967" quat="0 0 0.707107 -0.707107" type="mesh" rgba="0.498039 0.498039 0.498039 1" mesh="dc15_a01_case_b_dummy"/>

<body name="gripper_assembly" pos="-7.44154e-06 -0.0450002 0.0133967" quat="0.5 -0.5 -0.5 -0.5">
<inertial pos="-0.00548595 -0.000433143 -0.0190793" quat="0.700194 0.164851 0.167361 0.674197" mass="0.0360627" diaginertia="1.3261e-05 1.231e-05 5.3532e-06"/>
<joint name="wrist_roll_joint" pos="0 0 0" axis="0 0 1" range="-2.96706 2.96706" damping="0.1" actuatorfrcrange="-0.5 0.5" actuatorfrclimited="true"/>
<geom pos="-0.00075 -0.01475 -0.02" quat="0.707107 -0.707107 0 0" type="mesh" rgba="0.917647 0.917647 0.917647 1" mesh="static_side"/>
<geom pos="0.00755 0.01135 -0.013" quat="0.5 -0.5 0.5 0.5" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc15_a01_horn_idle2_dummy"/>
<geom pos="0.00755 -0.00825 -0.013" quat="0.5 0.5 0.5 -0.5" type="mesh" rgba="0.615686 0.811765 0.929412 1" mesh="dc15_a01_case_m_dummy"/>
<geom pos="0.00755 -0.00825 -0.013" quat="0.5 0.5 0.5 -0.5" type="mesh" rgba="0.980392 0.713726 0.00392157 1" mesh="dc15_a01_case_f_dummy"/>
<geom pos="0.00755 -0.01185 -0.013" quat="0 -0.707107 0 -0.707107" type="mesh" rgba="0.972549 0.529412 0.00392157 1" mesh="dc15_a01_horn_dummy"/>
<geom pos="0.00755 0.01125 -0.013" quat="0.5 0.5 0.5 -0.5" type="mesh" rgba="0.498039 0.498039 0.498039 1" mesh="dc15_a01_case_b_dummy"/>

<body name="moving_side" pos="0.00755 -0.01475 -0.013" quat="0.707107 -0.707107 0 0">
<inertial pos="-0.000395599 0.022415 0.0145636" quat="0.722353 0.689129 0.0389102 0.0423547" mass="0.0089856" diaginertia="3.28451e-06 2.24898e-06 1.41539e-06"/>
<joint name="gripper_joint" pos="0 0 0" axis="0 0 1" range="-1.74533 0.0523599" damping="0.1" actuatorfrcrange="-0.5 0.5" actuatorfrclimited="true"/>
<geom pos="-0.00838199 -0.000256591 -0.003" type="mesh" rgba="0.768627 0.886275 0.952941 1" mesh="moving_side"/>
</body>
</body>
</body>
</body>
</body>
</body>
</worldbody>

<actuator>
<position joint="shoulder_pan_joint" ctrlrange="-3.14159 3.14159" ctrllimited="true" kp="20" />
<position joint="shoulder_lift_joint" ctrlrange="-1.5708 1.22173" ctrllimited="true" kp="20" />
<position joint="elbow_flex_joint" ctrlrange="-1.48353 1.74533" ctrllimited="true" kp="20" />
<position joint="wrist_flex_joint" ctrlrange="-1.91986 1.91986" ctrllimited="true" kp="20" />
<position joint="wrist_roll_joint" ctrlrange="-2.96706 2.96706" ctrllimited="true" kp="20" />
<position joint="gripper_joint" ctrlrange="-1.74533 0.0523599" ctrllimited="true" kp="20" />
</actuator>
</mujoco>

+ 141
- 0
examples/alexk-lcr/assets/simulation/stack_two_cubes.xml View File

@@ -0,0 +1,141 @@
<mujoco model="bd1 scene">
<option timestep="0.005"/>

<compiler angle="radian" autolimits="true"/>

<asset>
<mesh name="base" file="base.stl"/>
<mesh name="dc11_a01_spacer_dummy" file="dc11_a01_spacer_dummy.stl"/>
<mesh name="dc11_a01_dummy" file="dc11_a01_dummy.stl"/>
<mesh name="rotation_connector" file="rotation_connector.stl"/>
<mesh name="arm_connector" file="arm_connector.stl"/>
<mesh name="dc15_a01_horn_idle2_dummy" file="dc15_a01_horn_idle2_dummy.stl"/>
<mesh name="dc15_a01_case_m_dummy" file="dc15_a01_case_m_dummy.stl"/>
<mesh name="dc15_a01_case_f_dummy" file="dc15_a01_case_f_dummy.stl"/>
<mesh name="dc15_a01_horn_dummy" file="dc15_a01_horn_dummy.stl"/>
<mesh name="dc15_a01_case_b_dummy" file="dc15_a01_case_b_dummy.stl"/>
<mesh name="connector" file="connector.stl"/>
<mesh name="shoulder_rotation" file="shoulder_rotation.stl"/>
<mesh name="static_side" file="static_side.stl"/>
<mesh name="moving_side" file="moving_side.stl"/>
<texture type="skybox" builtin="gradient" rgb1="0.3 0.5 0.7" rgb2="0 0 0" width="512" height="3072" />
<texture type="2d" name="groundplane" builtin="checker" mark="edge" rgb1="0.2 0.3 0.4" rgb2="0.1 0.2 0.3" markrgb="0.8 0.8 0.8" width="300" height="300" />
<material name="groundplane" texture="groundplane" texuniform="true" texrepeat="5 5" reflectance="0.2" />
</asset>

<visual>
<headlight diffuse="0.6 0.6 0.6" ambient="0.3 0.3 0.3" specular="0 0 0" />
<rgba haze="0.15 0.25 0.35 1" />
<global azimuth="150" elevation="-20" offheight="640" />
</visual>

<worldbody>
<light pos="0 0 3" dir="0 0 -1" directional="false" />
<body name="floor">
<geom pos="0 0 0" name="floor" size="0 0 .125" type="plane" material="groundplane" conaffinity="1" contype="1" />
</body>

<body name="cube_red" pos="0.1 0.1 0.01">
<freejoint name="cube_red"/>
<inertial pos="0 0 0" mass="0.1" diaginertia="0.00001125 0.00001125 0.00001125"/>
<geom friction="0.5" condim="3" pos="0 0 0" size="0.015 0.015 0.015" type="box" name="cube_red" rgba="0.5 0 0 1" priority="1"/>
</body>

<body name="cube_blue" pos="-0.1 -0.1 0.01">
<freejoint name="cube_blue"/>
<inertial pos="0 0 0" mass="0.1" diaginertia="0.00001125 0.00001125 0.00001125"/>
<geom friction="0.5" condim="3" pos="0 0 0" size="0.015 0.015 0.015" type="box" name="cube_blue" rgba="0 0 0.5 1" priority="1"/>
</body>


<camera name="camera_front" pos="0.049 0.888 0.317" xyaxes="-0.998 0.056 -0.000 -0.019 -0.335 0.942"/>
<camera name="camera_top" pos="0 0 1" euler="0 0 0" mode="fixed"/>
<camera name="camera_vizu" pos="-0.1 0.6 0.3" quat="-0.15 -0.1 0.6 1"/>
<geom pos="0.0401555 -0.0353754 -0.0242427" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="base"/>
<geom pos="0.0511555 0.0406246 0.0099573" quat="0 0 0 1" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="0.0291555 0.000624643 0.0099573" quat="0 0 0 1" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="0.0511555 0.000624643 -0.0184427" quat="0 0 1 0" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="0.0401555 0.0326246 -0.0042427" type="mesh" rgba="0.498039 0.498039 0.498039 1" mesh="dc11_a01_dummy"/>
<geom pos="0.0291555 0.000624643 -0.0184427" quat="0 0.707107 0.707107 0" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="0.0291555 0.0406246 0.0099573" quat="0.707107 0 0 -0.707107" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="0.0511555 0.0406246 -0.0184427" quat="0 0.707107 -0.707107 0" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="0.0291555 0.0406246 -0.0184427" quat="0 -1 0 0" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="0.0511555 0.000624643 0.0099573" quat="0.707107 0 0 0.707107" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>

<body name="pitch1_assembly" pos="0.0401555 0.0326246 0.0166573">

<inertial pos="-0.000767103 -0.0121505 0.0134241" quat="0.498429 0.53272 -0.473938 0.493113" mass="0.0606831" diaginertia="1.86261e-05 1.72746e-05 1.11693e-05"/>
<joint name="shoulder_pan_joint" pos="0 0 0" axis="0 0 1" range="-3.14159 3.14159" damping="0.1" actuatorfrcrange="-0.5 0.5" actuatorfrclimited="true"/>
<geom pos="0 0 -0.0209" type="mesh" rgba="0.231373 0.380392 0.705882 1" mesh="rotation_connector"/>
<geom pos="-0.014 0.008 0.0264" quat="0 -0.707107 0 0.707107" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="-0.014 -0.032 0.0044" quat="0 -0.707107 0 0.707107" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="0.0144 -0.032 0.0264" quat="0.707107 0 0.707107 0" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="0.0002 0 0.0154" quat="0.707107 0 -0.707107 0" type="mesh" rgba="0.498039 0.498039 0.498039 1" mesh="dc11_a01_dummy"/>
<geom pos="0.0144 -0.032 0.0044" quat="0.5 0.5 0.5 0.5" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="-0.014 0.008 0.0044" quat="0.5 0.5 -0.5 -0.5" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="0.0144 0.008 0.0264" quat="0.5 -0.5 0.5 -0.5" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="0.0144 0.008 0.0044" quat="0 0.707107 0 0.707107" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>
<geom pos="-0.014 -0.032 0.0264" quat="0.5 -0.5 -0.5 0.5" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc11_a01_spacer_dummy"/>

<body name="pitch2_assembly" pos="-0.0188 0 0.0154" quat="0 0.707107 0 0.707107">

<inertial pos="0.0766242 -0.00031229 0.0187402" quat="0.52596 0.513053 0.489778 0.469319" mass="0.0432446" diaginertia="7.21796e-05 7.03107e-05 1.07533e-05"/>
<joint name="shoulder_lift_joint" pos="0 0 0" axis="0 0 1" range="-1.5708 1.22173" damping="0.1" actuatorfrcrange="-0.5 0.5" actuatorfrclimited="true"/>
<geom pos="0 0 0.019" quat="0.5 -0.5 -0.5 -0.5" type="mesh" rgba="0.980392 0.713726 0.00392157 1" mesh="arm_connector"/>
<geom pos="0.1083 -0.0148 0.03035" quat="1 0 0 0" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc15_a01_horn_idle2_dummy"/>
<geom pos="0.1083 -0.0148 0.01075" quat="0 -1 0 0" type="mesh" rgba="0.615686 0.811765 0.929412 1" mesh="dc15_a01_case_m_dummy"/>
<geom pos="0.1083 -0.0148 0.01075" quat="0 -1 0 0" type="mesh" rgba="0.980392 0.713726 0.00392157 1" mesh="dc15_a01_case_f_dummy"/>
<geom pos="0.1083 -0.0148 0.00715" quat="0 0 0.707107 -0.707107" type="mesh" rgba="0.972549 0.529412 0.00392157 1" mesh="dc15_a01_horn_dummy"/>
<geom pos="0.1083 -0.0148 0.03025" quat="0 -1 0 0" type="mesh" rgba="0.498039 0.498039 0.498039 1" mesh="dc15_a01_case_b_dummy"/>

<body name="pitch3_assembly" pos="0.1083 -0.0148 0.00425" quat="0.707107 0 0 0.707107">
<inertial pos="-0.0551014 -0.00287792 0.0144813" quat="0.500323 0.499209 0.499868 0.5006" mass="0.0788335" diaginertia="6.80912e-05 6.45748e-05 9.84479e-06"/>
<joint name="elbow_flex_joint" pos="0 0 0" axis="0 0 1" range="-1.48353 1.74533" damping="0.1" actuatorfrcrange="-0.5 0.5" actuatorfrclimited="true"/>
<geom pos="-0.00863031 0.00847376 0.0145" quat="0.5 0.5 0.5 -0.5" type="mesh" rgba="0.615686 0.811765 0.929412 1" mesh="connector"/>
<geom pos="-0.100476 -0.00269986 0.02635" quat="0.707107 0 0 -0.707107" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc15_a01_horn_idle2_dummy"/>
<geom pos="-0.100476 -0.00269986 0.00675" quat="0 -0.707107 0.707107 0" type="mesh" rgba="0.615686 0.811765 0.929412 1" mesh="dc15_a01_case_m_dummy"/>
<geom pos="-0.100476 -0.00269986 0.00675" quat="0 -0.707107 0.707107 0" type="mesh" rgba="0.980392 0.713726 0.00392157 1" mesh="dc15_a01_case_f_dummy"/>
<geom pos="-0.100476 -0.00269986 0.00315" quat="0.5 -0.5 -0.5 0.5" type="mesh" rgba="0.972549 0.529412 0.00392157 1" mesh="dc15_a01_horn_dummy"/>
<geom pos="-0.100476 -0.00269986 0.02625" quat="0 -0.707107 0.707107 0" type="mesh" rgba="0.498039 0.498039 0.498039 1" mesh="dc15_a01_case_b_dummy"/>

<body name="effector_roll_assembly" pos="-0.100476 -0.00269986 0.02925" quat="0 -0.707107 -0.707107 0">
<inertial pos="-1.65017e-05 -0.02659 0.0195388" quat="0.936813 0.349829 -0.00055331 -0.000300569" mass="0.0240506" diaginertia="6.03208e-06 4.12894e-06 3.3522e-06"/>
<joint name="wrist_flex_joint" pos="0 0 0" axis="0 0 1" range="-1.91986 1.91986" damping="0.1" actuatorfrcrange="-0.5 0.5" actuatorfrclimited="true"/>
<geom pos="-0.0109998 -0.0190002 0.039" quat="0.707107 -0.707107 0 0" type="mesh" rgba="0.615686 0.811765 0.929412 1" mesh="shoulder_rotation"/>
<geom pos="-7.44154e-06 -0.0385002 0.0133967" quat="0 0 0.707107 -0.707107" type="mesh" rgba="0.615686 0.811765 0.929412 1" mesh="dc15_a01_case_m_dummy"/>
<geom pos="-7.44154e-06 -0.0385002 0.0133967" quat="0 0 0.707107 -0.707107" type="mesh" rgba="0.980392 0.713726 0.00392157 1" mesh="dc15_a01_case_f_dummy"/>
<geom pos="-7.44154e-06 -0.0421002 0.0133967" quat="0 1 0 0" type="mesh" rgba="0.972549 0.529412 0.00392157 1" mesh="dc15_a01_horn_dummy"/>
<geom pos="-7.44154e-06 -0.0190002 0.0133967" quat="0 0 0.707107 -0.707107" type="mesh" rgba="0.498039 0.498039 0.498039 1" mesh="dc15_a01_case_b_dummy"/>

<body name="gripper_assembly" pos="-7.44154e-06 -0.0450002 0.0133967" quat="0.5 -0.5 -0.5 -0.5">
<inertial pos="-0.00548595 -0.000433143 -0.0190793" quat="0.700194 0.164851 0.167361 0.674197" mass="0.0360627" diaginertia="1.3261e-05 1.231e-05 5.3532e-06"/>
<joint name="wrist_roll_joint" pos="0 0 0" axis="0 0 1" range="-2.96706 2.96706" damping="0.1" actuatorfrcrange="-0.5 0.5" actuatorfrclimited="true"/>
<geom pos="-0.00075 -0.01475 -0.02" quat="0.707107 -0.707107 0 0" type="mesh" rgba="0.917647 0.917647 0.917647 1" mesh="static_side"/>
<geom pos="0.00755 0.01135 -0.013" quat="0.5 -0.5 0.5 0.5" type="mesh" rgba="0.647059 0.647059 0.647059 1" mesh="dc15_a01_horn_idle2_dummy"/>
<geom pos="0.00755 -0.00825 -0.013" quat="0.5 0.5 0.5 -0.5" type="mesh" rgba="0.615686 0.811765 0.929412 1" mesh="dc15_a01_case_m_dummy"/>
<geom pos="0.00755 -0.00825 -0.013" quat="0.5 0.5 0.5 -0.5" type="mesh" rgba="0.980392 0.713726 0.00392157 1" mesh="dc15_a01_case_f_dummy"/>
<geom pos="0.00755 -0.01185 -0.013" quat="0 -0.707107 0 -0.707107" type="mesh" rgba="0.972549 0.529412 0.00392157 1" mesh="dc15_a01_horn_dummy"/>
<geom pos="0.00755 0.01125 -0.013" quat="0.5 0.5 0.5 -0.5" type="mesh" rgba="0.498039 0.498039 0.498039 1" mesh="dc15_a01_case_b_dummy"/>

<body name="moving_side" pos="0.00755 -0.01475 -0.013" quat="0.707107 -0.707107 0 0">
<inertial pos="-0.000395599 0.022415 0.0145636" quat="0.722353 0.689129 0.0389102 0.0423547" mass="0.0089856" diaginertia="3.28451e-06 2.24898e-06 1.41539e-06"/>
<joint name="gripper_joint" pos="0 0 0" axis="0 0 1" range="-1.74533 0.0523599" damping="0.1" actuatorfrcrange="-0.5 0.5" actuatorfrclimited="true"/>
<geom pos="-0.00838199 -0.000256591 -0.003" type="mesh" rgba="0.768627 0.886275 0.952941 1" mesh="moving_side"/>
</body>
</body>
</body>
</body>
</body>
</body>
</worldbody>

<actuator>
<position joint="shoulder_pan_joint" ctrlrange="-3.14159 3.14159" ctrllimited="true" kp="20" />
<position joint="shoulder_lift_joint" ctrlrange="-1.5708 1.22173" ctrllimited="true" kp="20" />
<position joint="elbow_flex_joint" ctrlrange="-1.48353 1.74533" ctrllimited="true" kp="20" />
<position joint="wrist_flex_joint" ctrlrange="-1.91986 1.91986" ctrllimited="true" kp="20" />
<position joint="wrist_roll_joint" ctrlrange="-2.96706 2.96706" ctrllimited="true" kp="20" />
<position joint="gripper_joint" ctrlrange="-1.74533 0.0523599" ctrllimited="true" kp="20" />
</actuator>
</mujoco>

+ 435
- 0
examples/alexk-lcr/bus.py View File

@@ -0,0 +1,435 @@
"""Module for managing Dynamixel servo bus communication and control.

This module provides functionality for communicating with and controlling Dynamixel servos
through a serial bus interface. It includes methods for reading and writing servo parameters,
managing torque and operating modes, and handling joint positions and velocities.
"""

import enum
from typing import Union

import pyarrow as pa
from dynamixel_sdk import (
COMM_SUCCESS,
DXL_HIBYTE,
DXL_HIWORD,
DXL_LOBYTE,
DXL_LOWORD,
GroupSyncRead,
GroupSyncWrite,
PacketHandler,
PortHandler,
)

PROTOCOL_VERSION = 2.0
BAUD_RATE = 1_000_000
TIMEOUT_MS = 1000


def wrap_joints_and_values(
joints: Union[list[str], pa.Array],
values: Union[list[int], pa.Array],
) -> pa.StructArray:
"""Wrap joints and their corresponding values into a structured array.

Args:
joints: A list, numpy array, or pyarrow array of joint names.
values: A single integer value, or a list, numpy array, or pyarrow array of integer values.
If a single integer is provided, it will be broadcasted to all joints.

Returns:
A structured array with two fields:
- "joints": A string field containing the names of the joints.
- "values": An Int32Array containing the values corresponding to the joints.

Raises:
ValueError: If lengths of joints and values do not match.

Example:
joints = ["shoulder_pan", "shoulder_lift", "elbow_flex"]
values = [100, 200, 300]
struct_array = wrap_joints_and_values(joints, values)

This example wraps the given joints and their corresponding values into a structured array.

Another example with a single integer value:
joints = ["shoulder_pan", "shoulder_lift", "elbow_flex"]
value = 150
struct_array = wrap_joints_and_values(joints, value)

This example broadcasts the single integer value to all joints and wraps them into a structured array.

"""
if isinstance(values, int):
values = [values] * len(joints)

if len(joints) != len(values):
raise ValueError("joints and values must have the same length")

mask = pa.array([False] * len(values), type=pa.bool_())

if isinstance(values, list):
mask = pa.array([value is None for value in values])

if isinstance(values, pa.Array):
mask = values.is_null()

return pa.StructArray.from_arrays(
arrays=[joints, values], names=["joints", "values"], mask=mask,
).drop_null()


class TorqueMode(enum.Enum):
"""Enumeration for servo torque control modes."""

ENABLED = pa.scalar(1, pa.uint32())
DISABLED = pa.scalar(0, pa.uint32())


class OperatingMode(enum.Enum):
"""Enumeration for servo operating modes."""

VELOCITY = pa.scalar(1, pa.uint32())
POSITION = pa.scalar(3, pa.uint32())
EXTENDED_POSITION = pa.scalar(4, pa.uint32())
CURRENT_CONTROLLED_POSITION = pa.scalar(5, pa.uint32())
PWM = pa.scalar(16, pa.uint32())


X_SERIES_CONTROL_TABLE = [
("Model_Number", 0, 2),
("Model_Information", 2, 4),
("Firmware_Version", 6, 1),
("ID", 7, 1),
("Baud_Rate", 8, 1),
("Return_Delay_Time", 9, 1),
("Drive_Mode", 10, 1),
("Operating_Mode", 11, 1),
("Secondary_ID", 12, 1),
("Protocol_Type", 13, 1),
("Homing_Offset", 20, 4),
("Moving_Threshold", 24, 4),
("Temperature_Limit", 31, 1),
("Max_Voltage_Limit", 32, 2),
("Min_Voltage_Limit", 34, 2),
("PWM_Limit", 36, 2),
("Current_Limit", 38, 2),
("Acceleration_Limit", 40, 4),
("Velocity_Limit", 44, 4),
("Max_Position_Limit", 48, 4),
("Min_Position_Limit", 52, 4),
("Shutdown", 63, 1),
("Torque_Enable", 64, 1),
("LED", 65, 1),
("Status_Return_Level", 68, 1),
("Registered_Instruction", 69, 1),
("Hardware_Error_Status", 70, 1),
("Velocity_I_Gain", 76, 2),
("Velocity_P_Gain", 78, 2),
("Position_D_Gain", 80, 2),
("Position_I_Gain", 82, 2),
("Position_P_Gain", 84, 2),
("Feedforward_2nd_Gain", 88, 2),
("Feedforward_1st_Gain", 90, 2),
("Bus_Watchdog", 98, 1),
("Goal_PWM", 100, 2),
("Goal_Current", 102, 2),
("Goal_Velocity", 104, 4),
("Profile_Acceleration", 108, 4),
("Profile_Velocity", 112, 4),
("Goal_Position", 116, 4),
("Realtime_Tick", 120, 2),
("Moving", 122, 1),
("Moving_Status", 123, 1),
("Present_PWM", 124, 2),
("Present_Current", 126, 2),
("Present_Velocity", 128, 4),
("Present_Position", 132, 4),
("Velocity_Trajectory", 136, 4),
("Position_Trajectory", 140, 4),
("Present_Input_Voltage", 144, 2),
("Present_Temperature", 146, 1),
]

MODEL_CONTROL_TABLE = {
"x_series": X_SERIES_CONTROL_TABLE,
"xl330-m077": X_SERIES_CONTROL_TABLE,
"xl330-m288": X_SERIES_CONTROL_TABLE,
"xl430-w250": X_SERIES_CONTROL_TABLE,
"xm430-w350": X_SERIES_CONTROL_TABLE,
"xm540-w270": X_SERIES_CONTROL_TABLE,
}


class DynamixelBus:
"""Class for managing communication with Dynamixel servos on a serial bus."""

def __init__(self, port: str, description: dict[str, (int, str)]):
"""Initialize the Dynamixel bus connection.

Args:
port: The serial port to connect to the Dynamixel bus.
description: A dictionary containing the description of the motors connected to the bus.
The keys are the motor names and the values are tuples containing the motor id
and the motor model.

"""
self.port = port
self.descriptions = description
self.motor_ctrl = {}

for motor_name, (motor_id, motor_model) in description.items():
if motor_model not in MODEL_CONTROL_TABLE:
raise ValueError(f"Model {motor_model} is not supported.")

self.motor_ctrl[motor_name] = {}

self.motor_ctrl[motor_name]["id"] = motor_id
for data_name, address, bytes_size in MODEL_CONTROL_TABLE[motor_model]:
self.motor_ctrl[motor_name][data_name] = {
"addr": address,
"bytes_size": bytes_size,
}

self.port_handler = PortHandler(self.port)
self.packet_handler = PacketHandler(PROTOCOL_VERSION)

if not self.port_handler.openPort():
raise OSError(f"Failed to open port {self.port}")

self.port_handler.setBaudRate(BAUD_RATE)
self.port_handler.setPacketTimeoutMillis(TIMEOUT_MS)

self.group_readers = {}
self.group_writers = {}

def close(self):
"""Close the serial port connection."""
self.port_handler.closePort()

def write(self, data_name: str, data: pa.StructArray):
"""Write data to the specified servo parameters.

Args:
data_name: Name of the parameter to write.
data: Structured array containing the data to write.

"""
motor_ids = [
self.motor_ctrl[motor_name.as_py()]["id"]
for motor_name in data.field("joints")
]

values = pa.Array.from_buffers(
pa.uint32(),
length=len(data.field("values")),
buffers=data.field("values").buffers(),
)

group_key = f"{data_name}_" + "_".join([str(idx) for idx in motor_ids])

first_motor_name = list(self.motor_ctrl.keys())[0]

packet_address = self.motor_ctrl[first_motor_name][data_name]["addr"]
packet_bytes_size = self.motor_ctrl[first_motor_name][data_name]["bytes_size"]

init_group = data_name not in self.group_readers

if init_group:
self.group_writers[group_key] = GroupSyncWrite(
self.port_handler,
self.packet_handler,
packet_address,
packet_bytes_size,
)

for idx, value in zip(motor_ids, values):
value = value.as_py()
if value is None:
continue

if packet_bytes_size == 1:
data = [
DXL_LOBYTE(DXL_LOWORD(value)),
]
elif packet_bytes_size == 2:
data = [
DXL_LOBYTE(DXL_LOWORD(value)),
DXL_HIBYTE(DXL_LOWORD(value)),
]
elif packet_bytes_size == 4:
data = [
DXL_LOBYTE(DXL_LOWORD(value)),
DXL_HIBYTE(DXL_LOWORD(value)),
DXL_LOBYTE(DXL_HIWORD(value)),
DXL_HIBYTE(DXL_HIWORD(value)),
]
else:
raise NotImplementedError(
f"Value of the number of bytes to be sent is expected to be in [1, 2, 4], but {packet_bytes_size} "
f"is provided instead.",
)

if init_group:
self.group_writers[group_key].addParam(idx, data)
else:
self.group_writers[group_key].changeParam(idx, data)

comm = self.group_writers[group_key].txPacket()
if comm != COMM_SUCCESS:
raise ConnectionError(
f"Write failed due to communication error on port {self.port} for group_key {group_key}: "
f"{self.packet_handler.getTxRxResult(comm)}",
)

def read(self, data_name: str, motor_names: pa.Array) -> pa.StructArray:
"""Read data from the specified servo parameters.

Args:
data_name: Name of the parameter to read.
motor_names: Array of motor names to read from.

Returns:
Structured array containing the read data.

"""
motor_ids = [
self.motor_ctrl[motor_name.as_py()]["id"] for motor_name in motor_names
]

group_key = f"{data_name}_" + "_".join([str(idx) for idx in motor_ids])

first_motor_name = list(self.motor_ctrl.keys())[0]

packet_address = self.motor_ctrl[first_motor_name][data_name]["addr"]
packet_bytes_size = self.motor_ctrl[first_motor_name][data_name]["bytes_size"]

if data_name not in self.group_readers:
self.group_readers[group_key] = GroupSyncRead(
self.port_handler,
self.packet_handler,
packet_address,
packet_bytes_size,
)

for idx in motor_ids:
self.group_readers[group_key].addParam(idx)

comm = self.group_readers[group_key].txRxPacket()
if comm != COMM_SUCCESS:
raise ConnectionError(
f"Read failed due to communication error on port {self.port} for group_key {group_key}: "
f"{self.packet_handler.getTxRxResult(comm)}",
)

values = pa.array(
[
self.group_readers[group_key].getData(
idx, packet_address, packet_bytes_size,
)
for idx in motor_ids
],
type=pa.uint32(),
)
values = values.from_buffers(pa.int32(), len(values), values.buffers())

return wrap_joints_and_values(motor_names, values)

def write_torque_enable(self, torque_mode: pa.StructArray):
"""Enable or disable torque for the specified servos.

Args:
torque_mode: Structured array containing the torque mode for each servo.

"""
self.write("Torque_Enable", torque_mode)

def write_operating_mode(self, operating_mode: pa.StructArray):
"""Set the operating mode for the specified servos.

Args:
operating_mode: Structured array containing the operating mode for each servo.

"""
self.write("Operating_Mode", operating_mode)

def read_position(self, motor_names: pa.Array) -> pa.StructArray:
"""Read the current position of the specified servos.

Args:
motor_names: Array of motor names to read positions from.

Returns:
Structured array containing the current positions.

"""
return self.read("Present_Position", motor_names)

def read_velocity(self, motor_names: pa.Array) -> pa.StructArray:
"""Read the current velocity of the specified servos.

Args:
motor_names: Array of motor names to read velocities from.

Returns:
Structured array containing the current velocities.

"""
return self.read("Present_Velocity", motor_names)

def read_current(self, motor_names: pa.Array) -> pa.StructArray:
"""Read the current current of the specified servos.

Args:
motor_names: Array of motor names to read currents from.

Returns:
Structured array containing the current currents.

"""
return self.read("Present_Current", motor_names)

def write_goal_position(self, goal_position: pa.StructArray):
"""Set the goal position for the specified servos.

Args:
goal_position: Structured array containing the goal positions.

"""
self.write("Goal_Position", goal_position)

def write_goal_current(self, goal_current: pa.StructArray):
"""Set the goal current for the specified servos.

Args:
goal_current: Structured array containing the goal currents.

"""
self.write("Goal_Current", goal_current)

def write_position_p_gain(self, position_p_gain: pa.StructArray):
"""Set the position P gain for the specified servos.

Args:
position_p_gain: Structured array containing the position P gains.

"""
self.write("Position_P_Gain", position_p_gain)

def write_position_i_gain(self, position_i_gain: pa.StructArray):
"""Set the position I gain for the specified servos.

Args:
position_i_gain: Structured array containing the position I gains.

"""
self.write("Position_I_Gain", position_i_gain)

def write_position_d_gain(self, position_d_gain: pa.StructArray):
"""Set the position D gain for the specified servos.

Args:
position_d_gain: Structured array containing the position D gains.

"""
self.write("Position_D_Gain", position_d_gain)

+ 0
- 0
examples/alexk-lcr/configs/.gitkeep View File


+ 219
- 0
examples/alexk-lcr/configure.py View File

@@ -0,0 +1,219 @@
"""Module for configuring and setting up the Low Cost Robot (LCR) hardware.

This module provides functionality for initializing and configuring the LCR robot's
servo motors and other hardware components.

The program will:
1. Disable all torque motors of provided LCR.
2. Ask the user to move the LCR to the position 1 (see CONFIGURING.md for more details).
3. Record the position of the LCR.
4. Ask the user to move the LCR to the position 2 (see CONFIGURING.md for more details).
5. Record the position of the LCR.
8. Calculate interpolation functions.
9. Let the user verify in real time that the LCR is working properly.

It will also enable all appropriate operating modes for the LCR.
"""

import argparse
import json
import time

import pyarrow as pa
from bus import DynamixelBus, OperatingMode, TorqueMode
from pwm_position_control.functions import construct_control_table
from pwm_position_control.tables import (
construct_logical_to_pwm_conversion_table_arrow,
construct_pwm_to_logical_conversion_table_arrow,
)
from pwm_position_control.transform import pwm_to_logical_arrow, wrap_joints_and_values

FULL_ARM = pa.array(
[
"shoulder_pan",
"shoulder_lift",
"elbow_flex",
"wrist_flex",
"wrist_roll",
"gripper",
],
type=pa.string(),
)

ARM_WITHOUT_GRIPPER = pa.array(
["shoulder_pan", "shoulder_lift", "elbow_flex", "wrist_flex", "wrist_roll"],
type=pa.string(),
)

GRIPPER = pa.array(["gripper"], type=pa.string())


def pause():
"""Pause execution and wait for user input to continue."""
input("Press Enter to continue...")


def configure_servos(bus: DynamixelBus):
"""Configure servo motors with appropriate settings.

Args:
bus: DynamixelBus instance for servo communication

"""
bus.write_torque_enable(
wrap_joints_and_values(FULL_ARM, [TorqueMode.DISABLED.value] * 6),
)

bus.write_operating_mode(
wrap_joints_and_values(
ARM_WITHOUT_GRIPPER, [OperatingMode.EXTENDED_POSITION.value] * 5,
),
)

bus.write_operating_mode(
wrap_joints_and_values(
GRIPPER, [OperatingMode.CURRENT_CONTROLLED_POSITION.value],
),
)


def main():
"""Initialize and configure the LCR robot hardware components."""
parser = argparse.ArgumentParser(
description="LCR Auto Configure: This program is used to automatically configure the Low Cost Robot (LCR) for "
"the user.",
)

parser.add_argument("--port", type=str, required=True, help="The port of the LCR.")
parser.add_argument(
"--right",
action="store_true",
help="If the LCR is on the right side of the user.",
)
parser.add_argument(
"--left",
action="store_true",
help="If the LCR is on the left side of the user.",
)
parser.add_argument(
"--follower",
action="store_true",
help="If the LCR is the follower of the user.",
)
parser.add_argument(
"--leader", action="store_true", help="If the LCR is the leader of the user.",
)

args = parser.parse_args()

if args.right and args.left:
raise ValueError("You cannot specify both --right and --left.")

if args.follower and args.leader:
raise ValueError("You cannot specify both --follower and --leader.")

targets = (
wrap_joints_and_values(FULL_ARM, [0, -90, 90, 0, -90, 0]),
wrap_joints_and_values(FULL_ARM, [90, 0, 0, 90, 0, -90]),
)

arm = DynamixelBus(
args.port,
{
"shoulder_pan": (1, "x_series"),
"shoulder_lift": (2, "x_series"),
"elbow_flex": (3, "x_series"),
"wrist_flex": (4, "x_series"),
"wrist_roll": (5, "x_series"),
"gripper": (6, "x_series"),
},
)

configure_servos(arm)

print("Please move the LCR to the first position.")
pause()
pwm_position_1 = arm.read_position(FULL_ARM)

print("Please move the LCR to the second position.")
pause()
pwm_position_2 = arm.read_position(FULL_ARM)

print("Configuration completed.")

pwm_positions = (pwm_position_1, pwm_position_2)

pwm_to_logical_conversion_table = construct_pwm_to_logical_conversion_table_arrow(
pwm_positions, targets,
)
logical_to_pwm_conversion_table = construct_logical_to_pwm_conversion_table_arrow(
pwm_positions, targets,
)

control_table_json = {}
for i in range(len(FULL_ARM)):
model = (
"xl430-w250"
if i <= 1 and args.follower
else "xl330-m288" if args.follower else "xl330-m077"
)

control_table_json[FULL_ARM[i].as_py()] = {
"id": i + 1,
"model": model,
"torque": (
True if args.follower else True if args.leader and i == 5 else False
),
"goal_current": (
500
if args.follower and i == 5
else 40 if args.leader and i == 5 else None
),
"goal_position": -40.0 if args.leader and i == 5 else None,
"pwm_to_logical": pwm_to_logical_conversion_table[FULL_ARM[i].as_py()],
"logical_to_pwm": logical_to_pwm_conversion_table[FULL_ARM[i].as_py()],
"P": (
640
if model == "xl430-w250"
else 1500 if model == "xl330-m288" and i != 5 else 250
),
"I": 0,
"D": 3600 if model == "xl430-w250" else 600,
}

left = "left" if args.left else "right"
leader = "leader" if args.leader else "follower"

path = (
input(
f"Please enter the path of the configuration file (default is ./examples/alexk-lcr/configs/{leader}.{left}.json): ",
)
or f"./examples/alexk-lcr/configs/{leader}.{left}.json"
)

with open(path, "w") as file:
json.dump(control_table_json, file)

control_table = construct_control_table(
pwm_to_logical_conversion_table, logical_to_pwm_conversion_table,
)

while True:
try:
pwm_position = arm.read_position(FULL_ARM)
logical_position = pwm_to_logical_arrow(
pwm_position, control_table, ranged=True,
).field("values")

print(f"Logical Position: {logical_position}")

except ConnectionError:
print(
"Connection error occurred. Please check the connection and try again.",
)

time.sleep(0.5)


if __name__ == "__main__":
main()

+ 74
- 0
examples/alexk-lcr/graphs/bi_teleop_real.yml View File

@@ -0,0 +1,74 @@
nodes:
- id: lcr-left-leader
build: pip install ../../../node-hub/dynamixel-client
path: dynamixel-client
inputs:
pull_position: dora/timer/millis/10 # pull the position every 10ms
write_goal_position: lcr-to-lcr-left/leader_goal
outputs:
- position
env:
PORT: /dev/tty.usbmodem575E0030111
CONFIG: ../configs/leader.left.json

- id: lcr-to-lcr-left
build: pip install git+https://github.com/Hennzau/pwm-position-control
path: ../nodes/interpolate_lcr_to_lcr.py
inputs:
leader_position: lcr-left-leader/position
follower_position: lcr-left-follower/position
outputs:
- follower_goal
- leader_goal
env:
LEADER_CONTROL: ../configs/leader.left.json
FOLLOWER_CONTROL: ../configs/follower.left.json

- id: lcr-left-follower
build: pip install ../../../node-hub/dynamixel-client
path: dynamixel-client
inputs:
pull_position: dora/timer/millis/10
write_goal_position: lcr-to-lcr-left/follower_goal
outputs:
- position
env:
PORT: /dev/tty.usbmodem575E0031141
CONFIG: ../configs/follower.left.json

- id: lcr-right-leader
build: pip install ../../../node-hub/dynamixel-client
path: dynamixel-client
inputs:
pull_position: dora/timer/millis/10 # pull the position every 10ms
write_goal_position: lcr-to-lcr-right/leader_goal
outputs:
- position
env:
PORT: /dev/tty.usbmodem575E0030531
CONFIG: ../configs/leader.right.json

- id: lcr-to-lcr-right
build: pip install git+https://github.com/Hennzau/pwm-position-control
path: ../nodes/interpolate_lcr_to_lcr.py
inputs:
leader_position: lcr-right-leader/position
follower_position: lcr-right-follower/position
outputs:
- follower_goal
- leader_goal
env:
LEADER_CONTROL: ../configs/leader.right.json
FOLLOWER_CONTROL: ../configs/follower.right.json

- id: lcr-right-follower
build: pip install ../../../node-hub/dynamixel-client
path: dynamixel-client
inputs:
pull_position: dora/timer/millis/10
write_goal_position: lcr-to-lcr-right/follower_goal
outputs:
- position
env:
PORT: /dev/tty.usbmodem575E0032531
CONFIG: ../configs/follower.right.json

+ 40
- 0
examples/alexk-lcr/graphs/mono_replay_real.yml View File

@@ -0,0 +1,40 @@
nodes:
- id: replay-client
build: pip install ../../../node-hub/replay-client
path: replay-client
inputs:
pull_position: dora/timer/millis/33
outputs:
- position
- end
env:
PATH: ../../../datasets/enzo2
EPISODE: 1

- id: replay-to-lcr
build: pip install git+https://github.com/Hennzau/pwm-position-control
path: ../nodes/interpolate_replay_to_lcr.py
inputs:
leader_position:
source: replay-client/position
queue_size: 1
follower_position:
source: lcr-follower/position
queue_size: 1
outputs:
- follower_goal
env:
FOLLOWER_CONTROL: ../configs/follower.left.json

- id: lcr-follower
build: pip install ../../../node-hub/dynamixel-client
path: dynamixel-client
inputs:
pull_position: dora/timer/millis/33
write_goal_position: replay-to-lcr/follower_goal
end: replay-client/end
outputs:
- position
env:
PORT: /dev/tty.usbmodem575E0031141
CONFIG: ../configs/follower.left.json

+ 37
- 0
examples/alexk-lcr/graphs/mono_teleop_real.yml View File

@@ -0,0 +1,37 @@
nodes:
- id: lcr-leader
build: pip install ../../../node-hub/dynamixel-client
path: dynamixel-client
inputs:
pull_position: dora/timer/millis/10
write_goal_position: lcr-to-lcr/leader_goal
outputs:
- position
env:
PORT: /dev/tty.usbmodem575E0030111
CONFIG: ../configs/leader.left.json

- id: lcr-to-lcr
build: pip install git+https://github.com/Hennzau/pwm-position-control
path: ../nodes/interpolate_lcr_to_lcr.py
inputs:
leader_position: lcr-leader/position
follower_position: lcr-follower/position
outputs:
- follower_goal
- leader_goal
env:
LEADER_CONTROL: ../configs/leader.left.json
FOLLOWER_CONTROL: ../configs/follower.left.json

- id: lcr-follower
build: pip install ../../../node-hub/dynamixel-client
path: dynamixel-client
inputs:
pull_position: dora/timer/millis/10
write_goal_position: lcr-to-lcr/follower_goal
outputs:
- position
env:
PORT: /dev/tty.usbmodem575E0031141
CONFIG: ../configs/follower.left.json

+ 70
- 0
examples/alexk-lcr/graphs/mono_teleop_real_and_simu.yml View File

@@ -0,0 +1,70 @@
nodes:
- id: lcr-leader
build: pip install ../../../node-hub/dynamixel-client
path: dynamixel-client
inputs:
pull_position: simu-lcr-follower/tick
write_goal_position: lcr-to-lcr/leader_goal
end: simu-lcr-follower/end
outputs:
- position
env:
PORT: /dev/tty.usbmodem575E0030111
CONFIG: ../configs/leader.left.json

- id: lcr-to-lcr
build: pip install git+https://github.com/Hennzau/pwm-position-control
path: ../nodes/interpolate_lcr_to_lcr.py
inputs:
leader_position: lcr-leader/position
follower_position: lcr-follower/position
outputs:
- follower_goal
- leader_goal
env:
LEADER_CONTROL: ../configs/leader.left.json
FOLLOWER_CONTROL: ../configs/follower.left.json

- id: lcr-follower
build: pip install ../../../node-hub/dynamixel-client
path: dynamixel-client
inputs:
pull_position: simu-lcr-follower/tick
write_goal_position: lcr-to-lcr/follower_goal
end: simu-lcr-follower/end
outputs:
- position
env:
PORT: /dev/tty.usbmodem575E0031141
CONFIG: ../configs/follower.left.json

- id: lcr-to-simu-lcr
build: pip install git+https://github.com/Hennzau/pwm-position-control
path: ../nodes/interpolate_lcr_to_simu_lcr.py
inputs:
leader_position:
source: lcr-leader/position
queue_size: 1
outputs:
- follower_goal
- leader_goal
env:
LEADER_CONTROL: ../configs/leader.left.json

- id: simu-lcr-follower
build: pip install ../../../node-hub/mujoco-client
# for windows
# path: mujoco-client
# for macos
path: mjpython
args: ../../../node-hub/mujoco-client/mujoco_client/main.py
inputs:
write_goal_position: lcr-to-simu-lcr/follower_goal
tick: dora/timer/millis/10
outputs:
- position
- tick
- end
env:
SCENE: ../assets/simulation/reach_cube.xml
CONFIG: ../configs/follower.left.json

+ 43
- 0
examples/alexk-lcr/graphs/mono_teleop_simu.yml View File

@@ -0,0 +1,43 @@
nodes:
- id: lcr-leader
build: pip install ../../../node-hub/dynamixel-client
path: dynamixel-client
inputs:
pull_position: simu-lcr-follower/tick
write_goal_position: lcr-to-simu-lcr/leader_goal
end: simu-lcr-follower/end
outputs:
- position
env:
PORT: /dev/tty.usbmodem575E0030111
CONFIG: ../configs/leader.left.json

- id: lcr-to-simu-lcr
build: pip install git+https://github.com/Hennzau/pwm-position-control
path: ../nodes/interpolate_lcr_to_simu_lcr.py
inputs:
leader_position: lcr-leader/position
outputs:
- follower_goal
- leader_goal
env:
LEADER_CONTROL: ../configs/leader.left.json

- id: simu-lcr-follower
build: pip install ../../../node-hub/mujoco-client
# for windows
# path: mujoco-client
# for macos
path: mjpython
args: ../../../node-hub/mujoco-client/mujoco_client/main.py
inputs:
write_goal_position: lcr-to-simu-lcr/follower_goal
tick: dora/timer/millis/10
outputs:
- position
- tick
- end

env:
SCENE: ../assets/simulation/reach_cube.xml
CONFIG: ../configs/follower.left.json

+ 119
- 0
examples/alexk-lcr/graphs/record_mono_teleop_real.yml View File

@@ -0,0 +1,119 @@
nodes:
- id: lcr-leader
build: pip install ../../../node-hub/dynamixel-client
path: dynamixel-client
inputs:
pull_position:
source: lerobot-dashboard/tick
queue_size: 1
write_goal_position: lcr-to-lcr/leader_goal
end: lerobot-dashboard/end
outputs:
- position
env:
PORT: /dev/tty.usbmodem575E0030111
CONFIG: ../configs/leader.left.json

- id: lcr-to-lcr
build: pip install git+https://github.com/Hennzau/pwm-position-control
path: ../nodes/interpolate_lcr_to_lcr.py
inputs:
leader_position:
source: lcr-leader/position
queue_size: 1
follower_position:
source: lcr-follower/position
queue_size: 1
outputs:
- follower_goal
- leader_goal
env:
LEADER_CONTROL: ../configs/leader.left.json
FOLLOWER_CONTROL: ../configs/follower.left.json

- id: lcr-to-record
build: pip install git+https://github.com/Hennzau/pwm-position-control
path: ../nodes/interpolate_lcr_to_record.py
inputs:
leader_position:
source: lcr-leader/position
queue_size: 1
follower_position:
source: lcr-follower/position
queue_size: 1
outputs:
- logical_goal
- logical_position
env:
LEADER_CONTROL: ../configs/leader.left.json
FOLLOWER_CONTROL: ../configs/follower.left.json

- id: lcr-follower
build: pip install ../../../node-hub/dynamixel-client
path: dynamixel-client
inputs:
pull_position:
source: lerobot-dashboard/tick
queue_size: 1
write_goal_position:
source: lcr-to-lcr/follower_goal
queue_size: 1
end: lerobot-dashboard/end
outputs:
- position
env:
PORT: /dev/tty.usbmodem575E0031141
CONFIG: ../configs/follower.left.json

- id: opencv-video-capture
build: pip install ../../../node-hub/opencv-video-capture
path: opencv-video-capture
inputs:
tick:
source: lerobot-dashboard/tick
queue_size: 1
outputs:
- image
env:
PATH: 1
IMAGE_WIDTH: 860
IMAGE_HEIGHT: 540

- id: video-encoder
build: pip install ../../../node-hub/video-encoder
path: video-encoder
inputs:
image: opencv-video-capture/image
episode_index: lerobot-dashboard/episode
outputs:
- image
env:
VIDEO_NAME: cam_up
FPS: 30

- id: lerobot-dashboard
build: pip install ../../../node-hub/lerobot-dashboard
path: lerobot-dashboard
inputs:
tick:
source: dora/timer/millis/16
queue_size: 1
image_left: opencv-video-capture/image
outputs:
- tick
- episode
- failed
- end
env:
WINDOW_WIDTH: 1720
WINDOW_HEIGHT: 540

- id: dora-record
build: cargo install dora-record
path: dora-record
inputs:
action: lcr-to-record/logical_goal
observation.state: lcr-to-record/logical_position
episode_index: lerobot-dashboard/episode
failed_episode_index: lerobot-dashboard/failed
observation.images.cam_up: video-encoder/image

+ 153
- 0
examples/alexk-lcr/nodes/interpolate_lcr_to_lcr.py View File

@@ -0,0 +1,153 @@
"""Module for interpolating between LCR robot configurations.

This module provides functionality for calculating and interpolating between
different LCR robot configurations to ensure smooth transitions.
"""

import argparse
import json
import os

import pyarrow as pa
import pyarrow.compute as pc
from dora import Node
from pwm_position_control.load import load_control_table_from_json_conversion_tables
from pwm_position_control.transform import (
logical_to_pwm_with_offset_arrow,
pwm_to_logical_arrow,
wrap_joints_and_values,
)


def main():
"""Initialize and run the LCR interpolation node."""
parser = argparse.ArgumentParser(
description="Interpolation LCR Node: This Dora node is used to calculates appropriate goal positions for the "
"LCR followers knowing a Leader position and Follower position.",
)

parser.add_argument(
"--name",
type=str,
required=False,
help="The name of the node in the dataflow.",
default="lcr-to-lcr",
)
parser.add_argument(
"--leader-control",
type=str,
help="The configuration file for controlling the leader.",
default=None,
)
parser.add_argument(
"--follower-control",
type=str,
help="The configuration file for controlling the follower.",
default=None,
)

args = parser.parse_args()

if not os.environ.get("LEADER_CONTROL") and args.leader_control is None:
raise ValueError(
"The leader control is not set. Please set the configuration of the leader in the environment variables or "
"as an argument.",
)

if not os.environ.get("FOLLOWER_CONTROL") and args.follower_control is None:
raise ValueError(
"The follower control is not set. Please set the configuration of the follower in the environment "
"variables or as an argument.",
)

with open(
os.environ.get("LEADER_CONTROL")
if args.leader_control is None
else args.leader_control,
) as file:
leader_control = json.load(file)
load_control_table_from_json_conversion_tables(leader_control, leader_control)

with open(
os.environ.get("FOLLOWER_CONTROL")
if args.follower_control is None
else args.follower_control,
) as file:
follower_control = json.load(file)
load_control_table_from_json_conversion_tables(
follower_control, follower_control,
)

initial_mask = [
True if leader_control[joint]["goal_position"] is not None else False
for joint in leader_control.keys()
]
logical_leader_initial_goal = wrap_joints_and_values(
[
joint
for joint in leader_control.keys()
if leader_control[joint]["goal_position"] is not None
],
[
leader_control[joint]["goal_position"]
for joint in leader_control.keys()
if leader_control[joint]["goal_position"] is not None
],
)

node = Node(args.name)

leader_initialized = False
follower_initialized = False

follower_position = None

for event in node:
event_type = event["type"]

if event_type == "INPUT":
event_id = event["id"]

if event_id == "leader_position":
leader_position = event["value"]

if not leader_initialized:
leader_initialized = True

pwm_goal = logical_to_pwm_with_offset_arrow(
leader_position.filter(initial_mask),
logical_leader_initial_goal,
leader_control,
)

node.send_output("leader_goal", pwm_goal, event["metadata"])

if not follower_initialized:
continue

leader_position = pwm_to_logical_arrow(leader_position, leader_control)

interpolation = pa.array([1, 1, 1, 1, 1, 700 / 450], type=pa.float32())

logical_goal = wrap_joints_and_values(
leader_position.field("joints"),
pc.multiply(leader_position.field("values"), interpolation),
)

pwm_goal = logical_to_pwm_with_offset_arrow(
follower_position, logical_goal, follower_control,
)

node.send_output("follower_goal", pwm_goal, event["metadata"])

elif event_id == "follower_position":
follower_position = event["value"]
follower_initialized = True

elif event_type == "ERROR":
print("[lcr-to-lcr] error: ", event["error"])
break


if __name__ == "__main__":
main()

+ 115
- 0
examples/alexk-lcr/nodes/interpolate_lcr_to_record.py View File

@@ -0,0 +1,115 @@
"""TODO: Add docstring."""

import argparse
import json
import os

import pyarrow as pa
import pyarrow.compute as pc
from dora import Node
from pwm_position_control.load import load_control_table_from_json_conversion_tables
from pwm_position_control.transform import (
pwm_to_logical_arrow,
wrap_joints_and_values,
)


def main():
"""TODO: Add docstring."""
parser = argparse.ArgumentParser(
description="Interpolation LCR Node: This Dora node is used to calculates appropriate goal positions for the "
"LCR followers knowing a Leader position and Follower position.",
)

parser.add_argument(
"--name",
type=str,
required=False,
help="The name of the node in the dataflow.",
default="lcr-to-record",
)
parser.add_argument(
"--leader-control",
type=str,
help="The configuration file for controlling the leader.",
default=None,
)
parser.add_argument(
"--follower-control",
type=str,
help="The configuration file for controlling the follower.",
default=None,
)

args = parser.parse_args()

if not os.environ.get("LEADER_CONTROL") and args.leader_control is None:
raise ValueError(
"The leader control is not set. Please set the configuration of the leader in the environment variables or "
"as an argument.",
)

if not os.environ.get("FOLLOWER_CONTROL") and args.follower_control is None:
raise ValueError(
"The follower control is not set. Please set the configuration of the follower in the environment "
"variables or as an argument.",
)

with open(
os.environ.get("LEADER_CONTROL")
if args.leader_control is None
else args.leader_control,
) as file:
leader_control = json.load(file)
load_control_table_from_json_conversion_tables(leader_control, leader_control)

with open(
os.environ.get("FOLLOWER_CONTROL")
if args.follower_control is None
else args.follower_control,
) as file:
follower_control = json.load(file)
load_control_table_from_json_conversion_tables(
follower_control, follower_control,
)

node = Node(args.name)

for event in node:
event_type = event["type"]

if event_type == "INPUT":
event_id = event["id"]

if event_id == "leader_position":
leader_position = event["value"]

leader_position = pwm_to_logical_arrow(leader_position, leader_control)

interpolation = pa.array([1, 1, 1, 1, 1, 700 / 450], type=pa.float32())

logical_goal = wrap_joints_and_values(
leader_position.field("joints"),
pc.multiply(leader_position.field("values"), interpolation),
)

node.send_output("logical_goal", logical_goal, event["metadata"])

elif event_id == "follower_position":
follower_position = event["value"]

follower_position = pwm_to_logical_arrow(
follower_position, follower_control,
)

node.send_output(
"logical_position", follower_position, event["metadata"],
)

elif event_type == "ERROR":
print("[lcr-to-record] error: ", event["error"])
break


if __name__ == "__main__":
main()

+ 138
- 0
examples/alexk-lcr/nodes/interpolate_lcr_to_simu_lcr.py View File

@@ -0,0 +1,138 @@
"""Module for interpolating between LCR and simulated LCR configurations.

This module provides functionality for calculating and interpolating between
physical LCR robot configurations and their simulated counterparts.
"""

import argparse
import json
import os

import numpy as np
import pyarrow as pa
import pyarrow.compute as pc
from dora import Node
from pwm_position_control.load import load_control_table_from_json_conversion_tables
from pwm_position_control.transform import (
logical_to_pwm_with_offset_arrow,
pwm_to_logical_arrow,
wrap_joints_and_values,
)


def main():
"""Initialize and run the LCR to simulated LCR interpolation node."""
parser = argparse.ArgumentParser(
description="Interpolation LCR Node: This Dora node is used to calculates appropriate goal positions for the "
"LCR followers knowing a Leader position and Follower position.",
)

parser.add_argument(
"--name",
type=str,
required=False,
help="The name of the node in the dataflow.",
default="lcr-to-simu-lcr",
)
parser.add_argument(
"--leader-control",
type=str,
help="The configuration file for controlling the leader.",
default=None,
)

args = parser.parse_args()

if not os.environ.get("LEADER_CONTROL") and args.leader_control is None:
raise ValueError(
"The leader control is not set. Please set the configuration of the leader in the environment variables or "
"as an argument.",
)

with open(
os.environ.get("LEADER_CONTROL")
if args.leader_control is None
else args.leader_control,
) as file:
leader_control = json.load(file)
load_control_table_from_json_conversion_tables(leader_control, leader_control)

initial_mask = [
True if leader_control[joint]["goal_position"] is not None else False
for joint in leader_control.keys()
]
logical_leader_initial_goal = wrap_joints_and_values(
[
joint
for joint in leader_control.keys()
if leader_control[joint]["goal_position"] is not None
],
[
leader_control[joint]["goal_position"]
for joint in leader_control.keys()
if leader_control[joint]["goal_position"] is not None
],
)

node = Node(args.name)

leader_initialized = False

for event in node:
event_type = event["type"]

if event_type == "INPUT":
event_id = event["id"]

if event_id == "leader_position":
leader_position = event["value"]

if not leader_initialized:
leader_initialized = True

physical_goal = logical_to_pwm_with_offset_arrow(
leader_position.filter(initial_mask),
logical_leader_initial_goal,
leader_control,
)

node.send_output("leader_goal", physical_goal, event["metadata"])

leader_position = pwm_to_logical_arrow(leader_position, leader_control)

interpolation_m = pa.array(
[
np.pi / 180,
np.pi / 180,
np.pi / 180,
np.pi / 180,
np.pi / 180,
np.pi / 180 * 700 / 450,
],
type=pa.float32(),
)

interpolation_a = pa.array([0, 0, 0, 0, 90, 0], type=pa.float32())

logical_goal = wrap_joints_and_values(
pa.array(
[
joint.as_py() + "_joint"
for joint in leader_position.field("joints")
],
),
pc.multiply(
pc.add(leader_position.field("values"), interpolation_a),
interpolation_m,
),
)

node.send_output("follower_goal", logical_goal, event["metadata"])

elif event_type == "ERROR":
print("[lcr-to-simu] error: ", event["error"])
break


if __name__ == "__main__":
main()

+ 85
- 0
examples/alexk-lcr/nodes/interpolate_replay_to_lcr.py View File

@@ -0,0 +1,85 @@
"""TODO: Add docstring."""

import argparse
import json
import os

from dora import Node
from pwm_position_control.load import load_control_table_from_json_conversion_tables
from pwm_position_control.transform import logical_to_pwm_with_offset_arrow


def main():
"""TODO: Add docstring."""
parser = argparse.ArgumentParser(
description="Interpolation LCR Node: This Dora node is used to calculates appropriate goal positions for the "
"LCR followers knowing a Leader position and Follower position.",
)

parser.add_argument(
"--name",
type=str,
required=False,
help="The name of the node in the dataflow.",
default="replay-to-lcr",
)
parser.add_argument(
"--follower-control",
type=str,
help="The configuration file for controlling the follower.",
default=None,
)

args = parser.parse_args()

if not os.environ.get("FOLLOWER_CONTROL") and args.follower_control is None:
raise ValueError(
"The follower control is not set. Please set the configuration of the follower in the environment "
"variables or as an argument.",
)

with open(
os.environ.get("FOLLOWER_CONTROL")
if args.follower_control is None
else args.follower_control,
) as file:
follower_control = json.load(file)
load_control_table_from_json_conversion_tables(
follower_control, follower_control,
)

node = Node(args.name)

follower_initialized = False

follower_position = None

for event in node:
event_type = event["type"]

if event_type == "INPUT":
event_id = event["id"]

if event_id == "leader_position":
leader_position = event["value"]

if not follower_initialized:
continue

physical_goal = logical_to_pwm_with_offset_arrow(
follower_position, leader_position, follower_control,
)

node.send_output("follower_goal", physical_goal, event["metadata"])

elif event_id == "follower_position":
follower_position = event["value"]
follower_initialized = True

elif event_type == "ERROR":
print("[replay-to-lcr] error: ", event["error"])
break


if __name__ == "__main__":
main()

+ 64
- 0
examples/aloha/ASSEMBLING.md View File

@@ -0,0 +1,64 @@
# Dora pipeline Robots

Aloha is a bi manual robot that can be teleoperated using a similar arm. This repository contains
the Dora pipeline to manipulate arms, cameras, and record/replay episodes with LeRobot.

- To check if the robot is connected, install dynamixel
wizard [here](https://emanual.robotis.com/docs/en/software/dynamixel/dynamixel_wizard2/)
- Dynamixel wizard is a very helpful debugging tool that connects to individual motors of the robot. It allows
things such as rebooting the motor (very useful!), torque on/off, and sending commands.
However, it has no knowledge about the kinematics of the robot, so be careful about collisions.
The robot _will_ collapse if motors are torque off i.e. there is no automatically engaged brakes in joints.
- Open Dynamixel wizard, go into `options` and select:
- Protocol 2.0
- All ports
- 1000000 bps
- ID range from 0-10
- Note: repeat above everytime before you scan.
- Then hit `Scan`. There should be 4 devices showing up, each with 9 motors.
- One issue that arises is the port each robot binds to can change over time, e.g. a robot that
is initially `ttyUSB0` might suddenly become `ttyUSB5`. To resolve this, we bind each robot to a fixed symlink
port with the following mapping:
- `ttyDXL_master_right`: right master robot (master: the robot that the operator would be holding)
- `ttyDXL_puppet_right`: right puppet robot (puppet: the robot that performs the task)
- `ttyDXL_master_left`: left master robot
- `ttyDXL_puppet_left`: left puppet robot
- Take `ttyDXL_master_right`: right master robot as an example:

1. Find the port that the right master robot is currently binding to, e.g. `ttyUSB0`
2. run `udevadm info --name=/dev/ttyUSB0 --attribute-walk | grep serial` to obtain the serial number. Use the first
one that shows up, the format should look similar to `FT6S4DSP`.
3. `sudo vim /etc/udev/rules.d/99-fixed-interbotix-udev.rules` and add the following line:

SUBSYSTEM=="tty", ATTRS{serial}=="<serial number here>", ENV{ID_MM_DEVICE_IGNORE}="1",
ATTR{device/latency_timer}="1", SYMLINK+="ttyDXL_master_right"

4. This will make sure the right master robot is _always_ binding to `ttyDXL_master_right`
5. Repeat with the rest of 3 arms.

> You have an example of the given rules in `hardware_config.yml`.

```bash
cd dora

sudo cp examples/aloha/hardware_config/99-interbotix-udev.rules /etc/udev/rules.d
sudo cp examples/aloha/hardware_config/99-fixed-interbotix-udev.rules /etc/udev/rules.d
```

- To apply the changes, run `sudo udevadm control --reload && sudo udevadm trigger`
- If successful, you should be able to find `ttyDXL*` in your `/dev`

## Documentation

https://github.com/Interbotix/interbotix_ros_toolboxes/blob/humble/interbotix_xs_toolbox/interbotix_xs_modules/interbotix_xs_modules/xs_robot/core.py

https://github.com/Interbotix/interbotix_ros_toolboxes/blob/c187bcea89b60391244bb19943ebd78f770aa975/interbotix_xs_toolbox/interbotix_xs_modules/interbotix_xs_modules/xs_robot/core.py#L380-L398

## Acknowledgement

This work is inspired from [tonyzhaozh/aloha](https://github.com/tonyzhaozh/aloha) and we're trying to bring perfornance
improvement.

## License

This library is licensed under the [Apache License 2.0](../../LICENSE).

+ 95
- 0
examples/aloha/CONFIGURING.md View File

@@ -0,0 +1,95 @@
# Dora pipeline Robots

Aloha is a bi manual robot that can be teleoperated using a similar arm. This repository contains
the Dora pipeline to manipulate arms, cameras, and record/replay episodes with LeRobot.

## Configuring

Once you have assembled the robot, and installed the required software, you can configure the robot. It's essential to
configure it
correctly for the robot to work as expected. Here are the reasons why you need to configure the robot:

- You may have done some 'mistakes' during the assembly, like inverting the motors, or changing the offsets by rotating
the motors before assembling the robot. So your configuration will be different from the one we used to record the
data set.
- The recording pipeline needs to know the position of the motors to record the data set correctly. If the motors are
calibrated differently, the data set will be incorrect.

**Please read the instructions carefully before configuring the robot.**

The first thing to do is to configure the Servo BUS:

- Setting all the servos to the same baud rate (1M).
- Setting the ID of the servos from the base (1) to the gripper (9) for the Follower and Leader arms.

Those steps can be done using the official wizard provided by the
manufacturer [ROBOTICS](https://emanual.robotis.com/docs/en/software/dynamixel/dynamixel_wizard2/).

After that, you need to configure the homing offsets and drive mode to have the same behavior for every user. We
recommend using our on-board tool to set all of that automatically:

- Connect the Follower arm to your computer.
- Retrieve the device port from the official wizard.
- Run the configuration tool with the following command and follow the instructions:

```bash
cd dora/

# If you are using a custom environment, you will have to activate it before running the command
source [your_custom_env_bin]/activate

# If you followed the installation instructions, you can run the following command
source venv/bin/activate # On Linux
source venv/Scripts/activate # On Windows bash
venv\Scripts\activate.bat # On Windows cmd
venv\Scripts\activate.ps1 # On Windows PowerShell

python ./examples/aloha/configure.py --port /dev/ttyUSB0 --follower --left # (or right)
```

**Note:** change `/dev/ttyUSB0` to the device port you retrieved from the official wizard (like `COM3` on Windows).
**Note:** The configuration tool will disable all torque so you can move the arm freely to the Position 1.
**Note:** You will be asked to set the arm in two different positions. The two positions are:

TODO: image for aloha

**Node:** You will be asked the path of the configuration file, you can press enter to use the default one.

- Repeat the same steps for the Leader arm:

```bash
python ./examples/aloha/configure.py --port /dev/ttyUSB1 --leader --left # (or right)
```

**Note:** change `/dev/ttyUSB1` to the device port you retrieved from the official wizard (like `COM4` on Windows).
**Note:** The configuration tool will disable all torque so you can move the arm freely to the Position 1.
**Node:** You will be asked the path of the configuration file, you can press enter to use the default one.

After following the guide, you should have the following configuration:

TODO: image for aloha

This configuration has to be exported into environment variables inside the graph file. Here is an example of the
configuration:

```YAML
nodes:
- id: aloha-follower
env:
PORT: /dev/ttyUSB0
CONFIG: ../configs/follower.left.json # relative path to `./examples/aloha/configs/follower.json`

- id: aloha-to-aloha
env:
LEADER_CONTROL: ../configs/leader.left.json
FOLLOWER_CONTROL: ../configs/follower.left.json
```

## Acknowledgement

This work is inspired from [tonyzhaozh/aloha](https://github.com/tonyzhaozh/aloha) and we're trying to bring perfornance
improvement.

## License

This library is licensed under the [Apache License 2.0](../../LICENSE).

+ 87
- 0
examples/aloha/INSTALLATION.md View File

@@ -0,0 +1,87 @@
# Dora pipeline Robots

Aloha is a bi manual robot that can be teleoperated using a similar arm. This repository contains
the Dora pipeline to manipulate arms, cameras, and record/replay episodes with LeRobot.

## Installation

Dataflow-oriented robotic application (Dora) is a framework that makes creation of robotic applications fast and simple.
See [Dora repository](https://github.com/dora-rs/dora).

**Please read the instructions carefully before installing the required software and environment to run the robot.**

You must install Dora before attempting to run theAloha Robot pipeline. Here are the steps to install Dora:

- Install Rust by following the instructions at [Rustup](https://rustup.rs/). (You may need to install Visual Studio C++
build tools on Windows.)
- Install Dora by running the following command:

```bash
cargo install dora-cli
```

Now you're ready to run Rust dataflow applications! We decided to only make Python dataflow for Aloha Robot, so
you may need to setup your Python environment:

- Install Python 3.12 or later by following the instructions at [Python](https://www.python.org/downloads/).
- Clone this repository by running the following command:

```bash
git clone https://github.com/dora-rs/dora
```

- Open a bash terminal and navigate to the repository by running the following command:

```bash
cd dora
```

- Create a virtual environment by running the following command (you can find where is all your pythons executable with
the command `whereis python3` on Linux, on default for Windows it's located
in `C:\Users\<User>\AppData\Local\Programs\Python\Python3.12\python.exe)`):

```bash
path_to_your_python3_executable -m venv venv
```

- Activate the virtual environment and install the required Python packages by running the following command:

```bash
# If you are using a custom environment, you will have to activate it before running the command
source [your_custom_env_bin]/activate

# If you followed the installation instructions, you can run the following command
source venv/bin/activate # On Linux
source venv/Scripts/activate # On Windows bash
venv\Scripts\activate.bat # On Windows cmd
venv\Scripts\activate.ps1 # On Windows PowerShell

pip install -r examples/so100/requirements.txt
```

If you want to install the required Python packages in development mode, you can run the following command, but you will
have to avoid using `dora build` during execution procedure:

```bash
pip install -r examples/aloha/development.txt # You **MUST** be inside dora to run this command
```

**Note**: You're totally free to use your own Python environment, a Conda environment, or whatever you prefer, you will
have to activate
your custom python environment before running `dora up && dora start [graph].yml`.

In order to record episodes, you need ffmpeg installed on your system. You can download it from
the [official website](https://ffmpeg.org/download.html). If you're on Windows, you can download the latest build
from [here](https://www.gyan.dev/ffmpeg/builds/). You can
extract the zip file and add the `bin` folder to your PATH.
If you're on Linux, you can install ffmpeg using the package manager of your distribution. (
e.g `sudo apt install ffmpeg` on Ubuntu, `brew install ffmpeg` on macOS)

## Acknowledgement

This work is inspired from [tonyzhaozh/aloha](https://github.com/tonyzhaozh/aloha) and we're trying to bring perfornance
improvement.

## License

This library is licensed under the [Apache License 2.0](../../LICENSE).

+ 42
- 0
examples/aloha/README.md View File

@@ -0,0 +1,42 @@
# Dora pipeline Robots

Aloha is a bi manual robot that can be teleoperated using a similar arm. This repository contains
the Dora pipeline to manipulate arms, cameras, and record/replay episodes with LeRobot.

## Assembling

Check the [ASSEMBLING.md](ASSEMBLING.md) file for instructions on how to assemble the robot.

## Installation

Check the [INSTALLATION.md](INSTALLATION.md) file for instructions on how to install the required software and
environment
to run the robot.

## Configuring

Check the [CONFIGURING.md](CONFIGURING.md) file for instructions on how to configure the robot to record episodes for
LeRobot and teleoperate the robot.

## Recording

It's probably better to check the [examples](#examples) below before trying to record episodes. It will give you a
better
understanding of how Dora works.

Check the [RECORDING.md](RECORDING.md) file for instructions on how to record episodes for LeRobot.

## Examples

There are also some other example applications in the `graphs` folder. Have fun!

Here is a list of the available examples:

## Acknowledgement

This work is inspired from [tonyzhaozh/aloha](https://github.com/tonyzhaozh/aloha) and we're trying to bring perfornance
improvement.

## License

This library is licensed under the [Apache License 2.0](../../LICENSE).

+ 17
- 0
examples/aloha/RECORDING.md View File

@@ -0,0 +1,17 @@
# Dora pipeline Robots

Aloha is a bi manual robot that can be teleoperated using a similar arm. This repository contains
the Dora pipeline to manipulate arms, cameras, and record/replay episodes with LeRobot.

## Recording

There is not a recording section for the robot at the moment.

## Acknowledgement

This work is inspired from [tonyzhaozh/aloha](https://github.com/tonyzhaozh/aloha) and we're trying to bring perfornance
improvement.

## License

This library is licensed under the [Apache License 2.0](../../LICENSE).

+ 13
- 0
examples/aloha/benchmark/python/README.md View File

@@ -0,0 +1,13 @@
# Python API Powered Aloha inspired by [AlexanderKoch-Koch/low_cost_robot](https://github.com/AlexanderKoch-Koch/low_cost_robot)

## Getting started

Modify port and servo motor configuration.

```bash
python python teleoperate_real_robot.py
```

## Results

On my run, I get about 50Hz teleoperation although the python calls does not block for as long.

+ 535
- 0
examples/aloha/benchmark/python/dynamixel.py View File

@@ -0,0 +1,535 @@
"""Module for managing Dynamixel servo communication and control.

This module provides functionality for communicating with and controlling Dynamixel servos
through a serial interface. It includes methods for reading and writing servo parameters,
managing operating modes, and handling joint positions and velocities.
"""

from __future__ import annotations

import enum
import math
import os
import time
from dataclasses import dataclass

from dynamixel_sdk import * # Uses Dynamixel SDK library


class ReadAttribute(enum.Enum):
"""Enumeration for Dynamixel servo read attributes."""

TEMPERATURE = 146
VOLTAGE = 145
VELOCITY = 128
POSITION = 132
CURRENT = 126
PWM = 124
HARDWARE_ERROR_STATUS = 70
HOMING_OFFSET = 20
BAUDRATE = 8


class OperatingMode(enum.Enum):
"""Enumeration for Dynamixel servo operating modes."""

VELOCITY = 1
POSITION = 3
CURRENT_CONTROLLED_POSITION = 5
PWM = 16
UNKNOWN = -1


class Dynamixel:
"""Class for managing communication with Dynamixel servos."""

ADDR_TORQUE_ENABLE = 64
ADDR_GOAL_POSITION = 116
ADDR_VELOCITY_LIMIT = 44
ADDR_GOAL_PWM = 100
OPERATING_MODE_ADDR = 11
POSITION_I = 82
POSITION_P = 84
ADDR_ID = 7

@dataclass
class Config:
"""Configuration class for Dynamixel servo settings."""

def instantiate(self):
"""Create a new Dynamixel instance with this configuration."""
return Dynamixel(self)

baudrate: int = 57600
protocol_version: float = 2.0
device_name: str = "" # /dev/tty.usbserial-1120'
dynamixel_id: int = 1

def __init__(self, config: Config):
"""Initialize the Dynamixel servo connection.

Args:
config: Configuration object containing connection settings.

"""
self.config = config
self.connect()

def connect(self):
"""Establish connection with the Dynamixel servo."""
if self.config.device_name == "":
for port_name in os.listdir("/dev"):
if "ttyUSB" in port_name or "ttyACM" in port_name:
self.config.device_name = "/dev/" + port_name
print(f"using device {self.config.device_name}")
self.portHandler = PortHandler(self.config.device_name)
# self.portHandler.LA
self.packetHandler = PacketHandler(self.config.protocol_version)
if not self.portHandler.openPort():
raise Exception(f"Failed to open port {self.config.device_name}")

if not self.portHandler.setBaudRate(self.config.baudrate):
raise Exception(f"failed to set baudrate to {self.config.baudrate}")

# self.operating_mode = OperatingMode.UNKNOWN
# self.torque_enabled = False
# self._disable_torque()

self.operating_modes = [None for _ in range(32)]
self.torque_enabled = [None for _ in range(32)]
return True

def disconnect(self):
"""Close the connection with the Dynamixel servo."""
self.portHandler.closePort()

def set_goal_position(self, motor_id, goal_position):
"""Set the goal position for the specified servo.

Args:
motor_id: ID of the servo to control.
goal_position: Target position value.

"""
# if self.operating_modes[motor_id] is not OperatingMode.POSITION:
# self._disable_torque(motor_id)
# self.set_operating_mode(motor_id, OperatingMode.POSITION)

# if not self.torque_enabled[motor_id]:
# self._enable_torque(motor_id)

# self._enable_torque(motor_id)
dxl_comm_result, dxl_error = self.packetHandler.write4ByteTxRx(
self.portHandler, motor_id, self.ADDR_GOAL_POSITION, goal_position,
)
# self._process_response(dxl_comm_result, dxl_error)
# print(f'set position of motor {motor_id} to {goal_position}')

def set_pwm_value(self, motor_id: int, pwm_value, tries=3):
"""Set the PWM value for the specified servo.

Args:
motor_id: ID of the servo to control.
pwm_value: PWM value to set.
tries: Number of attempts to set the value.

"""
if self.operating_modes[motor_id] is not OperatingMode.PWM:
self._disable_torque(motor_id)
self.set_operating_mode(motor_id, OperatingMode.PWM)

if not self.torque_enabled[motor_id]:
self._enable_torque(motor_id)
# print(f'enabling torque')
dxl_comm_result, dxl_error = self.packetHandler.write2ByteTxRx(
self.portHandler, motor_id, self.ADDR_GOAL_PWM, pwm_value,
)
# self._process_response(dxl_comm_result, dxl_error)
# print(f'set pwm of motor {motor_id} to {pwm_value}')
if dxl_comm_result != COMM_SUCCESS:
if tries <= 1:
raise ConnectionError(
f"dxl_comm_result: {self.packetHandler.getTxRxResult(dxl_comm_result)}",
)
print(
f"dynamixel pwm setting failure trying again with {tries - 1} tries",
)
self.set_pwm_value(motor_id, pwm_value, tries=tries - 1)
elif dxl_error != 0:
print(f"dxl error {dxl_error}")
raise ConnectionError(
f"dynamixel error: {self.packetHandler.getTxRxResult(dxl_error)}",
)

def read_temperature(self, motor_id: int):
"""Read the temperature of the specified servo.

Args:
motor_id: ID of the servo to read from.

Returns:
The current temperature value.

"""
return self._read_value(motor_id, ReadAttribute.TEMPERATURE, 1)

def read_velocity(self, motor_id: int):
"""Read the current velocity of the specified servo.

Args:
motor_id: ID of the servo to read from.

Returns:
The current velocity value.

"""
pos = self._read_value(motor_id, ReadAttribute.VELOCITY, 4)
if pos > 2**31:
pos -= 2**32
# print(f'read position {pos} for motor {motor_id}')
return pos

def read_position(self, motor_id: int):
"""Read the current position of the specified servo.

Args:
motor_id: ID of the servo to read from.

Returns:
The current position value.

"""
pos = self._read_value(motor_id, ReadAttribute.POSITION, 4)
if pos > 2**31:
pos -= 2**32
# print(f'read position {pos} for motor {motor_id}')
return pos

def read_position_degrees(self, motor_id: int) -> float:
"""Read the current position of the specified servo in degrees.

Args:
motor_id: ID of the servo to read from.

Returns:
The current position in degrees.

"""
return (self.read_position(motor_id) / 4096) * 360

def read_position_radians(self, motor_id: int) -> float:
"""Read the current position of the specified servo in radians.

Args:
motor_id: ID of the servo to read from.

Returns:
The current position in radians.

"""
return (self.read_position(motor_id) / 4096) * 2 * math.pi

def read_current(self, motor_id: int):
"""Read the current value of the specified servo.

Args:
motor_id: ID of the servo to read from.

Returns:
The current value.

"""
current = self._read_value(motor_id, ReadAttribute.CURRENT, 2)
if current > 2**15:
current -= 2**16
return current

def read_present_pwm(self, motor_id: int):
"""Read the current PWM value of the specified servo.

Args:
motor_id: ID of the servo to read from.

Returns:
The current PWM value.

"""
return self._read_value(motor_id, ReadAttribute.PWM, 2)

def read_hardware_error_status(self, motor_id: int):
"""Read the hardware error status of the specified servo.

Args:
motor_id: ID of the servo to read from.

Returns:
The hardware error status value.

"""
return self._read_value(motor_id, ReadAttribute.HARDWARE_ERROR_STATUS, 1)

def set_id(self, old_id, new_id, use_broadcast_id: bool = False):
"""Set the ID of the Dynamixel servo.

Args:
old_id: Current ID of the servo.
new_id: New ID to set.
use_broadcast_id: If True, set IDs of all connected servos.
If False, change only servo with self.config.id

"""
if use_broadcast_id:
current_id = 254
else:
current_id = old_id
dxl_comm_result, dxl_error = self.packetHandler.write1ByteTxRx(
self.portHandler, current_id, self.ADDR_ID, new_id,
)
self._process_response(dxl_comm_result, dxl_error, old_id)
self.config.id = id

def _enable_torque(self, motor_id):
"""Enable torque for the specified servo.

Args:
motor_id: ID of the servo to control.

"""
dxl_comm_result, dxl_error = self.packetHandler.write1ByteTxRx(
self.portHandler, motor_id, self.ADDR_TORQUE_ENABLE, 1,
)
self._process_response(dxl_comm_result, dxl_error, motor_id)
self.torque_enabled[motor_id] = True

def _disable_torque(self, motor_id):
"""Disable torque for the specified servo.

Args:
motor_id: ID of the servo to control.

"""
dxl_comm_result, dxl_error = self.packetHandler.write1ByteTxRx(
self.portHandler, motor_id, self.ADDR_TORQUE_ENABLE, 0,
)
self._process_response(dxl_comm_result, dxl_error, motor_id)
self.torque_enabled[motor_id] = False

def _process_response(self, dxl_comm_result: int, dxl_error: int, motor_id: int):
"""Process the response from a servo communication.

Args:
dxl_comm_result: Communication result.
dxl_error: Error value.
motor_id: ID of the servo that was communicated with.

Raises:
ConnectionError: If communication failed.
RuntimeError: If servo reported an error.

"""
if dxl_comm_result != COMM_SUCCESS:
raise ConnectionError(
f"dxl_comm_result for motor {motor_id}: {self.packetHandler.getTxRxResult(dxl_comm_result)}",
)
if dxl_error != 0:
print(f"dxl error {dxl_error}")
raise ConnectionError(
f"dynamixel error for motor {motor_id}: {self.packetHandler.getTxRxResult(dxl_error)}",
)

def set_operating_mode(self, motor_id: int, operating_mode: OperatingMode):
"""Set the operating mode for the specified servo.

Args:
motor_id: ID of the servo to control.
operating_mode: Operating mode to set.

"""
dxl_comm_result, dxl_error = self.packetHandler.write2ByteTxRx(
self.portHandler, motor_id, self.OPERATING_MODE_ADDR, operating_mode.value,
)
self._process_response(dxl_comm_result, dxl_error, motor_id)
self.operating_modes[motor_id] = operating_mode

def set_pwm_limit(self, motor_id: int, limit: int):
"""Set the PWM limit for the specified servo.

Args:
motor_id: ID of the servo to control.
limit: PWM limit value to set.

"""
dxl_comm_result, dxl_error = self.packetHandler.write2ByteTxRx(
self.portHandler, motor_id, 36, limit,
)
self._process_response(dxl_comm_result, dxl_error, motor_id)

def set_velocity_limit(self, motor_id: int, velocity_limit):
"""Set the velocity limit for the specified servo.

Args:
motor_id: ID of the servo to control.
velocity_limit: Velocity limit value to set.

"""
dxl_comm_result, dxl_error = self.packetHandler.write4ByteTxRx(
self.portHandler, motor_id, self.ADDR_VELOCITY_LIMIT, velocity_limit,
)
self._process_response(dxl_comm_result, dxl_error, motor_id)

def set_p(self, motor_id: int, p: int):
"""Set the position P gain for the specified servo.

Args:
motor_id (int): The ID of the servo motor
p (int): The position P gain value to set

"""
dxl_comm_result, dxl_error = self.packetHandler.write2ByteTxRx(
self.portHandler, motor_id, self.POSITION_P, p,
)
self._process_response(dxl_comm_result, dxl_error, motor_id)

def set_i(self, motor_id: int, i: int):
"""Set the position I gain for the specified servo.

Args:
motor_id (int): The ID of the servo motor
i (int): The position I gain value to set

"""
dxl_comm_result, dxl_error = self.packetHandler.write2ByteTxRx(
self.portHandler, motor_id, self.POSITION_I, i,
)
self._process_response(dxl_comm_result, dxl_error, motor_id)

def read_home_offset(self, motor_id: int):
"""Read the home offset of the specified servo.

Args:
motor_id: ID of the servo to read from.

Returns:
The home offset value.

"""
self._disable_torque(motor_id)
# dxl_comm_result, dxl_error = self.packetHandler.write4ByteTxRx(self.portHandler, motor_id,
# ReadAttribute.HOMING_OFFSET.value, home_position)
home_offset = self._read_value(motor_id, ReadAttribute.HOMING_OFFSET, 4)
# self._process_response(dxl_comm_result, dxl_error)
self._enable_torque(motor_id)
return home_offset

def set_home_offset(self, motor_id: int, home_position: int):
"""Set the home offset for the specified servo.

Args:
motor_id: ID of the servo to control.
home_position: Home position value to set.

"""
self._disable_torque(motor_id)
dxl_comm_result, dxl_error = self.packetHandler.write4ByteTxRx(
self.portHandler, motor_id, ReadAttribute.HOMING_OFFSET.value, home_position,
)
self._process_response(dxl_comm_result, dxl_error, motor_id)
self._enable_torque(motor_id)

def set_baudrate(self, motor_id: int, baudrate):
"""Set the baudrate for the specified servo.

Args:
motor_id: ID of the servo to control.
baudrate: Baudrate value to set.

"""
# translate baudrate into dynamixel baudrate setting id
if baudrate == 57600:
baudrate_id = 1
elif baudrate == 1_000_000:
baudrate_id = 3
elif baudrate == 2_000_000:
baudrate_id = 4
elif baudrate == 3_000_000:
baudrate_id = 5
elif baudrate == 4_000_000:
baudrate_id = 6
else:
raise Exception("baudrate not implemented")

self._disable_torque(motor_id)
dxl_comm_result, dxl_error = self.packetHandler.write1ByteTxRx(
self.portHandler, motor_id, ReadAttribute.BAUDRATE.value, baudrate_id,
)
self._process_response(dxl_comm_result, dxl_error, motor_id)

def _read_value(self, motor_id, attribute: ReadAttribute, num_bytes: int, tries=10):
"""Read a value from the specified servo address.

Args:
motor_id: ID of the servo to read from.
attribute: Attribute to read.
num_bytes: Number of bytes to read.
tries: Number of attempts to read the value.

Returns:
The read value.

"""
try:
if num_bytes == 1:
value, dxl_comm_result, dxl_error = self.packetHandler.read1ByteTxRx(
self.portHandler, motor_id, attribute.value,
)
elif num_bytes == 2:
value, dxl_comm_result, dxl_error = self.packetHandler.read2ByteTxRx(
self.portHandler, motor_id, attribute.value,
)
elif num_bytes == 4:
value, dxl_comm_result, dxl_error = self.packetHandler.read4ByteTxRx(
self.portHandler, motor_id, attribute.value,
)
except Exception:
if tries == 0:
raise Exception
return self._read_value(motor_id, attribute, num_bytes, tries=tries - 1)
if dxl_comm_result != COMM_SUCCESS:
if tries <= 1:
# print("%s" % self.packetHandler.getTxRxResult(dxl_comm_result))
raise ConnectionError(
f"dxl_comm_result {dxl_comm_result} for servo {motor_id} value {value}",
)
print(
f"dynamixel read failure for servo {motor_id} trying again with {tries - 1} tries",
)
time.sleep(0.02)
return self._read_value(motor_id, attribute, num_bytes, tries=tries - 1)
if (
dxl_error != 0
): # # print("%s" % self.packetHandler.getRxPacketError(dxl_error))
# raise ConnectionError(f'dxl_error {dxl_error} binary ' + "{0:b}".format(37))
if tries == 0 and dxl_error != 128:
raise Exception(
f"Failed to read value from motor {motor_id} error is {dxl_error}",
)
return self._read_value(motor_id, attribute, num_bytes, tries=tries - 1)
return value

def set_home_position(self, motor_id: int):
"""Set the home position for the specified servo.

Args:
motor_id: ID of the servo to control.

"""
print(f"setting home position for motor {motor_id}")
self.set_home_offset(motor_id, 0)
current_position = self.read_position(motor_id)
print(f"position before {current_position}")
self.set_home_offset(motor_id, -current_position)
# dynamixel.set_home_offset(motor_id, -4096)
# dynamixel.set_home_offset(motor_id, -4294964109)
current_position = self.read_position(motor_id)
# print(f'signed position {current_position - 2** 32}')
print(f"position after {current_position}")

+ 229
- 0
examples/aloha/benchmark/python/robot.py View File

@@ -0,0 +1,229 @@
"""Module for controlling robot hardware and movements.

This module provides functionality for controlling robot hardware components,
including servo motors and various control modes.
"""

import time
from enum import Enum, auto
from typing import Union

import numpy as np
from dynamixel import OperatingMode, ReadAttribute
from dynamixel_sdk import (
DXL_HIBYTE,
DXL_HIWORD,
DXL_LOBYTE,
DXL_LOWORD,
GroupSyncRead,
GroupSyncWrite,
)


class MotorControlType(Enum):
"""Enumeration of different motor control modes.
Defines the various control modes available for the robot's motors,
including PWM control, position control, and disabled states.
"""

PWM = auto()
POSITION_CONTROL = auto()
DISABLED = auto()
UNKNOWN = auto()


class Robot:
"""A class for controlling robot hardware components.
This class provides methods for controlling robot servos, managing different
control modes, and reading sensor data.
"""

# def __init__(self, device_name: str, baudrate=1_000_000, servo_ids=[1, 2, 3, 4, 5]):
def __init__(self, dynamixel, baudrate=1_000_000, servo_ids=[1, 2, 3, 4, 5]):
"""Initialize the robot controller.

Args:
dynamixel: Dynamixel controller instance
baudrate: Communication baudrate for servo control
servo_ids: List of servo IDs to control

"""
self.servo_ids = servo_ids
self.dynamixel = dynamixel
# self.dynamixel = Dynamixel.Config(baudrate=baudrate, device_name=device_name).instantiate()
self.position_reader = GroupSyncRead(
self.dynamixel.portHandler,
self.dynamixel.packetHandler,
ReadAttribute.POSITION.value,
4,
)
for id in self.servo_ids:
self.position_reader.addParam(id)

self.velocity_reader = GroupSyncRead(
self.dynamixel.portHandler,
self.dynamixel.packetHandler,
ReadAttribute.VELOCITY.value,
4,
)
for id in self.servo_ids:
self.velocity_reader.addParam(id)

self.pos_writer = GroupSyncWrite(
self.dynamixel.portHandler,
self.dynamixel.packetHandler,
self.dynamixel.ADDR_GOAL_POSITION,
4,
)
for id in self.servo_ids:
self.pos_writer.addParam(id, [2048])

self.pwm_writer = GroupSyncWrite(
self.dynamixel.portHandler,
self.dynamixel.packetHandler,
self.dynamixel.ADDR_GOAL_PWM,
2,
)
for id in self.servo_ids:
self.pwm_writer.addParam(id, [2048])
self._disable_torque()
self.motor_control_state = MotorControlType.DISABLED

def read_position(self, tries=2):
"""Read the current joint positions of the robot.

Args:
tries: Maximum number of attempts to read positions

Returns:
list: Joint positions in range [0, 4096]

"""
result = self.position_reader.txRxPacket()
if result != 0:
if tries > 0:
return self.read_position(tries=tries - 1)
print("failed to read position!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
positions = []
for id in self.servo_ids:
position = self.position_reader.getData(id, ReadAttribute.POSITION.value, 4)
if position > 2**31:
position -= 2**32
positions.append(position)
return positions

def read_velocity(self):
"""Read the current joint velocities of the robot.

Returns:
list: Current joint velocities

"""
self.velocity_reader.txRxPacket()
velocties = []
for id in self.servo_ids:
velocity = self.velocity_reader.getData(id, ReadAttribute.VELOCITY.value, 4)
if velocity > 2**31:
velocity -= 2**32
velocties.append(velocity)
return velocties

def set_goal_pos(self, action):
"""Set goal positions for the robot joints.

Args:
action: List or numpy array of target joint positions in range [0, 4096]

"""
if self.motor_control_state is not MotorControlType.POSITION_CONTROL:
self._set_position_control()
for i, motor_id in enumerate(self.servo_ids):
data_write = [
DXL_LOBYTE(DXL_LOWORD(action[i])),
DXL_HIBYTE(DXL_LOWORD(action[i])),
DXL_LOBYTE(DXL_HIWORD(action[i])),
DXL_HIBYTE(DXL_HIWORD(action[i])),
]
self.pos_writer.changeParam(motor_id, data_write)

self.pos_writer.txPacket()

def set_pwm(self, action):
"""Set PWM values for the servos.

Args:
action: List or numpy array of PWM values in range [0, 885]

"""
if self.motor_control_state is not MotorControlType.PWM:
self._set_pwm_control()
for i, motor_id in enumerate(self.servo_ids):
data_write = [
DXL_LOBYTE(DXL_LOWORD(action[i])),
DXL_HIBYTE(DXL_LOWORD(action[i])),
]
self.pwm_writer.changeParam(motor_id, data_write)

self.pwm_writer.txPacket()

def set_trigger_torque(self):
"""Set a constant torque for the last servo in the chain.
This is useful for the trigger of the leader arm.
"""
self.dynamixel._enable_torque(self.servo_ids[-1])
self.dynamixel.set_pwm_value(self.servo_ids[-1], 200)

def limit_pwm(self, limit: Union[int, list, np.ndarray]):
"""Limit the PWM values for the servos in position control.

Args:
limit: PWM limit value in range 0-885

"""
if isinstance(limit, int):
limits = [
limit,
] * 5
else:
limits = limit
self._disable_torque()
for motor_id, limit in zip(self.servo_ids, limits):
self.dynamixel.set_pwm_limit(motor_id, limit)
self._enable_torque()

def _disable_torque(self):
print(f"disabling torque for servos {self.servo_ids}")
for motor_id in self.servo_ids:
self.dynamixel._disable_torque(motor_id)

def _enable_torque(self):
print(f"enabling torque for servos {self.servo_ids}")
for motor_id in self.servo_ids:
self.dynamixel._enable_torque(motor_id)

def _set_pwm_control(self):
self._disable_torque()
for motor_id in self.servo_ids:
self.dynamixel.set_operating_mode(motor_id, OperatingMode.PWM)
self._enable_torque()
self.motor_control_state = MotorControlType.PWM

def _set_position_control(self):
self._disable_torque()
for motor_id in self.servo_ids:
self.dynamixel.set_operating_mode(motor_id, OperatingMode.POSITION)
self._enable_torque()
self.motor_control_state = MotorControlType.POSITION_CONTROL


if __name__ == "__main__":
robot = Robot(device_name="/dev/tty.usbmodem57380045631")
robot._disable_torque()
for _ in range(10000):
s = time.time()
pos = robot.read_position()
elapsed = time.time() - s
print(f"read took {elapsed} pos {pos}")

+ 30
- 0
examples/aloha/benchmark/python/teleoperate_real_robot.py View File

@@ -0,0 +1,30 @@
"""Module for teleoperating a physical robot.

This module provides functionality for controlling a physical robot through
teleoperation, allowing real-time control of robot movements and actions.
"""

from dynamixel import Dynamixel
from robot import Robot

leader_dynamixel = Dynamixel.Config(
baudrate=1_000_000, device_name="/dev/ttyDXL_master_right",
).instantiate()
follower_dynamixel = Dynamixel.Config(
baudrate=1_000_000, device_name="/dev/ttyDXL_puppet_right",
).instantiate()
follower = Robot(follower_dynamixel, servo_ids=[1, 2, 3, 4, 5, 6, 7, 8])
leader = Robot(leader_dynamixel, servo_ids=[1, 2, 3, 4, 5, 6, 7, 8])
# leader.set_trigger_torque()

import time

times_rec = []
times_send = []

while True:
now = time.time()
pos = leader.read_position()
# print(f"Time to rec pos: {(time.time() - now) * 1000}")
follower.set_goal_pos(pos)
print(f"Time to send pos: {(time.time() - now) * 1000}")

+ 103
- 0
examples/aloha/benchmark/ros2/README.md View File

@@ -0,0 +1,103 @@
# `dora-rs` powered ALOHA

## Getting started

```bash
docker run --privileged --name ros2-aloha --network=host -e DISPLAY=${DISPLAY} -v /dev:/dev -v $(pwd):/dora-aloha -it osrf/ros:humble-desktop


## In the container
./dora-aloha/setup_ros2.sh # Run it once
```

## To activate the controller, just do:

```bash
## In the container
ros2 launch ~/interbotix_ws/src/interbotix_ros_manipulators/interbotix_ros_xsarms/interbotix_xsarm_control/launch/xsarm_control.launch.py robot_name:=robot_model_master robot_model:=aloha_wx250s mode_configs:=/dora-aloha/benchmark/ros2/config/master_modes_right.yaml &
ros2 launch ~/interbotix_ws/src/interbotix_ros_manipulators/interbotix_ros_xsarms/interbotix_xsarm_control/launch/xsarm_control.launch.py robot_name:=robot_model_puppet robot_model:=vx300s mode_configs:=/dora-aloha/benchmark/ros2/config/puppet_modes_right.yaml &

## In case you want to restart the controller, just do:
pkill -f robot
```

## To run dora

Outside of the controller docker:

### Setup

```bash
## Setup
git clone https://github.com/haixuanTao/ament_prefix_path.git $HOME/ros2-ament-prefix-path
export AMENT_PREFIX_PATH=$HOME/ros2-ament-prefix-path # <- holding ros2 message deserialization
```

### Start

```bash
## Start
dora up
dora start dataflow.yml --attach
```

## Result

I'm able to get about 100 Hz for teleoperation.

The improvement probably comes from the ros2 read/write written in C++.

## Hardware Installation

- To check if the robot is connected, install dynamixel wizard [here](https://emanual.robotis.com/docs/en/software/dynamixel/dynamixel_wizard2/)
- Dynamixel wizard is a very helpful debugging tool that connects to individual motors of the robot. It allows
things such as rebooting the motor (very useful!), torque on/off, and sending commands.
However, it has no knowledge about the kinematics of the robot, so be careful about collisions.
The robot _will_ collapse if motors are torque off i.e. there is no automatically engaged brakes in joints.
- Open Dynamixel wizard, go into `options` and select:
- Protocol 2.0
- All ports
- 1000000 bps
- ID range from 0-10
- Note: repeat above everytime before you scan.
- Then hit `Scan`. There should be 4 devices showing up, each with 9 motors.
- One issue that arises is the port each robot binds to can change over time, e.g. a robot that
is initially `ttyUSB0` might suddenly become `ttyUSB5`. To resolve this, we bind each robot to a fixed symlink
port with the following mapping:
- `ttyDXL_master_right`: right master robot (master: the robot that the operator would be holding)
- `ttyDXL_puppet_right`: right puppet robot (puppet: the robot that performs the task)
- `ttyDXL_master_left`: left master robot
- `ttyDXL_puppet_left`: left puppet robot
- Take `ttyDXL_master_right`: right master robot as an example:

1. Find the port that the right master robot is currently binding to, e.g. `ttyUSB0`
2. run `udevadm info --name=/dev/ttyUSB0 --attribute-walk | grep serial` to obtain the serial number. Use the first one that shows up, the format should look similar to `FT6S4DSP`.
3. `sudo vim /etc/udev/rules.d/99-fixed-interbotix-udev.rules` and add the following line:

SUBSYSTEM=="tty", ATTRS{serial}=="<serial number here>", ENV{ID_MM_DEVICE_IGNORE}="1", ATTR{device/latency_timer}="1", SYMLINK+="ttyDXL_master_right"

4. This will make sure the right master robot is _always_ binding to `ttyDXL_master_right`
5. Repeat with the rest of 3 arms.

- To apply the changes, run `sudo udevadm control --reload && sudo udevadm trigger`
- If successful, you should be able to find `ttyDXL*` in your `/dev`

## Hardware troubleshoot

- In case you're having `xsarm_control.launch.py` that is not launching for the reason: ` Can't find Dynamixel ID 'X'`, one of the issue I faced in my demo was Overload Error(OL). The fix was to go on Dynamixel Wizard, click on the motor ID `X` and clicking the reboot button the right side of the window. This error happens when the torque is too high. I had the issue when I try to set a closing position for the gripper that did not take into account the fingers.

## Support Matrix

| | dora-aloha |
| ----------------------------- | --------------------- |
| **Supported Platforms (x86)** | Windows, macOS, Linux |
| **Supported Platforms (ARM)** | Linux(RPI4) |

## Documentation

https://github.com/Interbotix/interbotix_ros_toolboxes/blob/humble/interbotix_xs_toolbox/interbotix_xs_modules/interbotix_xs_modules/xs_robot/core.py
https://github.com/Interbotix/interbotix_ros_toolboxes/blob/c187bcea89b60391244bb19943ebd78f770aa975/interbotix_xs_toolbox/interbotix_xs_modules/interbotix_xs_modules/xs_robot/core.py#L380-L398

## Acknowledgement

This work is heavily inspired from [tonyzhaozh/aloha](https://github.com/tonyzhaozh/aloha) and we're trying to bring perfornance improvement.

+ 9
- 0
examples/aloha/benchmark/ros2/config/master_modes_left.yaml View File

@@ -0,0 +1,9 @@
port: /dev/ttyDXL_master_left

groups:
arm:
torque_enable: false

singles:
gripper:
torque_enable: false

+ 9
- 0
examples/aloha/benchmark/ros2/config/master_modes_right.yaml View File

@@ -0,0 +1,9 @@
port: /dev/ttyDXL_master_right

groups:
arm:
torque_enable: false

singles:
gripper:
torque_enable: false

+ 17
- 0
examples/aloha/benchmark/ros2/config/puppet_modes_left.yaml View File

@@ -0,0 +1,17 @@
port: /dev/ttyDXL_puppet_left

groups:
arm:
operating_mode: position
profile_type: velocity
profile_velocity: 0
profile_acceleration: 0
torque_enable: true

singles:
gripper:
operating_mode: linear_position
profile_type: velocity
profile_velocity: 0
profile_acceleration: 0
torque_enable: true

+ 17
- 0
examples/aloha/benchmark/ros2/config/puppet_modes_right.yaml View File

@@ -0,0 +1,17 @@
port: /dev/ttyDXL_puppet_right

groups:
arm:
operating_mode: position
profile_type: velocity
profile_velocity: 0
profile_acceleration: 0
torque_enable: true

singles:
gripper:
operating_mode: linear_position
profile_type: velocity
profile_velocity: 0
profile_acceleration: 0
torque_enable: true

+ 4
- 0
examples/aloha/benchmark/ros2/dataflow.yml View File

@@ -0,0 +1,4 @@
nodes:
- id: control
custom:
source: teleop.py

+ 22
- 0
examples/aloha/benchmark/ros2/setup_ros2.sh View File

@@ -0,0 +1,22 @@
# setup ros2

## Source: https://docs.trossenrobotics.com/interbotix_xsarms_docs/ros_interface/ros2/software_setup.html

sudo apt update

sudo apt install curl vim git -y

curl 'https://raw.githubusercontent.com/Interbotix/interbotix_ros_manipulators/humble/interbotix_ros_xsarms/install/amd64/xsarm_amd64_install.sh' > xsarm_amd64_install.sh

source /opt/ros/$ROS_DISTRO/setup.bash

chmod +x xsarm_amd64_install.sh

./xsarm_amd64_install.sh -d humble -n

source /opt/ros/$ROS_DISTRO/setup.bash

source ~/interbotix_ws/install/setup.bash

ros2 pkg list | grep interbotix


+ 95
- 0
examples/aloha/benchmark/ros2/teleop.py View File

@@ -0,0 +1,95 @@
"""Module for ROS2-based robot teleoperation.

This module provides functionality for controlling robots through ROS2,
enabling teleoperation and real-time control of robot movements.
"""

import dora
import numpy as np
import pyarrow as pa
from dora import Node

ros2_context = dora.experimental.ros2_bridge.Ros2Context()
ros2_node = ros2_context.new_node(
"robot_model_master",
"/dora",
dora.experimental.ros2_bridge.Ros2NodeOptions(rosout=True),
)

# Define a ROS2 QOS
topic_qos = dora.experimental.ros2_bridge.Ros2QosPolicies(
reliable=True, max_blocking_time=0.1,
)

# Create a publisher to cmd_vel topic
puppet_arm_command = ros2_node.create_publisher(
ros2_node.create_topic(
"/robot_model_puppet/commands/joint_group",
"interbotix_xs_msgs/JointGroupCommand",
topic_qos,
),
)

gripper_command = ros2_node.create_publisher(
ros2_node.create_topic(
"/robot_model_puppet/commands/joint_single",
"interbotix_xs_msgs/JointSingleCommand",
topic_qos,
),
)
# Create a listener to pose topic
master_state = ros2_node.create_subscription(
ros2_node.create_topic(
"/robot_model_master/joint_states", "sensor_msgs/JointState", topic_qos,
),
)

# Create a dora node
dora_node = Node()

# Listen for both stream on the same loop as Python does not handle well multiprocessing
dora_node.merge_external_events(master_state)

PUPPET_GRIPPER_MAX = 0.115
PUPPET_GRIPPER_MIN = 0.0965

MASTER_GRIPPER_MAX = 0.8
MASTER_GRIPPER_MIN = -0.1

RATIO = (PUPPET_GRIPPER_MAX - PUPPET_GRIPPER_MIN) / (
MASTER_GRIPPER_MAX - MASTER_GRIPPER_MIN
)

for event in dora_node:
event_kind = event["kind"]

# ROS2 Event
if event_kind == "external":
pose = event.inner()[0]

values = np.array(pose["position"].values, dtype=np.float32)
values[6] = np.clip(
(values[6] - MASTER_GRIPPER_MIN) * RATIO + PUPPET_GRIPPER_MIN,
PUPPET_GRIPPER_MIN,
PUPPET_GRIPPER_MAX,
)
gripper_command.publish(
pa.array(
[
{
"name": "gripper",
"cmd": np.float32(values[6]),
},
],
),
)
puppet_arm_command.publish(
pa.array(
[
{
"name": "arm",
"cmd": values[:6],
},
],
),
)

+ 9
- 0
examples/aloha/benchmark/rust/README.md View File

@@ -0,0 +1,9 @@
# Rust powered ALOHA

## Getting started

Modify port and servo motor configuration for your device within `aloha-teleop`

```bash
cargo run -p aloha-teleop --release
```

+ 141
- 0
examples/aloha/graphs/eval.yml View File

@@ -0,0 +1,141 @@
nodes:
- id: aloha-client
custom:
source: cargo
args: run --release -p aloha-client
inputs:
puppet_goal_position: eval/action
tick: dora/timer/millis/5
outputs:
- puppet_position

- id: plot
custom:
source: ../nodes/plot_node.py
inputs:
image: cam_right_wrist/image
action: eval/action
puppet_position: aloha-client/puppet_position
envs:
IMAGE_WIDTH: 640
IMAGE_HEIGHT: 480

- id: cam_left_wrist
custom:
source: ../nodes/webcam.py
inputs:
tick: dora/timer/millis/33
outputs:
- image
envs:
CAMERA_ID: 2

- id: cam_right_wrist
custom:
source: ../nodes/webcam.py
inputs:
tick: dora/timer/millis/33
outputs:
- image
envs:
CAMERA_ID: 22

- id: cam_low
custom:
source: ../nodes/webcam.py
inputs:
tick: dora/timer/millis/33
outputs:
- image
envs:
CAMERA_ID: 14

- id: cam_high
custom:
source: ../nodes/webcam.py
inputs:
tick: dora/timer/millis/33
outputs:
- image
envs:
CAMERA_ID: 8

- id: eval
custom:
source: python
args: /home/rcadene/dora_lerobot/dora_lerobot/scripts/eval.py -p cadene/aloha_act_no_state_aloha_v2_static_dora_test_wrist_gripper eval.n_episodes=1 eval.batch_size=1 env.episode_length=20000
inputs:
agent_pos: aloha-client/puppet_position
cam_left_wrist: cam_left_wrist/image
cam_right_wrist: cam_right_wrist/image
cam_low: cam_low/image
cam_high: cam_high/image
outputs:
- action

- id: keyboard
custom:
source: ../nodes/keyboard_node.py
inputs:
heartbeat: dora/timer/millis/20
outputs:
- space
- failed

- id: cam_saver_left_wrist
custom:
source: ../nodes/lerobot_webcam_saver.py
inputs:
image: cam_left_wrist/image
record_episode: keyboard/space
outputs:
- saved_image
envs:
CAMERA_NAME: observation.images.cam_left_wrist

- id: cam_saver_right_wrist
custom:
source: ../nodes/lerobot_webcam_saver.py
inputs:
image: cam_right_wrist/image
record_episode: keyboard/space
outputs:
- saved_image
envs:
CAMERA_NAME: observation.images.cam_right_wrist

- id: cam_saver_low
custom:
source: ../nodes/lerobot_webcam_saver.py
inputs:
image: cam_low/image
record_episode: keyboard/space
outputs:
- saved_image
envs:
CAMERA_NAME: observation.images.cam_low

- id: cam_saver_high
custom:
source: ../nodes/lerobot_webcam_saver.py
inputs:
image: cam_high/image
record_episode: keyboard/space
outputs:
- saved_image
envs:
CAMERA_NAME: observation.images.cam_high

- id: dora-record
custom:
build: cargo install --git https://github.com/dora-rs/dora dora-record
source: dora-record
inputs:
action: eval/action
observation.state: aloha-client/puppet_position
episode_index: keyboard/space
failed_episode_index: keyboard/failed
observation.images.cam_left_wrist: cam_saver_left_wrist/saved_image
observation.images.cam_right_wrist: cam_saver_right_wrist/saved_image
observation.images.cam_low: cam_saver_low/saved_image
observation.images.cam_high: cam_saver_high/saved_image

+ 38
- 0
examples/aloha/graphs/gym.yml View File

@@ -0,0 +1,38 @@
nodes:
- id: aloha-client
custom:
source: cargo
args: run -p aloha-client --release
inputs:
puppet_goal_position: dora-gym/action
tick: dora/timer/millis/33
outputs:
- puppet_position

- id: plot
custom:
source: ../nodes/plot_node.py
inputs:
image: cam_left_wrist/image
envs:
IMAGE_WIDTH: 640
IMAGE_HEIGHT: 480

- id: cam_left_wrist
custom:
source: ../nodes/webcam.py
inputs:
tick: dora/timer/millis/33
outputs:
- image
envs:
CAMERA_ID: 2

- id: dora-gym
custom:
source: ../nodes/gym_dora_node.py
inputs:
agent_pos: aloha-client/puppet_position
cam_left_wrist: cam_left_wrist/image
outputs:
- action

+ 162
- 0
examples/aloha/graphs/record_2arms_teleop.yml View File

@@ -0,0 +1,162 @@
nodes:
- id: teleop_right
custom:
source: cargo
args: run --release -p aloha-teleop
inputs:
heartbeat: dora/timer/millis/20
outputs:
- puppet_goal_position
- puppet_position
- puppet_velocity
- puppet_current

- id: dora-record
custom:
build: cargo install --git https://github.com/dora-rs/dora dora-record
source: dora-record
inputs:
action: teleop_right/puppet_goal_position
observation.state: teleop_right/puppet_position
observation.velocity: teleop_right/puppet_velocity
observation.effort: teleop_right/puppet_current
episode_index: keyboard/space
failed_episode_index: keyboard/failed
observation.images.cam_left_wrist: cam_saver_left_wrist/saved_image
observation.images.cam_right_wrist: cam_saver_right_wrist/saved_image
observation.images.cam_low: cam_saver_low/saved_image
observation.images.cam_high: cam_saver_high/saved_image

- id: cam_left_wrist
custom:
source: ../nodes/webcam.py
inputs:
tick: dora/timer/millis/33
outputs:
- image
envs:
CAMERA_ID: 2

- id: cam_right_wrist
custom:
source: ../nodes/webcam.py
inputs:
tick: dora/timer/millis/33
outputs:
- image
envs:
CAMERA_ID: 22

- id: cam_low
custom:
source: ../nodes/webcam.py
inputs:
tick: dora/timer/millis/33
outputs:
- image
envs:
CAMERA_ID: 14

- id: cam_high
custom:
source: ../nodes/webcam.py
inputs:
tick: dora/timer/millis/33
outputs:
- image
envs:
CAMERA_ID: 8

- id: keyboard
custom:
source: ../nodes/keyboard_node.py
inputs:
heartbeat: dora/timer/millis/20
outputs:
- space
- failed

- id: cam_saver_left_wrist
custom:
source: ../nodes/lerobot_webcam_saver.py
inputs:
image: cam_left_wrist/image
record_episode: keyboard/space
outputs:
- saved_image
envs:
CAMERA_NAME: observation.images.cam_left_wrist

- id: cam_saver_right_wrist
custom:
source: ../nodes/lerobot_webcam_saver.py
inputs:
image: cam_right_wrist/image
record_episode: keyboard/space
outputs:
- saved_image
envs:
CAMERA_NAME: observation.images.cam_right_wrist

- id: cam_saver_low
custom:
source: ../nodes/lerobot_webcam_saver.py
inputs:
image: cam_low/image
record_episode: keyboard/space
outputs:
- saved_image
envs:
CAMERA_NAME: observation.images.cam_low

- id: cam_saver_high
custom:
source: ../nodes/lerobot_webcam_saver.py
inputs:
image: cam_high/image
record_episode: keyboard/space
outputs:
- saved_image
envs:
CAMERA_NAME: observation.images.cam_high

# Realsense seems to require specific power that makes it unreliable in our current setup
# - id: cam_left_wrist
# custom:
# source: ../nodes/realsense_node.py
# inputs:
# tick: dora/timer/millis/2
# outputs:
# - image
# envs:
# CAMERA_ID: 128422271614

# - id: cam_right_wrist
# custom:
# source: ../nodes/realsense_node.py
# inputs:
# tick: dora/timer/millis/2
# outputs:
# - image
# envs:
# CAMERA_ID: 128422270109

# - id: cam_low
# custom:
# source: ../nodes/realsense_node.py
# inputs:
# tick: dora/timer/millis/2
# outputs:
# - image
# envs:
# CAMERA_ID: 128422271393

# - id: cam_high
# custom:
# source: ../nodes/realsense_node.py
# inputs:
# tick: dora/timer/millis/2
# outputs:
# - image
# envs:
# CAMERA_ID: 128422271609

+ 32
- 0
examples/aloha/graphs/record_teleop.yml View File

@@ -0,0 +1,32 @@
nodes:
- id: teleop
custom:
source: cargo
args: run -p aloha-teleop --release
outputs:
- puppet_goal_position

- id: dora-record
custom:
build: cargo install --git https://github.com/dora-rs/dora dora-record
source: dora-record
inputs:
puppet_goal_position: teleop/puppet_goal_position

- id: plot
custom:
source: ../nodes/plot_node.py
inputs:
image: webcam/image
position: teleop/puppet_goal_position
envs:
IMAGE_WIDTH: 1280
IMAGE_HEIGHT: 720
- id: webcam
custom:
source: ../nodes/realsense_node.py
inputs:
tick: dora/timer/millis/20
outputs:
- image

+ 61
- 0
examples/aloha/graphs/replay.yml View File

@@ -0,0 +1,61 @@
nodes:
- id: aloha-client
custom:
source: cargo
args: run -p aloha-client --release
inputs:
puppet_goal_position: replay/puppet_goal_position
tick: dora/timer/millis/20
outputs:
- puppet_position

- id: replay
custom:
source: ../nodes/replay.py
inputs:
action: policy/action
outputs:
- puppet_goal_position

- id: whisper
custom:
source: ../nodes/whisper_node.py
inputs:
tick: dora/timer/millis/20
outputs:
- text_llm
- text_policy

- id: llm
operator:
python: ../nodes/llm_op.py
inputs:
text: whisper/text_llm

- id: policy
operator:
python: ../nodes/policy.py
inputs:
speech: whisper/text_policy
outputs:
- action

- id: plot
custom:
source: ../nodes/plot_node.py
inputs:
image: webcam/image
position: aloha-client/puppet_position
text_policy: whisper/text_policy
text_llm: whisper/text_llm
envs:
IMAGE_WIDTH: 1280
IMAGE_HEIGHT: 720
- id: webcam
custom:
source: ../nodes/realsense_node.py
inputs:
tick: dora/timer/millis/20
outputs:
- image

+ 4
- 0
examples/aloha/hardware_config/99-fixed-interbotix-udev.rules View File

@@ -0,0 +1,4 @@
SUBSYSTEM=="tty", ATTRS{serial}=="FT891KPN", ENV{ID_MM_DEVICE_IGNORE}="1", ATTR{device/latency_timer}="1", SYMLINK+="ttyDXL_master_left"
SUBSYSTEM=="tty", ATTRS{serial}=="FT89FM77", ENV{ID_MM_DEVICE_IGNORE}="1", ATTR{device/latency_timer}="1", SYMLINK+="ttyDXL_master_right"
SUBSYSTEM=="tty", ATTRS{serial}=="FT89FM09", ENV{ID_MM_DEVICE_IGNORE}="1", ATTR{device/latency_timer}="1", SYMLINK+="ttyDXL_puppet_left"
SUBSYSTEM=="tty", ATTRS{serial}=="FT891KBG", ENV{ID_MM_DEVICE_IGNORE}="1", ATTR{device/latency_timer}="1", SYMLINK+="ttyDXL_puppet_right"

+ 24
- 0
examples/aloha/hardware_config/99-interbotix-udev.rules View File

@@ -0,0 +1,24 @@
# Place this file in /etc/udev/rules.d/
# Then reload udev by typing 'udevadm control --reload-rules && udevadm trigger'
# Sets up rules to give permanent names to devices

# Allow serial devices to be read by anyone
KERNEL=="ttyUSB*", MODE:="0666"
KERNEL=="ttyACM*", MODE:="0666"
KERNEL=="js*", MODE:="0666"
KERNEL=="video*", MODE:="0666"

# OpenCM9.04C board
SUBSYSTEM=="tty", ATTRS{idVendor}=="fff1", ATTRS{idProduct}=="ff48", ENV{ID_MM_DEVICE_IGNORE}="1", SYMLINK+="ttyDXL"

# U2D2 board (also sets latency timer to 1ms for faster communication)
SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6014", ENV{ID_MM_DEVICE_IGNORE}="1", ATTR{device/latency_timer}="1", SYMLINK+="ttyDXL"

# RPLidar
SUBSYSTEM=="tty", ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="ea60", SYMLINK+="rplidar"

# Kobuki base
SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", ATTRS{serial}=="kobuki*", ATTR{device/latency_timer}="1", MODE:="0666", GROUP:="dialout", SYMLINK+="kobuki"

# LifeCam Cinema
SUBSYSTEM=="video4linux", ATTRS{idVendor}=="045e", ATTRS{idProduct}=="0812", ATTR{index}=="0", SYMLINK+="lifecam"

+ 13
- 0
examples/aloha/nodes/aloha-client/Cargo.toml View File

@@ -0,0 +1,13 @@
[package]
name = "aloha-client"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]

serialport = "4.2.0"
rustypot = { git = "https://github.com/haixuanTao/rustypot", branch = "angular-conversion" }
eyre = "0.6.12"
dora-node-api = "0.3.4"

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save