| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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" | |||
| @@ -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 |  |  |  | | |||
| | [PyRealsense](https://github.com/dora-rs/dora/blob/main/node-hub/dora-pyrealsense) | Linux🆗 <br> Mac🛠️ | Image and depth from Realsense |  |  |  | | |||
| | [Video Capture](https://github.com/dora-rs/dora/blob/main/node-hub/opencv-video-capture) | ✅ | Image stream from Camera |  |  |  | | |||
| ### Peripheral | |||
| | Title | Support | Description | Downloads | License | Release | | |||
| | ----------------------------------------------------------------------------------- | ------- | ------------------------- | ------------------------------------------------------------ | --------------------------------------------------------- | --------------------------------------------------------- | | |||
| | [Keyboard](https://github.com/dora-rs/dora/blob/main/node-hub/dora-keyboard) | ✅ | Keyboard char listener |  |  |  | | |||
| | [Microphone](https://github.com/dora-rs/dora/blob/main/node-hub/dora-microphone) | ✅ | Audio from microphone |  |  |  | | |||
| | [PyAudio(Speaker)](https://github.com/dora-rs/dora/blob/main/node-hub/dora-pyaudio) | ✅ | Output audio from speaker |  |  |  | | |||
| ### 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 |  |  |  | | |||
| | [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 |  |  |  | | |||
| ### 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 |  |  |  | | |||
| ### 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 |  |  |  | | |||
| | [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 |  |  |  | | |||
| ### 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 |  |  |  | | |||
| ### Object Detection | |||
| | Title | Support | Description | Downloads | License | Release | | |||
| | ---------------------------------------------------------------------- | ------- | ---------------- | ------------------------------------------------------ | --------------------------------------------------- | --------------------------------------------------- | | |||
| | [Yolov8](https://github.com/dora-rs/dora/blob/main/node-hub/dora-yolo) | ✅ | Object detection |  |  |  | | |||
| ### 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 |  |  |  | | |||
| ### 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 |  |  |  | | |||
| ### 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 |  |  |  | | |||
| | [InternVL](https://github.com/dora-rs/dora/blob/main/node-hub/dora-internvl) | 🆗 | InternVL is a vision language model |  |  |  | | |||
| ### 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 |  |  |  | | |||
| ### Translation | |||
| | Title | Support | Description | Downloads | License | Release | | |||
| | --------------------------------------------------------------------------------------- | ------- | ------------------------------- | --------------------------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | | |||
| | [ArgosTranslate](https://github.com/dora-rs/dora/blob/main/node-hub/dora-argotranslate) | 🆗 | Open Source translation engine |  |  |  | | |||
| | [Opus MT](https://github.com/dora-rs/dora/blob/main/node-hub/dora-opus) | 🆗 | Translate text between language |  |  |  | | |||
| ### 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 |  |  |  | | |||
| ### 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 |  |  |  | | |||
| | [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 |  |  |  | | |||
| | [Rerun](https://github.com/dora-rs/dora/blob/main/node-hub/dora-rerun) | ✅ | Visualization tool |  |  |  | | |||
| ### 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 |  |  | | |||
| | Camera | [PyRealsense](https://github.com/dora-rs/dora/blob/main/node-hub/dora-pyrealsense) | Linux🆗 <br> Mac🛠️ | Image and depth from Realsense |  |  | | |||
| | Camera | [OpenCV Video Capture](https://github.com/dora-rs/dora/blob/main/node-hub/opencv-video-capture) | ✅ | Image stream from OpenCV Camera |  |  | | |||
| | Peripheral | [Keyboard](https://github.com/dora-rs/dora/blob/main/node-hub/dora-keyboard) | ✅ | Keyboard char listener |  |  | | |||
| | Peripheral | [Microphone](https://github.com/dora-rs/dora/blob/main/node-hub/dora-microphone) | ✅ | Audio from microphone |  |  | | |||
| | Peripheral | [PyAudio(Speaker)](https://github.com/dora-rs/dora/blob/main/node-hub/dora-pyaudio) | ✅ | Output audio from speaker |  |  | | |||
| | 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 |  |  | | |||
| | 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 |  |  | | |||
| | 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 |  |  | | |||
| | 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 |  |  | | |||
| | 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 |  |  | | |||
| | Speech to Text(STT) | [Whisper](https://github.com/dora-rs/dora/blob/main/node-hub/dora-distil-whisper) | ✅ | Transcribe audio to text |  |  | | |||
| | Object Detection | [Yolov8](https://github.com/dora-rs/dora/blob/main/node-hub/dora-yolo) | ✅ | Object detection |  |  | | |||
| | Segmentation | [SAM2](https://github.com/dora-rs/dora/blob/main/node-hub/dora-sam2) | Cuda✅ <br> Metal🛠️ | Segment Anything |  |  | | |||
| | Large Language Model(LLM) | [Qwen2.5](https://github.com/dora-rs/dora/blob/main/node-hub/dora-qwen) | ✅ | Large Language Model using Qwen |  |  | | |||
| | 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 |  |  | | |||
| | Vision Language Model(VLM) | [InternVL](https://github.com/dora-rs/dora/blob/main/node-hub/dora-internvl) | 🆗 | InternVL is a vision language model |  |  | | |||
| | 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 |  |  | | |||
| | Translation | [ArgosTranslate](https://github.com/dora-rs/dora/blob/main/node-hub/dora-argotranslate) | 🆗 | Open Source translation engine |  |  | | |||
| | Translation | [Opus MT](https://github.com/dora-rs/dora/blob/main/node-hub/dora-opus) | 🆗 | Translate text between language |  |  | | |||
| | Text to Speech(TTS) | [Kokoro TTS](https://github.com/dora-rs/dora/blob/main/node-hub/dora-kokoro-tts) | ✅ | Efficient Text to Speech |  |  | | |||
| | Recorder | [Llama Factory Recorder](https://github.com/dora-rs/dora/blob/main/node-hub/llama-factory-recorder) | 🆗 | Record data to train LLM and VLM |  |  | | |||
| | 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 |  |  | | |||
| | Visualization | [Rerun](https://github.com/dora-rs/dora/blob/main/node-hub/dora-rerun) | ✅ | Visualization tool |  |  | | |||
| | 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. |  | | |||
| | Audio | [Translation](https://github.com/dora-rs/dora/blob/main/examples/translation) | Translate audio in real time. |  | | |||
| | Vision | [Vision Language Model(VLM)](https://github.com/dora-rs/dora/blob/main/examples/vlm) | Use a VLM to understand images. |  | | |||
| | Vision | [YOLO](https://github.com/dora-rs/dora/blob/main/examples/python-dataflow) | Use YOLO to detect object within image. |  | | |||
| | Vision | [Camera](https://github.com/dora-rs/dora/blob/main/examples/camera) | Simple webcam plot example |  | | |||
| | Model Training | [Piper RDT](https://github.com/dora-rs/dora/blob/main/examples/piper) | Piper RDT Pipeline |  | | |||
| | 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 |  | | |||
| | ROS2 | [C++ ROS2 Example](https://github.com/dora-rs/dora/blob/main/examples/c++-ros2-dataflow) | Example using C++ ROS2 |  | | |||
| | ROS2 | [Rust ROS2 Example](https://github.com/dora-rs/dora/blob/main/examples/rust-ros2-dataflow) | Example using Rust ROS2 |  | | |||
| | ROS2 | [Python ROS2 Example](https://github.com/dora-rs/dora/blob/main/examples/python-ros2-dataflow) | Example using Python ROS2 |  | | |||
| | Benchmark | [GPU Benchmark](https://github.com/dora-rs/dora/blob/main/examples/cuda-benchmark) | GPU Benchmark of dora-rs |  | | |||
| | Benchmark | [CPU Benchmark](https://github.com/dora-rs/dora-benchmark/blob/main) | CPU Benchmark of dora-rs |  | | |||
| | Tutorial | [Rust Example](https://github.com/dora-rs/dora/blob/main/examples/rust-dataflow) | Example using Rust |  | | |||
| | Tutorial | [Python Example](https://github.com/dora-rs/dora/blob/main/examples/python-dataflow) | Example using Python |  | | |||
| | Tutorial | [CMake Example](https://github.com/dora-rs/dora/blob/main/examples/cmake-dataflow) | Example using CMake |  | | |||
| | Tutorial | [C Example](https://github.com/dora-rs/dora/blob/main/examples/c-dataflow) | Example with C node |  | | |||
| | Tutorial | [CUDA Example](https://github.com/dora-rs/dora/blob/main/examples/cuda-benchmark) | Example using CUDA Zero Copy |  | | |||
| | Tutorial | [C++ Example](https://github.com/dora-rs/dora/blob/main/examples/c++-dataflow) | Example with C++ node |  | | |||
| ## Getting Started | |||
| @@ -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" | |||
| @@ -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" | |||
| @@ -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()); | |||
| @@ -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 { | |||
| @@ -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" | |||
| @@ -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. | |||
| @@ -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") | |||
| @@ -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( | |||
| @@ -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", | |||
| ] | |||
| @@ -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")?; | |||
| @@ -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 | |||
| @@ -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"] } | |||
| @@ -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)) | |||
| } | |||
| @@ -0,0 +1 @@ | |||
| *.csv | |||
| @@ -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. | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -0,0 +1 @@ | |||
| *.csv | |||
| @@ -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. | |||
| @@ -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() | |||
| @@ -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 | |||
| @@ -0,0 +1 @@ | |||
| *.csv | |||
| @@ -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. | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -0,0 +1,6 @@ | |||
| fn main() { | |||
| println!( | |||
| "cargo:rustc-env=TARGET={}", | |||
| std::env::var("TARGET").unwrap() | |||
| ); | |||
| } | |||
| @@ -16,4 +16,5 @@ features = ["python", "pyo3/extension-module"] | |||
| [tool.ruff.lint] | |||
| extend-select = [ | |||
| "D", # pydocstyle | |||
| "UP" | |||
| ] | |||
| @@ -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) | |||
| ) | |||
| }, | |||
| )? | |||
| } | |||
| } | |||
| } | |||
| @@ -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(()) | |||
| @@ -17,5 +17,6 @@ __node-name__ = "__node_name__.main:main" | |||
| [tool.ruff.lint] | |||
| extend-select = [ | |||
| "D", | |||
| "D", # pydocstyle | |||
| "UP" | |||
| ] | |||
| @@ -2,6 +2,7 @@ | |||
| from dora import Node | |||
| def main(): | |||
| """Listen for input events and print received messages.""" | |||
| node = Node() | |||
| @@ -3,6 +3,7 @@ | |||
| import pyarrow as pa | |||
| from dora import Node | |||
| def main(): | |||
| """Process node input events and send speech output.""" | |||
| node = Node() | |||
| @@ -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??; | |||
| } | |||
| @@ -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")?; | |||
| @@ -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"] | |||
| @@ -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"] | |||
| @@ -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. | |||
| @@ -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). | |||
| @@ -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: | |||
|  | |||
| **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: | |||
|  | |||
| 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). | |||
| @@ -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). | |||
| @@ -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.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.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.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.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.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). | |||
| @@ -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.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). | |||
| @@ -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> | |||
| @@ -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> | |||
| @@ -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> | |||
| @@ -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> | |||
| @@ -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> | |||
| @@ -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 +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() | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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() | |||
| @@ -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() | |||
| @@ -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() | |||
| @@ -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() | |||
| @@ -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). | |||
| @@ -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). | |||
| @@ -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). | |||
| @@ -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). | |||
| @@ -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). | |||
| @@ -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. | |||
| @@ -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}") | |||
| @@ -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}") | |||
| @@ -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}") | |||
| @@ -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. | |||
| @@ -0,0 +1,9 @@ | |||
| port: /dev/ttyDXL_master_left | |||
| groups: | |||
| arm: | |||
| torque_enable: false | |||
| singles: | |||
| gripper: | |||
| torque_enable: false | |||
| @@ -0,0 +1,9 @@ | |||
| port: /dev/ttyDXL_master_right | |||
| groups: | |||
| arm: | |||
| torque_enable: false | |||
| singles: | |||
| gripper: | |||
| torque_enable: false | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -0,0 +1,4 @@ | |||
| nodes: | |||
| - id: control | |||
| custom: | |||
| source: teleop.py | |||
| @@ -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 | |||
| @@ -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], | |||
| }, | |||
| ], | |||
| ), | |||
| ) | |||
| @@ -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 | |||
| ``` | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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" | |||
| @@ -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" | |||
| @@ -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" | |||