From 8d946c8246db0df2565b620bad0b78e1a45476f4 Mon Sep 17 00:00:00 2001 From: ReubenLimMonash Date: Wed, 22 Oct 2025 13:17:30 +0800 Subject: [PATCH 01/12] Update README --- LEROBOT_README.md | 343 +++++++++++++++++++++ README.md | 419 +++++--------------------- media/WIN_20251016_11_55_46_Pro.jpg | Bin 0 -> 181380 bytes media/photo_6302902272987434185_w.jpg | Bin 0 -> 158190 bytes 4 files changed, 419 insertions(+), 343 deletions(-) create mode 100644 LEROBOT_README.md create mode 100644 media/WIN_20251016_11_55_46_Pro.jpg create mode 100644 media/photo_6302902272987434185_w.jpg diff --git a/LEROBOT_README.md b/LEROBOT_README.md new file mode 100644 index 00000000000..357e62cc1e9 --- /dev/null +++ b/LEROBOT_README.md @@ -0,0 +1,343 @@ +

+ LeRobot, Hugging Face Robotics Library +
+
+

+ +
+ +[![Tests](https://github.com/huggingface/lerobot/actions/workflows/nightly.yml/badge.svg?branch=main)](https://github.com/huggingface/lerobot/actions/workflows/nightly.yml?query=branch%3Amain) +[![Python versions](https://img.shields.io/pypi/pyversions/lerobot)](https://www.python.org/downloads/) +[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/huggingface/lerobot/blob/main/LICENSE) +[![Status](https://img.shields.io/pypi/status/lerobot)](https://pypi.org/project/lerobot/) +[![Version](https://img.shields.io/pypi/v/lerobot)](https://pypi.org/project/lerobot/) +[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-v2.1-ff69b4.svg)](https://github.com/huggingface/lerobot/blob/main/CODE_OF_CONDUCT.md) +[![Discord](https://dcbadge.vercel.app/api/server/C5P34WJ68S?style=flat)](https://discord.gg/s3KuuzsPFb) + + + +
+ +

+

+ Build Your Own HopeJR Robot!

+

+ +
+ HopeJR robot + +

Meet HopeJR – A humanoid robot arm and hand for dexterous manipulation!

+

Control it with exoskeletons and gloves for precise hand movements.

+

Perfect for advanced manipulation tasks! 🤖

+ +

+ See the full HopeJR tutorial here.

+
+ +
+ +

+

+ Build Your Own SO-101 Robot!

+

+ +
+ + + + + +
SO-101 follower armSO-101 leader arm
+ +

Meet the updated SO100, the SO-101 – Just €114 per arm!

+

Train it in minutes with a few simple moves on your laptop.

+

Then sit back and watch your creation act autonomously! 🤯

+ +

+ See the full SO-101 tutorial here.

+ +

Want to take it to the next level? Make your SO-101 mobile by building LeKiwi!

+

Check out the LeKiwi tutorial and bring your robot to life on wheels.

+ + LeKiwi mobile robot +
+ +
+ +

+

LeRobot: State-of-the-art AI for real-world robotics

+

+ +--- + +🤗 LeRobot aims to provide models, datasets, and tools for real-world robotics in PyTorch. The goal is to lower the barrier to entry to robotics so that everyone can contribute and benefit from sharing datasets and pretrained models. + +🤗 LeRobot contains state-of-the-art approaches that have been shown to transfer to the real-world with a focus on imitation learning and reinforcement learning. + +🤗 LeRobot already provides a set of pretrained models, datasets with human collected demonstrations, and simulation environments to get started without assembling a robot. In the coming weeks, the plan is to add more and more support for real-world robotics on the most affordable and capable robots out there. + +🤗 LeRobot hosts pretrained models and datasets on this Hugging Face community page: [huggingface.co/lerobot](https://huggingface.co/lerobot) + +#### Examples of pretrained models on simulation environments + + + + + + + + + + + + +
ACT policy on ALOHA envTDMPC policy on SimXArm envDiffusion policy on PushT env
ACT policy on ALOHA envTDMPC policy on SimXArm envDiffusion policy on PushT env
+ +## Installation + +LeRobot works with Python 3.10+ and PyTorch 2.2+. + +### Environment Setup + +Create a virtual environment with Python 3.10 and activate it, e.g. with [`miniconda`](https://docs.anaconda.com/free/miniconda/index.html): + +```bash +conda create -y -n lerobot python=3.10 +conda activate lerobot +``` + +When using `miniconda`, install `ffmpeg` in your environment: + +```bash +conda install ffmpeg -c conda-forge +``` + +> **NOTE:** This usually installs `ffmpeg 7.X` for your platform compiled with the `libsvtav1` encoder. If `libsvtav1` is not supported (check supported encoders with `ffmpeg -encoders`), you can: +> +> - _[On any platform]_ Explicitly install `ffmpeg 7.X` using: +> +> ```bash +> conda install ffmpeg=7.1.1 -c conda-forge +> ``` +> +> - _[On Linux only]_ Install [ffmpeg build dependencies](https://trac.ffmpeg.org/wiki/CompilationGuide/Ubuntu#GettheDependencies) and [compile ffmpeg from source with libsvtav1](https://trac.ffmpeg.org/wiki/CompilationGuide/Ubuntu#libsvtav1), and make sure you use the corresponding ffmpeg binary to your install with `which ffmpeg`. + +### Install LeRobot 🤗 + +#### From Source + +First, clone the repository and navigate into the directory: + +```bash +git clone https://github.com/huggingface/lerobot.git +cd lerobot +``` + +Then, install the library in editable mode. This is useful if you plan to contribute to the code. + +```bash +pip install -e . +``` + +> **NOTE:** If you encounter build errors, you may need to install additional dependencies (`cmake`, `build-essential`, and `ffmpeg libs`). On Linux, run: +> `sudo apt-get install cmake build-essential python3-dev pkg-config libavformat-dev libavcodec-dev libavdevice-dev libavutil-dev libswscale-dev libswresample-dev libavfilter-dev`. For other systems, see: [Compiling PyAV](https://pyav.org/docs/develop/overview/installation.html#bring-your-own-ffmpeg) + +For simulations, 🤗 LeRobot comes with gymnasium environments that can be installed as extras: + +- [aloha](https://github.com/huggingface/gym-aloha) +- [xarm](https://github.com/huggingface/gym-xarm) +- [pusht](https://github.com/huggingface/gym-pusht) + +For instance, to install 🤗 LeRobot with aloha and pusht, use: + +```bash +pip install -e ".[aloha, pusht]" +``` + +### Installation from PyPI + +**Core Library:** +Install the base package with: + +```bash +pip install lerobot +``` + +_This installs only the default dependencies._ + +**Extra Features:** +To install additional functionality, use one of the following: + +```bash +pip install 'lerobot[all]' # All available features +pip install 'lerobot[aloha,pusht]' # Specific features (Aloha & Pusht) +pip install 'lerobot[feetech]' # Feetech motor support +``` + +_Replace `[...]` with your desired features._ + +**Available Tags:** +For a full list of optional dependencies, see: +https://pypi.org/project/lerobot/ + +### Weights & Biases + +To use [Weights and Biases](https://docs.wandb.ai/quickstart) for experiment tracking, log in with + +```bash +wandb login +``` + +(note: you will also need to enable WandB in the configuration. See below.) + +### Visualize datasets + +Check out [example 1](https://github.com/huggingface/lerobot/blob/main/examples/dataset/load_lerobot_dataset.py) that illustrates how to use our dataset class which automatically downloads data from the Hugging Face hub. + +You can also locally visualize episodes from a dataset on the hub by executing our script from the command line: + +```bash +lerobot-dataset-viz \ + --repo-id lerobot/pusht \ + --episode-index 0 +``` + +or from a dataset in a local folder with the `root` option and the `--local-files-only` (in the following case the dataset will be searched for in `./my_local_data_dir/lerobot/pusht`) + +```bash +lerobot-dataset-viz \ + --repo-id lerobot/pusht \ + --root ./my_local_data_dir \ + --local-files-only 1 \ + --episode-index 0 +``` + +It will open `rerun.io` and display the camera streams, robot states and actions, like this: + +https://github-production-user-asset-6210df.s3.amazonaws.com/4681518/328035972-fd46b787-b532-47e2-bb6f-fd536a55a7ed.mov?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAVCODYLSA53PQK4ZA%2F20240505%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20240505T172924Z&X-Amz-Expires=300&X-Amz-Signature=d680b26c532eeaf80740f08af3320d22ad0b8a4e4da1bcc4f33142c15b509eda&X-Amz-SignedHeaders=host&actor_id=24889239&key_id=0&repo_id=748713144 + +Our script can also visualize datasets stored on a distant server. See `lerobot-dataset-viz --help` for more instructions. + +### The `LeRobotDataset` format + +A dataset in `LeRobotDataset` format is very simple to use. It can be loaded from a repository on the Hugging Face hub or a local folder simply with e.g. `dataset = LeRobotDataset("lerobot/aloha_static_coffee")` and can be indexed into like any Hugging Face and PyTorch dataset. For instance `dataset[0]` will retrieve a single temporal frame from the dataset containing observation(s) and an action as PyTorch tensors ready to be fed to a model. + +A specificity of `LeRobotDataset` is that, rather than retrieving a single frame by its index, we can retrieve several frames based on their temporal relationship with the indexed frame, by setting `delta_timestamps` to a list of relative times with respect to the indexed frame. For example, with `delta_timestamps = {"observation.image": [-1, -0.5, -0.2, 0]}` one can retrieve, for a given index, 4 frames: 3 "previous" frames 1 second, 0.5 seconds, and 0.2 seconds before the indexed frame, and the indexed frame itself (corresponding to the 0 entry). See example [1_load_lerobot_dataset.py](https://github.com/huggingface/lerobot/blob/main/examples/dataset/load_lerobot_dataset.py) for more details on `delta_timestamps`. + +Under the hood, the `LeRobotDataset` format makes use of several ways to serialize data which can be useful to understand if you plan to work more closely with this format. We tried to make a flexible yet simple dataset format that would cover most type of features and specificities present in reinforcement learning and robotics, in simulation and in real-world, with a focus on cameras and robot states but easily extended to other types of sensory inputs as long as they can be represented by a tensor. + +Here are the important details and internal structure organization of a typical `LeRobotDataset` instantiated with `dataset = LeRobotDataset("lerobot/aloha_static_coffee")`. The exact features will change from dataset to dataset but not the main aspects: + +``` +dataset attributes: + ├ hf_dataset: a Hugging Face dataset (backed by Arrow/parquet). Typical features example: + │ ├ observation.images.cam_high (VideoFrame): + │ │ VideoFrame = {'path': path to a mp4 video, 'timestamp' (float32): timestamp in the video} + │ ├ observation.state (list of float32): position of an arm joints (for instance) + │ ... (more observations) + │ ├ action (list of float32): goal position of an arm joints (for instance) + │ ├ episode_index (int64): index of the episode for this sample + │ ├ frame_index (int64): index of the frame for this sample in the episode ; starts at 0 for each episode + │ ├ timestamp (float32): timestamp in the episode + │ ├ next.done (bool): indicates the end of an episode ; True for the last frame in each episode + │ └ index (int64): general index in the whole dataset + ├ meta: a LeRobotDatasetMetadata object containing: + │ ├ info: a dictionary of metadata on the dataset + │ │ ├ codebase_version (str): this is to keep track of the codebase version the dataset was created with + │ │ ├ fps (int): frame per second the dataset is recorded/synchronized to + │ │ ├ features (dict): all features contained in the dataset with their shapes and types + │ │ ├ total_episodes (int): total number of episodes in the dataset + │ │ ├ total_frames (int): total number of frames in the dataset + │ │ ├ robot_type (str): robot type used for recording + │ │ ├ data_path (str): formattable string for the parquet files + │ │ └ video_path (str): formattable string for the video files (if using videos) + │ ├ episodes: a DataFrame containing episode metadata with columns: + │ │ ├ episode_index (int): index of the episode + │ │ ├ tasks (list): list of tasks for this episode + │ │ ├ length (int): number of frames in this episode + │ │ ├ dataset_from_index (int): start index of this episode in the dataset + │ │ └ dataset_to_index (int): end index of this episode in the dataset + │ ├ stats: a dictionary of statistics (max, mean, min, std) for each feature in the dataset, for instance + │ │ ├ observation.images.front_cam: {'max': tensor with same number of dimensions (e.g. `(c, 1, 1)` for images, `(c,)` for states), etc.} + │ │ └ ... + │ └ tasks: a DataFrame containing task information with task names as index and task_index as values + ├ root (Path): local directory where the dataset is stored + ├ image_transforms (Callable): optional image transformations to apply to visual modalities + └ delta_timestamps (dict): optional delta timestamps for temporal queries +``` + +A `LeRobotDataset` is serialised using several widespread file formats for each of its parts, namely: + +- hf_dataset stored using Hugging Face datasets library serialization to parquet +- videos are stored in mp4 format to save space +- metadata are stored in plain json/jsonl files + +Dataset can be uploaded/downloaded from the HuggingFace hub seamlessly. To work on a local dataset, you can specify its location with the `root` argument if it's not in the default `~/.cache/huggingface/lerobot` location. + +#### Reproduce state-of-the-art (SOTA) + +We provide some pretrained policies on our [hub page](https://huggingface.co/lerobot) that can achieve state-of-the-art performances. +You can reproduce their training by loading the config from their run. Simply running: + +```bash +lerobot-train --config_path=lerobot/diffusion_pusht +``` + +reproduces SOTA results for Diffusion Policy on the PushT task. + +## Contribute + +If you would like to contribute to 🤗 LeRobot, please check out our [contribution guide](https://github.com/huggingface/lerobot/blob/main/CONTRIBUTING.md). + +### Add a pretrained policy + +Once you have trained a policy you may upload it to the Hugging Face hub using a hub id that looks like `${hf_user}/${repo_name}` (e.g. [lerobot/diffusion_pusht](https://huggingface.co/lerobot/diffusion_pusht)). + +You first need to find the checkpoint folder located inside your experiment directory (e.g. `outputs/train/2024-05-05/20-21-12_aloha_act_default/checkpoints/002500`). Within that there is a `pretrained_model` directory which should contain: + +- `config.json`: A serialized version of the policy configuration (following the policy's dataclass config). +- `model.safetensors`: A set of `torch.nn.Module` parameters, saved in [Hugging Face Safetensors](https://huggingface.co/docs/safetensors/index) format. +- `train_config.json`: A consolidated configuration containing all parameters used for training. The policy configuration should match `config.json` exactly. This is useful for anyone who wants to evaluate your policy or for reproducibility. + +To upload these to the hub, run the following: + +```bash +huggingface-cli upload ${hf_user}/${repo_name} path/to/pretrained_model +``` + +See [eval.py](https://github.com/huggingface/lerobot/blob/main/src/lerobot/scripts/eval.py) for an example of how other people may use your policy. + +### Acknowledgment + +- The LeRobot team 🤗 for building SmolVLA [Paper](https://arxiv.org/abs/2506.01844), [Blog](https://huggingface.co/blog/smolvla). +- Thanks to Tony Zhao, Zipeng Fu and colleagues for open sourcing ACT policy, ALOHA environments and datasets. Ours are adapted from [ALOHA](https://tonyzhaozh.github.io/aloha) and [Mobile ALOHA](https://mobile-aloha.github.io). +- Thanks to Cheng Chi, Zhenjia Xu and colleagues for open sourcing Diffusion policy, Pusht environment and datasets, as well as UMI datasets. Ours are adapted from [Diffusion Policy](https://diffusion-policy.cs.columbia.edu) and [UMI Gripper](https://umi-gripper.github.io). +- Thanks to Nicklas Hansen, Yunhai Feng and colleagues for open sourcing TDMPC policy, Simxarm environments and datasets. Ours are adapted from [TDMPC](https://github.com/nicklashansen/tdmpc) and [FOWM](https://www.yunhaifeng.com/FOWM). +- Thanks to Antonio Loquercio and Ashish Kumar for their early support. +- Thanks to [Seungjae (Jay) Lee](https://sjlee.cc/), [Mahi Shafiullah](https://mahis.life/) and colleagues for open sourcing [VQ-BeT](https://sjlee.cc/vq-bet/) policy and helping us adapt the codebase to our repository. The policy is adapted from [VQ-BeT repo](https://github.com/jayLEE0301/vq_bet_official). + +## Citation + +If you want, you can cite this work with: + +```bibtex +@misc{cadene2024lerobot, + author = {Cadene, Remi and Alibert, Simon and Soare, Alexander and Gallouedec, Quentin and Zouitine, Adil and Palma, Steven and Kooijmans, Pepijn and Aractingi, Michel and Shukor, Mustafa and Aubakirova, Dana and Russi, Martino and Capuano, Francesco and Pascal, Caroline and Choghari, Jade and Moss, Jess and Wolf, Thomas}, + title = {LeRobot: State-of-the-art Machine Learning for Real-World Robotics in Pytorch}, + howpublished = "\url{https://github.com/huggingface/lerobot}", + year = {2024} +} +``` + +## Star History + +[![Star History Chart](https://api.star-history.com/svg?repos=huggingface/lerobot&type=Timeline)](https://star-history.com/#huggingface/lerobot&Timeline) + +``` + +``` diff --git a/README.md b/README.md index 357e62cc1e9..56809981dd9 100644 --- a/README.md +++ b/README.md @@ -1,343 +1,76 @@ -

- LeRobot, Hugging Face Robotics Library -
-
-

- -
- -[![Tests](https://github.com/huggingface/lerobot/actions/workflows/nightly.yml/badge.svg?branch=main)](https://github.com/huggingface/lerobot/actions/workflows/nightly.yml?query=branch%3Amain) -[![Python versions](https://img.shields.io/pypi/pyversions/lerobot)](https://www.python.org/downloads/) -[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/huggingface/lerobot/blob/main/LICENSE) -[![Status](https://img.shields.io/pypi/status/lerobot)](https://pypi.org/project/lerobot/) -[![Version](https://img.shields.io/pypi/v/lerobot)](https://pypi.org/project/lerobot/) -[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-v2.1-ff69b4.svg)](https://github.com/huggingface/lerobot/blob/main/CODE_OF_CONDUCT.md) -[![Discord](https://dcbadge.vercel.app/api/server/C5P34WJ68S?style=flat)](https://discord.gg/s3KuuzsPFb) - - - -
- -

-

- Build Your Own HopeJR Robot!

-

- -
- HopeJR robot - -

Meet HopeJR – A humanoid robot arm and hand for dexterous manipulation!

-

Control it with exoskeletons and gloves for precise hand movements.

-

Perfect for advanced manipulation tasks! 🤖

- -

- See the full HopeJR tutorial here.

-
- -
- -

-

- Build Your Own SO-101 Robot!

-

- -
- - - - - -
SO-101 follower armSO-101 leader arm
- -

Meet the updated SO100, the SO-101 – Just €114 per arm!

-

Train it in minutes with a few simple moves on your laptop.

-

Then sit back and watch your creation act autonomously! 🤯

- -

- See the full SO-101 tutorial here.

- -

Want to take it to the next level? Make your SO-101 mobile by building LeKiwi!

-

Check out the LeKiwi tutorial and bring your robot to life on wheels.

- - LeKiwi mobile robot -
- -
- -

-

LeRobot: State-of-the-art AI for real-world robotics

-

- ---- - -🤗 LeRobot aims to provide models, datasets, and tools for real-world robotics in PyTorch. The goal is to lower the barrier to entry to robotics so that everyone can contribute and benefit from sharing datasets and pretrained models. - -🤗 LeRobot contains state-of-the-art approaches that have been shown to transfer to the real-world with a focus on imitation learning and reinforcement learning. - -🤗 LeRobot already provides a set of pretrained models, datasets with human collected demonstrations, and simulation environments to get started without assembling a robot. In the coming weeks, the plan is to add more and more support for real-world robotics on the most affordable and capable robots out there. - -🤗 LeRobot hosts pretrained models and datasets on this Hugging Face community page: [huggingface.co/lerobot](https://huggingface.co/lerobot) - -#### Examples of pretrained models on simulation environments - - - - - - - - - - - - -
ACT policy on ALOHA envTDMPC policy on SimXArm envDiffusion policy on PushT env
ACT policy on ALOHA envTDMPC policy on SimXArm envDiffusion policy on PushT env
- -## Installation - -LeRobot works with Python 3.10+ and PyTorch 2.2+. - -### Environment Setup - -Create a virtual environment with Python 3.10 and activate it, e.g. with [`miniconda`](https://docs.anaconda.com/free/miniconda/index.html): - -```bash -conda create -y -n lerobot python=3.10 -conda activate lerobot -``` - -When using `miniconda`, install `ffmpeg` in your environment: - -```bash -conda install ffmpeg -c conda-forge -``` - -> **NOTE:** This usually installs `ffmpeg 7.X` for your platform compiled with the `libsvtav1` encoder. If `libsvtav1` is not supported (check supported encoders with `ffmpeg -encoders`), you can: -> -> - _[On any platform]_ Explicitly install `ffmpeg 7.X` using: -> -> ```bash -> conda install ffmpeg=7.1.1 -c conda-forge -> ``` -> -> - _[On Linux only]_ Install [ffmpeg build dependencies](https://trac.ffmpeg.org/wiki/CompilationGuide/Ubuntu#GettheDependencies) and [compile ffmpeg from source with libsvtav1](https://trac.ffmpeg.org/wiki/CompilationGuide/Ubuntu#libsvtav1), and make sure you use the corresponding ffmpeg binary to your install with `which ffmpeg`. - -### Install LeRobot 🤗 - -#### From Source - -First, clone the repository and navigate into the directory: - -```bash -git clone https://github.com/huggingface/lerobot.git -cd lerobot -``` - -Then, install the library in editable mode. This is useful if you plan to contribute to the code. - -```bash -pip install -e . -``` - -> **NOTE:** If you encounter build errors, you may need to install additional dependencies (`cmake`, `build-essential`, and `ffmpeg libs`). On Linux, run: -> `sudo apt-get install cmake build-essential python3-dev pkg-config libavformat-dev libavcodec-dev libavdevice-dev libavutil-dev libswscale-dev libswresample-dev libavfilter-dev`. For other systems, see: [Compiling PyAV](https://pyav.org/docs/develop/overview/installation.html#bring-your-own-ffmpeg) - -For simulations, 🤗 LeRobot comes with gymnasium environments that can be installed as extras: - -- [aloha](https://github.com/huggingface/gym-aloha) -- [xarm](https://github.com/huggingface/gym-xarm) -- [pusht](https://github.com/huggingface/gym-pusht) - -For instance, to install 🤗 LeRobot with aloha and pusht, use: - -```bash -pip install -e ".[aloha, pusht]" -``` - -### Installation from PyPI - -**Core Library:** -Install the base package with: - -```bash -pip install lerobot -``` - -_This installs only the default dependencies._ - -**Extra Features:** -To install additional functionality, use one of the following: - -```bash -pip install 'lerobot[all]' # All available features -pip install 'lerobot[aloha,pusht]' # Specific features (Aloha & Pusht) -pip install 'lerobot[feetech]' # Feetech motor support -``` - -_Replace `[...]` with your desired features._ - -**Available Tags:** -For a full list of optional dependencies, see: -https://pypi.org/project/lerobot/ - -### Weights & Biases - -To use [Weights and Biases](https://docs.wandb.ai/quickstart) for experiment tracking, log in with - -```bash -wandb login -``` - -(note: you will also need to enable WandB in the configuration. See below.) - -### Visualize datasets - -Check out [example 1](https://github.com/huggingface/lerobot/blob/main/examples/dataset/load_lerobot_dataset.py) that illustrates how to use our dataset class which automatically downloads data from the Hugging Face hub. - -You can also locally visualize episodes from a dataset on the hub by executing our script from the command line: - -```bash -lerobot-dataset-viz \ - --repo-id lerobot/pusht \ - --episode-index 0 -``` - -or from a dataset in a local folder with the `root` option and the `--local-files-only` (in the following case the dataset will be searched for in `./my_local_data_dir/lerobot/pusht`) - -```bash -lerobot-dataset-viz \ - --repo-id lerobot/pusht \ - --root ./my_local_data_dir \ - --local-files-only 1 \ - --episode-index 0 -``` - -It will open `rerun.io` and display the camera streams, robot states and actions, like this: - -https://github-production-user-asset-6210df.s3.amazonaws.com/4681518/328035972-fd46b787-b532-47e2-bb6f-fd536a55a7ed.mov?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAVCODYLSA53PQK4ZA%2F20240505%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20240505T172924Z&X-Amz-Expires=300&X-Amz-Signature=d680b26c532eeaf80740f08af3320d22ad0b8a4e4da1bcc4f33142c15b509eda&X-Amz-SignedHeaders=host&actor_id=24889239&key_id=0&repo_id=748713144 - -Our script can also visualize datasets stored on a distant server. See `lerobot-dataset-viz --help` for more instructions. - -### The `LeRobotDataset` format - -A dataset in `LeRobotDataset` format is very simple to use. It can be loaded from a repository on the Hugging Face hub or a local folder simply with e.g. `dataset = LeRobotDataset("lerobot/aloha_static_coffee")` and can be indexed into like any Hugging Face and PyTorch dataset. For instance `dataset[0]` will retrieve a single temporal frame from the dataset containing observation(s) and an action as PyTorch tensors ready to be fed to a model. - -A specificity of `LeRobotDataset` is that, rather than retrieving a single frame by its index, we can retrieve several frames based on their temporal relationship with the indexed frame, by setting `delta_timestamps` to a list of relative times with respect to the indexed frame. For example, with `delta_timestamps = {"observation.image": [-1, -0.5, -0.2, 0]}` one can retrieve, for a given index, 4 frames: 3 "previous" frames 1 second, 0.5 seconds, and 0.2 seconds before the indexed frame, and the indexed frame itself (corresponding to the 0 entry). See example [1_load_lerobot_dataset.py](https://github.com/huggingface/lerobot/blob/main/examples/dataset/load_lerobot_dataset.py) for more details on `delta_timestamps`. - -Under the hood, the `LeRobotDataset` format makes use of several ways to serialize data which can be useful to understand if you plan to work more closely with this format. We tried to make a flexible yet simple dataset format that would cover most type of features and specificities present in reinforcement learning and robotics, in simulation and in real-world, with a focus on cameras and robot states but easily extended to other types of sensory inputs as long as they can be represented by a tensor. - -Here are the important details and internal structure organization of a typical `LeRobotDataset` instantiated with `dataset = LeRobotDataset("lerobot/aloha_static_coffee")`. The exact features will change from dataset to dataset but not the main aspects: - -``` -dataset attributes: - ├ hf_dataset: a Hugging Face dataset (backed by Arrow/parquet). Typical features example: - │ ├ observation.images.cam_high (VideoFrame): - │ │ VideoFrame = {'path': path to a mp4 video, 'timestamp' (float32): timestamp in the video} - │ ├ observation.state (list of float32): position of an arm joints (for instance) - │ ... (more observations) - │ ├ action (list of float32): goal position of an arm joints (for instance) - │ ├ episode_index (int64): index of the episode for this sample - │ ├ frame_index (int64): index of the frame for this sample in the episode ; starts at 0 for each episode - │ ├ timestamp (float32): timestamp in the episode - │ ├ next.done (bool): indicates the end of an episode ; True for the last frame in each episode - │ └ index (int64): general index in the whole dataset - ├ meta: a LeRobotDatasetMetadata object containing: - │ ├ info: a dictionary of metadata on the dataset - │ │ ├ codebase_version (str): this is to keep track of the codebase version the dataset was created with - │ │ ├ fps (int): frame per second the dataset is recorded/synchronized to - │ │ ├ features (dict): all features contained in the dataset with their shapes and types - │ │ ├ total_episodes (int): total number of episodes in the dataset - │ │ ├ total_frames (int): total number of frames in the dataset - │ │ ├ robot_type (str): robot type used for recording - │ │ ├ data_path (str): formattable string for the parquet files - │ │ └ video_path (str): formattable string for the video files (if using videos) - │ ├ episodes: a DataFrame containing episode metadata with columns: - │ │ ├ episode_index (int): index of the episode - │ │ ├ tasks (list): list of tasks for this episode - │ │ ├ length (int): number of frames in this episode - │ │ ├ dataset_from_index (int): start index of this episode in the dataset - │ │ └ dataset_to_index (int): end index of this episode in the dataset - │ ├ stats: a dictionary of statistics (max, mean, min, std) for each feature in the dataset, for instance - │ │ ├ observation.images.front_cam: {'max': tensor with same number of dimensions (e.g. `(c, 1, 1)` for images, `(c,)` for states), etc.} - │ │ └ ... - │ └ tasks: a DataFrame containing task information with task names as index and task_index as values - ├ root (Path): local directory where the dataset is stored - ├ image_transforms (Callable): optional image transformations to apply to visual modalities - └ delta_timestamps (dict): optional delta timestamps for temporal queries -``` - -A `LeRobotDataset` is serialised using several widespread file formats for each of its parts, namely: - -- hf_dataset stored using Hugging Face datasets library serialization to parquet -- videos are stored in mp4 format to save space -- metadata are stored in plain json/jsonl files - -Dataset can be uploaded/downloaded from the HuggingFace hub seamlessly. To work on a local dataset, you can specify its location with the `root` argument if it's not in the default `~/.cache/huggingface/lerobot` location. - -#### Reproduce state-of-the-art (SOTA) - -We provide some pretrained policies on our [hub page](https://huggingface.co/lerobot) that can achieve state-of-the-art performances. -You can reproduce their training by loading the config from their run. Simply running: - -```bash -lerobot-train --config_path=lerobot/diffusion_pusht -``` - -reproduces SOTA results for Diffusion Policy on the PushT task. - -## Contribute - -If you would like to contribute to 🤗 LeRobot, please check out our [contribution guide](https://github.com/huggingface/lerobot/blob/main/CONTRIBUTING.md). - -### Add a pretrained policy - -Once you have trained a policy you may upload it to the Hugging Face hub using a hub id that looks like `${hf_user}/${repo_name}` (e.g. [lerobot/diffusion_pusht](https://huggingface.co/lerobot/diffusion_pusht)). - -You first need to find the checkpoint folder located inside your experiment directory (e.g. `outputs/train/2024-05-05/20-21-12_aloha_act_default/checkpoints/002500`). Within that there is a `pretrained_model` directory which should contain: - -- `config.json`: A serialized version of the policy configuration (following the policy's dataclass config). -- `model.safetensors`: A set of `torch.nn.Module` parameters, saved in [Hugging Face Safetensors](https://huggingface.co/docs/safetensors/index) format. -- `train_config.json`: A consolidated configuration containing all parameters used for training. The policy configuration should match `config.json` exactly. This is useful for anyone who wants to evaluate your policy or for reproducibility. - -To upload these to the hub, run the following: - -```bash -huggingface-cli upload ${hf_user}/${repo_name} path/to/pretrained_model -``` - -See [eval.py](https://github.com/huggingface/lerobot/blob/main/src/lerobot/scripts/eval.py) for an example of how other people may use your policy. - -### Acknowledgment - -- The LeRobot team 🤗 for building SmolVLA [Paper](https://arxiv.org/abs/2506.01844), [Blog](https://huggingface.co/blog/smolvla). -- Thanks to Tony Zhao, Zipeng Fu and colleagues for open sourcing ACT policy, ALOHA environments and datasets. Ours are adapted from [ALOHA](https://tonyzhaozh.github.io/aloha) and [Mobile ALOHA](https://mobile-aloha.github.io). -- Thanks to Cheng Chi, Zhenjia Xu and colleagues for open sourcing Diffusion policy, Pusht environment and datasets, as well as UMI datasets. Ours are adapted from [Diffusion Policy](https://diffusion-policy.cs.columbia.edu) and [UMI Gripper](https://umi-gripper.github.io). -- Thanks to Nicklas Hansen, Yunhai Feng and colleagues for open sourcing TDMPC policy, Simxarm environments and datasets. Ours are adapted from [TDMPC](https://github.com/nicklashansen/tdmpc) and [FOWM](https://www.yunhaifeng.com/FOWM). -- Thanks to Antonio Loquercio and Ashish Kumar for their early support. -- Thanks to [Seungjae (Jay) Lee](https://sjlee.cc/), [Mahi Shafiullah](https://mahis.life/) and colleagues for open sourcing [VQ-BeT](https://sjlee.cc/vq-bet/) policy and helping us adapt the codebase to our repository. The policy is adapted from [VQ-BeT repo](https://github.com/jayLEE0301/vq_bet_official). - -## Citation - -If you want, you can cite this work with: - -```bibtex -@misc{cadene2024lerobot, - author = {Cadene, Remi and Alibert, Simon and Soare, Alexander and Gallouedec, Quentin and Zouitine, Adil and Palma, Steven and Kooijmans, Pepijn and Aractingi, Michel and Shukor, Mustafa and Aubakirova, Dana and Russi, Martino and Capuano, Francesco and Pascal, Caroline and Choghari, Jade and Moss, Jess and Wolf, Thomas}, - title = {LeRobot: State-of-the-art Machine Learning for Real-World Robotics in Pytorch}, - howpublished = "\url{https://github.com/huggingface/lerobot}", - year = {2024} -} -``` - -## Star History - -[![Star History Chart](https://api.star-history.com/svg?repos=huggingface/lerobot&type=Timeline)](https://star-history.com/#huggingface/lerobot&Timeline) - -``` - -``` +# LeRobot Setup with Mini Studio Guide + +This documentation shows how to setup the LeRobot SO-101 dual-arm teleoperation system. + +# Step 1: Installation +For installation instruction refer to [Lerobot README](LEROBOT_README.md). + +# Step 2: Mini studio setup +Setup the mini studio and Lerobot as per below image. + +![image info](media\photo_6302902272987434185_w.jpg) + +# Step 3: Hardware Connection + +1. Connect Follower and Leader Arm to USB port +2. Power both arms with their DC adapters +3. Check Follower and Leader Arm COM port number + +To check the COM port number, run below command. Make sure to activate lerobot environment. + +Activate conda evn: +``` +conda activate lerobot +``` +Check COM port number: + +``` +lerobot-find-port +``` + +# Step 4: Run Lerobot Demo + +# Calibration +It may request for calibration file. If no file exist or required to calibrate follow guideline on how to do calibration from [Lerobot](https://huggingface.co/docs/lerobot/so101?calibrate_leader=Command). + + +# Teleoperation Demo + +Setup together Leader arm for teleoperation demo. Then run below command to run teleoperation demo. +``` +lerobot-teleoperate +--robot.type=so101_follower +--robot.port= +--robot.id=follower_one +--robot.cameras "{bottom: {type: opencv, index_or_path: 1, width: 640, height: 480, fps: 30}, top: {type: opencv, index_or_path: 0, width: 640, height: 480, fps: 30}}" +--teleop.type=so101_leader +--teleop.port= +--teleop.id=leader_one +--display_data=true +``` +When setup for teleoperation demo, Leader arm and follower arm can be setup side to side or front to back depend on suitability of the place. Refer below image for setup suggestions. + +# Deploy Trained Policy Demo + +To run the Lerobot with trained AI model, run below command +``` +lerobot-record +--robot.type=so101_follower +--robot.port= +--robot.id=follower_one +--robot.cameras "{bottom: {type: opencv, index_or_path: 1, width: 640, height: 480, fps: 30}, top: {type: opencv, index_or_path: 0, width: 640, height: 480, fps: 30}}" +--display_data=true +--dataset.repo_id= +--dataset.num_episodes=1 --dataset.episode_time_s=600 +--dataset.reset_time_s=60 +--dataset.single_task="Pickup and insert white block into cup" +--policypath=C:\Users\User\Documents\reuben_ws\lerobot\outputs\train\act_mini_studio_block_in_cup_redo\checkpoints\last\pretrained_model +--dataset.push_to_hub=false +``` +During demonstration, try to place the block at these positions. See image below. +![image info](media/WIN_20251016_11_55_46_Pro.jpg) + + +## Lerobot + +![image info](media\lerobot-logo-light.png) diff --git a/media/WIN_20251016_11_55_46_Pro.jpg b/media/WIN_20251016_11_55_46_Pro.jpg new file mode 100644 index 0000000000000000000000000000000000000000..bb94aa2de264bbd8b302614cbc0a8496aae5849e GIT binary patch literal 181380 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 z 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�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 literal 0 HcmV?d00001 From 65cbea9764e042f3952d8afd07ac779fee5b751d Mon Sep 17 00:00:00 2001 From: raiddanial Date: Wed, 10 Dec 2025 10:39:52 +0800 Subject: [PATCH 02/12] Add AmazingHand dual-hand support(tested with left only) with teleoperator and run-policy command - Added lerobot_robot_amazinghand robot driver with dual-hand support (left/right) - Added lerobot_teleoperator_amazinghandtracker with MediaPipe hand tracking - Implemented lerobot-run-policy command for running trained policies without recording - Added configurable MediaPipe confidence thresholds (default 0.7) - Fixed motor ID mapping for right hand (left IDs - 10) - Added MuJoCo visualization support with LEROBOT_SHOW_MUJOCO_VIEWER - Created comprehensive setup documentation in AMAZINGHAND_SETUP.md --- .../lerobot_robot_amazinghand/amazinghand.py | 782 ++++++++++++++++++ 1 file changed, 782 insertions(+) create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/amazinghand.py diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/amazinghand.py b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/amazinghand.py new file mode 100644 index 00000000000..85a37d656fa --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/amazinghand.py @@ -0,0 +1,782 @@ +#!/usr/bin/env python + +# Copyright 2025 The HuggingFace Inc. team. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +import time +from functools import cached_property +from typing import Any +from pathlib import Path + +import numpy as np +import mujoco +import mink + +from lerobot.cameras.utils import make_cameras_from_configs +from lerobot.motors import Motor, MotorNormMode, MotorCalibration +from lerobot.motors.calibration_gui import RangeFinderGUI +from lerobot.motors.feetech import ( + FeetechMotorsBus, +) +from lerobot.utils.errors import DeviceAlreadyConnectedError, DeviceNotConnectedError + +from lerobot.robots.robot import Robot +from .config_amazinghand import AmazingHandConfig +from lerobot.robots.config import RobotConfig + +logger = logging.getLogger(__name__) + +class AmazingHand(Robot): + config_class = AmazingHandConfig + name = "lerobot_robot_amazinghand" + + def __init__(self, config: AmazingHandConfig): + super().__init__(config) + self.config = config + import os + + self.bus = FeetechMotorsBus( + port=self.config.port, + motors={ + # Index + "index_m1": Motor(15, "scs0009", MotorNormMode.RANGE_0_100), + "index_m2": Motor(16, "scs0009", MotorNormMode.RANGE_0_100), + # Middle + "middle_m1": Motor(13, "scs0009", MotorNormMode.RANGE_0_100), + "middle_m2": Motor(14, "scs0009", MotorNormMode.RANGE_0_100), + # Ring + "ring_m1": Motor(11, "scs0009", MotorNormMode.RANGE_0_100), + "ring_m2": Motor(12, "scs0009", MotorNormMode.RANGE_0_100), + # Thumb + "thumb_m1": Motor(17, "scs0009", MotorNormMode.RANGE_0_100), + "thumb_m2": Motor(18, "scs0009", MotorNormMode.RANGE_0_100), + }, + calibration=self.calibration, + protocol_version=1, + ) + self.cameras = make_cameras_from_configs(config.cameras) + # Demo has NO motor inversions - all motors are invert=false + self.inverted_motors = [] + + # Initialize MuJoCo simulation + IK solver + self._setup_simulation() + logger.info("Robot smoothing: DISABLED (raw IK output sent directly)") + + # MuJoCo viewer for visualization (optional, controlled by env var) + self.viewer = None + self._viewer_thread = None + self._viewer_stop = False + self._viewer_enabled = os.getenv("LEROBOT_SHOW_MUJOCO_VIEWER", "0").lower() in ("1", "true", "yes") + + # Optional per-finger axis sign flips before IK (for quick field tuning) + # Format: LEROBOT_TIP_AXIS_FLIPS="index:x:-1;middle:x:-1;thumb:y:-1" (axis in {x,y,z}, sign in {1,-1}) + self.axis_flips: dict[str, np.ndarray] = {} + flips_env = os.getenv("LEROBOT_TIP_AXIS_FLIPS") + if flips_env: + try: + for entry in flips_env.split(";"): + entry = entry.strip() + if not entry: + continue + # Allow compact form finger:axis:sign or finger:x:-1,y:1,z:1 + if "," in entry: + # finger:axis:sign,axis:sign,axis:sign + finger, rest = entry.split(":", 1) + signs = {"x":1, "y":1, "z":1} + for part in rest.split(","): + a,s = part.split(":") + signs[a.strip()] = int(s.strip()) + else: + finger, axis, sign = entry.split(":") + signs = {"x":1, "y":1, "z":1} + signs[axis.strip()] = int(sign.strip()) + self.axis_flips[finger.strip()] = np.array([signs["x"], signs["y"], signs["z"]], dtype=float) + logger.info(f"Axis flips configured: {self.axis_flips}") + except Exception as e: + logger.warning(f"Failed to parse LEROBOT_TIP_AXIS_FLIPS='{flips_env}': {e}") + + # Optional per-finger axis permutation (map hand-frame components to robot frame order) + # Format: LEROBOT_TIP_AXIS_PERM="index=2,1,0;middle=0,2,1" where values are indices for x,y,z + self.axis_perm: dict[str, np.ndarray] = {} + perm_env = os.getenv("LEROBOT_TIP_AXIS_PERM") + if perm_env: + try: + for entry in perm_env.split(";"): + entry = entry.strip() + if not entry: + continue + finger, perm = entry.split("=") + idxs = [int(x.strip()) for x in perm.split(",")] + if len(idxs) != 3 or any(i not in (0,1,2) for i in idxs): + raise ValueError("Permutation must be three indices 0,1,2") + self.axis_perm[finger.strip()] = np.array(idxs, dtype=int) + logger.info(f"Axis permutations configured: {self.axis_perm}") + except Exception as e: + logger.warning(f"Failed to parse LEROBOT_TIP_AXIS_PERM='{perm_env}': {e}") + + # Optional per-finger motor swap (swap m1/m2 logical mapping) before sending goals + swaps_env = os.getenv("LEROBOT_SWAP_FINGERS") # e.g. "index,middle" + self.swap_fingers: set[str] = set() + if swaps_env: + try: + self.swap_fingers = {s.strip() for s in swaps_env.split(",") if s.strip()} + if self.swap_fingers: + logger.info(f"Motor swap enabled for fingers: {sorted(self.swap_fingers)}") + except Exception: + pass + + def _setup_simulation(self): + """Initialize MuJoCo simulation and mink IK solver with persistent state.""" + # Load MuJoCo model: prefer env override LEROBOT_MJCF_PATH if provided, else bundled MJCF + import os + env_mjcf = os.getenv("LEROBOT_MJCF_PATH") + mjcf_path: Path + if env_mjcf: + p = Path(env_mjcf) + if not p.exists(): + raise FileNotFoundError(f"LEROBOT_MJCF_PATH set but file not found: {p}") + mjcf_path = p + logger.info(f"Using MuJoCo model from LEROBOT_MJCF_PATH: {mjcf_path}") + else: + # Try both the original teleoperator location and new configs location + robot_package_dir = Path(__file__).parent.parent.parent.parent + # First try configs/robot/amazinghand/mjcf/scene.xml (copied from Demo) + mjcf_path = robot_package_dir / "configs" / "robot" / "amazinghand" / "mjcf" / "scene.xml" + if not mjcf_path.exists(): + # Fallback to teleoperator package + mjcf_path = robot_package_dir / "teleoperators" / "lerobot_teleoperator_amazinghandtracker" / "lerobot_teleoperator_amazinghandtracker" / "mjcf" / "scene.xml" + if not mjcf_path.exists(): + raise FileNotFoundError(f"MuJoCo model not found at {mjcf_path}") + logger.info(f"Using bundled MuJoCo model: {mjcf_path}") + + self.mj_model = mujoco.MjModel.from_xml_path(str(mjcf_path)) + self.mj_data = mujoco.MjData(self.mj_model) + self.configuration = mink.Configuration(self.mj_model) + self.data = self.configuration.data + + # Initialize from zero keyframe for a stable starting pose + try: + self.configuration.update_from_keyframe("zero") + except Exception: + # Some models may not have a 'zero' keyframe; ignore if missing + pass + + # Posture and equality constraint tasks to stabilize IK + self.posture_task = mink.PostureTask(self.mj_model, cost=1e-2) + self.posture_task.set_target_from_configuration(self.configuration) + self.eq_task = mink.EqualityConstraintTask(self.mj_model, cost=1000.0) + + # Create persistent IK tasks for each fingertip (position-only control) + self.tip_tasks = {} + for finger_idx in [1, 2, 3, 4]: + site_name = f"tip{finger_idx}" + site_id = mujoco.mj_name2id(self.mj_model, mujoco.mjtObj.mjOBJ_SITE, site_name) + if site_id >= 0: + task = mink.FrameTask( + frame_name=site_name, + frame_type="site", + position_cost=1.0, + orientation_cost=0.0, + lm_damping=1.0, + ) + self.tip_tasks[finger_idx] = task + + # Initialize mocap bodies at their respective sites (Demo pattern) + for finger_idx in [1, 2, 3, 4]: + mocap_body = f"finger{finger_idx}_target" + site_name = f"tip{finger_idx}" + try: + mink.move_mocap_to_frame(self.mj_model, self.data, mocap_body, site_name, "site") + except Exception as e: + logger.warning(f"Could not initialize mocap {mocap_body} from {site_name}: {e}") + + # Per-finger scaling factors and offsets from original mj_mink_left.py + # Map logical finger names to MJCF indices (finger1..finger4) + # Increased scale for better range of motion in teleoperation + # Can be overridden with LEROBOT_TIP_SCALE env var + self.tip_scale = float(os.getenv("LEROBOT_TIP_SCALE", "2.5")) + # All fingers use same scale for consistency with Demo + self.tip_scales = {1: self.tip_scale, 2: self.tip_scale, 3: self.tip_scale, 4: self.tip_scale} + logger.info(f"Using tip vector scale factor: {self.tip_scale} (set LEROBOT_TIP_SCALE to adjust)") + self.finger_name_to_index = {"index": 1, "middle": 2, "ring": 3, "thumb": 4} + self.tip_offsets = { + 1: np.array([0.025, -0.022, 0.098]), # index + 2: np.array([0.025, 0.009, 0.092]), # middle + 3: np.array([0.025, 0.040, 0.082]), # ring + 4: np.array([0.024, -0.019, 0.017]), # thumb + } + + # Per-motor angular offsets (applied post-IK, in radians) + # These are CRITICAL - they center the robot's neutral position + # Can be loaded from TOML file via LEROBOT_MOTOR_OFFSETS_PATH env var + self.motor_offsets = self._load_motor_offsets() + logger.info("=" * 60) + logger.info("MOTOR OFFSETS (CRITICAL FOR ACCURACY):") + for motor_name in sorted(self.motor_offsets.keys()): + offset = self.motor_offsets[motor_name] + logger.info(f" {motor_name}: {offset:+.4f} rad ({offset * 180/np.pi:+.2f}°)") + logger.info("=" * 60) + + # Simulation configuration parameters (matching Demo's rate limiter) + self.sim_dt = self.mj_model.opt.timestep # Use model timestep (typically 0.001) + self.ik_solver = "quadprog" + self.ik_tolerance = 1e-5 + + # Optional angle scaling for more range (EXPERIMENTAL - use carefully) + self._angle_scale = float(os.getenv("LEROBOT_ANGLE_SCALE", "1.05")) + if self._angle_scale != 1.0: + logger.warning(f"⚠️ ANGLE_SCALE={self._angle_scale} (EXPERIMENTAL - may strain servos)") + + logger.info(f"Simulation initialized with dt={self.sim_dt}, solver={self.ik_solver}") + + def _load_motor_offsets(self) -> dict[str, float]: + """Load motor offsets from TOML file or use defaults. + + Checks in order: + 1. LEROBOT_MOTOR_OFFSETS_PATH env var (absolute path) + 2. config/motor_offsets.toml (next to this file) + 3. Default values from Demo + + Expected TOML format: + [[motors]] + finger_name = "l_finger1" + [motors.motor1] + offset = 0.031 + [motors.motor2] + offset = 0.173 + """ + import os + from pathlib import Path + + # Check env var first + toml_path_str = os.getenv("LEROBOT_MOTOR_OFFSETS_PATH") + + if toml_path_str: + toml_path = Path(toml_path_str) + else: + # Check standard location: config/l_hand.toml next to this file (Demo format) + toml_path = Path(__file__).parent / "config" / "l_hand.toml" + if not toml_path.exists(): + # Fallback to motor_offsets.toml if custom file exists + toml_path = Path(__file__).parent / "config" / "motor_offsets.toml" + if not toml_path.exists(): + toml_path = None + + if toml_path: + try: + import tomli + if not toml_path.exists(): + logger.warning(f"Motor offsets TOML not found: {toml_path}, using defaults") + else: + with open(toml_path, "rb") as f: + config = tomli.load(f) + + # Parse TOML structure + offsets = {} + finger_map = { + "l_finger1": ("index_m1", "index_m2"), + "l_finger2": ("middle_m1", "middle_m2"), + "l_finger3": ("ring_m1", "ring_m2"), + "l_finger4": ("thumb_m1", "thumb_m2"), + } + + for motor_entry in config.get("motors", []): + finger_name = motor_entry.get("finger_name") + if finger_name in finger_map: + m1_name, m2_name = finger_map[finger_name] + + motor1 = motor_entry.get("motor1", {}) + motor2 = motor_entry.get("motor2", {}) + + if "offset" in motor1: + offsets[m1_name] = float(motor1["offset"]) + if "offset" in motor2: + offsets[m2_name] = float(motor2["offset"]) + + logger.info(f"Loaded motor offsets from {toml_path}: {offsets}") + return offsets + + except ImportError: + logger.warning("tomli not installed, using default offsets. Install with: pip install tomli") + except Exception as e: + logger.warning(f"Failed to load motor offsets from {toml_path}: {e}, using defaults") + + # Default offsets from Demo's l_hand.toml + defaults = { + "index_m1": 0.0311, # finger1 motor1 + "index_m2": 0.1731, # finger1 motor2 + "middle_m1": -0.6009, # finger2 motor1 + "middle_m2": 0.6137, # finger2 motor2 + "ring_m1": -0.9901, # finger3 motor1 + "ring_m2": 0.9146, # finger3 motor2 + "thumb_m1": -0.1009, # finger4 motor1 + "thumb_m2": 0.5113, # finger4 motor2 + } + logger.info(f"Using default motor offsets from Demo config") + return defaults + + def _run_viewer(self): + """Run MuJoCo viewer in separate thread (for older MuJoCo API).""" + try: + # glfw-based viewer requires running in main thread on some platforms, + # but we'll try in background thread for convenience + import glfw + if not glfw.init(): + logger.error(f"{self}: Failed to initialize GLFW for viewer") + return + + # Create window + window = glfw.create_window(1200, 900, "MuJoCo - AmazingHand IK Simulation", None, None) + if not window: + logger.error(f"{self}: Failed to create GLFW window") + glfw.terminate() + return + + glfw.make_context_current(window) + glfw.swap_interval(1) + + # Create MuJoCo rendering context + import mujoco + context = mujoco.MjrContext(self.mj_model, mujoco.mjtFontScale.mjFONTSCALE_150) + scene = mujoco.MjvScene(self.mj_model, maxgeom=10000) + camera = mujoco.MjvCamera() + option = mujoco.MjvOption() + + # Set camera for better view + camera.azimuth = 90 + camera.elevation = -20 + camera.distance = 0.5 + camera.lookat[:] = [0.025, 0.01, 0.06] + + viewport = mujoco.MjrRect(0, 0, 1200, 900) + + while not self._viewer_stop and not glfw.window_should_close(window): + # Update scene + mujoco.mjv_updateScene( + self.mj_model, self.data, option, None, camera, + mujoco.mjtCatBit.mjCAT_ALL, scene + ) + + # Render + mujoco.mjr_render(viewport, scene, context) + + glfw.swap_buffers(window) + glfw.poll_events() + + time.sleep(0.01) # ~100 FPS max + + glfw.terminate() + logger.info(f"{self}: Viewer window closed.") + + except Exception as e: + logger.error(f"{self}: Error in viewer thread: {e}", exc_info=True) + + @property + def _motors_ft(self) -> dict[str, type]: + return {f"{motor}.pos": float for motor in self.bus.motors} + + @property + def _cameras_ft(self) -> dict[str, tuple]: + return { + cam: (self.config.cameras[cam].height, self.config.cameras[cam].width, 3) for cam in self.cameras + } + + @cached_property + def observation_features(self) -> dict[str, type | tuple]: + return {**self._motors_ft, **self._cameras_ft} + + @cached_property + def action_features(self) -> dict[str, type]: + # Record motor angles (what we actually control) + # Tip vectors from teleoperator are converted to motor angles via IK + return {f"{motor}.pos": float for motor in self.bus.motors.keys()} + + @property + def is_connected(self) -> bool: + return self.bus.is_connected and all(cam.is_connected for cam in self.cameras.values()) + + def connect(self, calibrate: bool = True) -> None: + if self.is_connected: + raise DeviceAlreadyConnectedError(f"{self} already connected") + + self.bus.connect() + if not self.is_calibrated and calibrate: + self.calibrate() + + # Connect the cameras + for cam in self.cameras.values(): + cam.connect() + + self.configure() + + # Launch MuJoCo viewer if enabled + if self._viewer_enabled: + logger.info(f"{self}: Launching MuJoCo viewer...") + try: + # Try new API (mujoco >= 3.0) + import mujoco.viewer + self.viewer = mujoco.viewer.launch_passive(self.mj_model, self.data) + logger.info(f"{self}: MuJoCo viewer launched (new API). You can see the simulation in real-time!") + except (AttributeError, ImportError) as e: + # Fallback to older API (mujoco < 3.0) - passive viewer using context manager pattern + logger.info(f"{self}: New viewer API not available ({e}), trying older API with threading...") + try: + import threading + self._viewer_thread = threading.Thread(target=self._run_viewer, daemon=True) + self._viewer_thread.start() + logger.info(f"{self}: MuJoCo viewer thread started. Window should appear shortly.") + except Exception as viewer_err: + logger.warning(f"{self}: Could not start viewer: {viewer_err}. Continuing without visualization.") + logger.info(f"{self}: You can still see IK results in the logs and on the physical robot.") + + logger.info(f"{self} connected.") + + @property + def is_calibrated(self) -> bool: + return self.bus.is_calibrated + + def calibrate(self) -> None: + logger.info("🔧 Starting calibration for teleoperation...") + logger.info("Move each finger through its full range of motion.") + + fingers = {} + for finger in ["thumb", "index", "middle", "ring"]: + fingers[finger] = [motor for motor in self.bus.motors if motor.startswith(finger)] + + self.calibration = RangeFinderGUI(self.bus, fingers).run() + + # Set homing_offset to 0 for all motors (Protocol 1 doesn't support homing offset) + # Demo uses motor offsets applied to joint angles, not homing_offset + for motor in self.calibration: + self.calibration[motor].homing_offset = 0 + + # Demo has ALL motors with invert=false (drive_mode=0) + # Set all motors to normal direction + for motor in self.calibration: + self.calibration[motor].drive_mode = 0 + + self._save_calibration() + logger.info(f"✅ Calibration saved to {self.calibration_fpath}") + logger.info("Motor ranges recorded. These will be used for teleoperation.") + + def configure(self) -> None: + with self.bus.torque_disabled(): + self.bus.configure_motors() + + # Set speed and acceleration for faster, more responsive movement + # Maximum_Acceleration: 0-254, higher = faster acceleration (default ~50) + # Goal_Speed: 0-2047, higher = faster movement (default varies, typical ~100-300) + import os + max_accel = int(os.getenv("LEROBOT_MAX_ACCELERATION", "254")) # Max acceleration + goal_speed = int(os.getenv("LEROBOT_GOAL_SPEED", "2047")) # Maximum speed for fastest response + + logger.info(f"Setting motor speed parameters: Max_Accel={max_accel}, Goal_Speed={goal_speed}") + + for motor_name in self.bus.motors: + try: + # Set maximum acceleration for faster response + self.bus.write("Maximum_Acceleration", motor_name, max_accel) + + # Set goal speed for faster movement + self.bus.write("Goal_Speed", motor_name, goal_speed) + + logger.info(f" {motor_name}: Max_Accel={max_accel}, Goal_Speed={goal_speed}") + except Exception as e: + logger.warning(f" {motor_name}: Could not set speed parameters: {e}") + + # Explicitly enable torque after configuration + logger.info("Enabling torque on all motors...") + for motor_name in self.bus.motors: + self.bus.write("Torque_Enable", motor_name, 1) + torque_state = self.bus.read("Torque_Enable", motor_name) + logger.info(f" {motor_name}: Torque_Enable = {torque_state}") + logger.info("✅ All motors torque enabled") + + def setup_motors(self) -> None: + # TODO: add docstring + for motor in self.bus.motors: + input(f"Connect the controller board to the '{motor}' motor only and press enter.") + self.bus.setup_motor(motor) + print(f"'{motor}' motor id set to {self.bus.motors[motor].id}") + + def get_observation(self) -> dict[str, Any]: + if not self.is_connected: + raise DeviceNotConnectedError(f"{self} is not connected.") + + obs_dict = {} + + # Read hand position - skip motors with errors + start = time.perf_counter() + for motor in self.bus.motors: + try: + obs_dict[f"{motor}.pos"] = self.bus.read("Present_Position", motor) + except RuntimeError as e: + if "voltage error" in str(e).lower(): + logger.warning(f"{self}: Motor {motor} has voltage error, skipping read. Check power supply!") + obs_dict[f"{motor}.pos"] = 0.0 # Default value + else: + raise + dt_ms = (time.perf_counter() - start) * 1e3 + logger.debug(f"{self} read state: {dt_ms:.1f}ms") + + # Capture images from cameras + for cam_key, cam in self.cameras.items(): + start = time.perf_counter() + frame = cam.async_read() + # Flip camera horizontally for mirror view in Rerun + if frame is not None: + import cv2 + frame = cv2.flip(frame, 1) + obs_dict[cam_key] = frame + dt_ms = (time.perf_counter() - start) * 1e3 + logger.debug(f"{self} read {cam_key}: {dt_ms:.1f}ms") + + return obs_dict + + def send_action(self, action: dict[str, Any]) -> dict[str, Any]: + if not self.is_connected: + raise DeviceNotConnectedError(f"{self} is not connected.") + + import os + + # Check if action contains motor angles (replay mode) or tip vectors (teleop mode) + motor_angles = {} + for motor_name in self.bus.motors.keys(): + angle_key = f"{motor_name}.pos" + if angle_key in action: + motor_angles[motor_name] = float(action[angle_key]) + + # If motor angles are present (replay mode), use them directly + if motor_angles: + # Convert motor angles (radians) to motor positions (0-100 percent) + goal_pos = {} + IK_TO_DELTA_SCALE = float(os.getenv("LEROBOT_IK_DELTA_SCALE", "2.0")) + + for motor_name, angle_rad in motor_angles.items(): + if motor_name in self.bus.motors: + # Get neutral position for YOUR hand assembly + neutral_offset = self.motor_offsets.get(motor_name, 0.0) + + # Treat recorded angle as DELTA from neutral + delta_from_neutral = angle_rad * IK_TO_DELTA_SCALE + + # Final angle = neutral + delta + final_angle = neutral_offset + delta_from_neutral + + # Radians to percent: [-π, π] → [0, 100] + motor_percent = ((final_angle + np.pi) / (2 * np.pi)) * 100.0 + goal_pos[motor_name] = motor_percent + + if goal_pos: + try: + self.bus.sync_write("Goal_Position", goal_pos, normalize=True) + except Exception as e: + logger.error(f"sync_write failed: {e}") + + return action + + # Extract tip vectors from action (teleop mode - raw meters) + tip_vectors = {} + for finger in ["index", "middle", "ring", "thumb"]: + x_key = f"{finger}_tip_x.pos" + y_key = f"{finger}_tip_y.pos" + z_key = f"{finger}_tip_z.pos" + if x_key in action and y_key in action and z_key in action: + tip_vectors[finger] = np.array([float(action[x_key]), float(action[y_key]), float(action[z_key])]) + + if not tip_vectors: + return action + + # Run IK solver to convert tip vectors to motor angles + motor_angles = self._solve_ik(tip_vectors) + if not motor_angles: + return action + + # Convert motor angles (radians) to motor positions (0-100 percent) + # KEY INSIGHT: Your hand assembly has different neutral position than MuJoCo model + # - IK produces angles relative to MuJoCo's neutral (0 rad) + # - Your offsets define YOUR hand's neutral position + # - We want: IK angle = DELTA from neutral, not absolute angle + # Solution: Send (neutral_offset + IK_delta) where IK is scaled to be a delta + goal_pos = {} + raw_percents = {} # Track for logging + + # Scale factor to treat IK output as delta from neutral (not absolute) + # IK typically outputs -1.5 to +1.5 rad range + # We want this to be ±movement around neutral, not absolute position + IK_TO_DELTA_SCALE = float(os.getenv("LEROBOT_IK_DELTA_SCALE", "2.0")) # Increased for more range (was 1.5) + + for motor_name, angle_rad in motor_angles.items(): + if motor_name in self.bus.motors: + # Get neutral position for YOUR hand assembly + neutral_offset = self.motor_offsets.get(motor_name, 0.0) + + # Treat IK output as DELTA from neutral + delta_from_neutral = angle_rad * IK_TO_DELTA_SCALE + + # Final angle = neutral + delta + final_angle = neutral_offset + delta_from_neutral + + # Radians to percent: [-π, π] → [0, 100] + motor_percent = ((final_angle + np.pi) / (2 * np.pi)) * 100.0 + raw_percents[motor_name] = motor_percent + + # NO EMA SMOOTHING - send directly + goal_pos[motor_name] = motor_percent + + if goal_pos: + try: + self.bus.sync_write("Goal_Position", goal_pos, normalize=True) + except Exception as e: + logger.error(f"sync_write failed: {e}") + + + + # Replace tip vectors with motor angles for recording/visualization + # This is what gets recorded during dataset collection + # Remove tip vector keys (they were just intermediate data) + keys_to_remove = [k for k in action.keys() if 'tip' in k] + for k in keys_to_remove: + del action[k] + + # Add motor angles (this is what we actually control and want to record) + for motor_name, angle_rad in motor_angles.items(): + action[f"{motor_name}.pos"] = angle_rad + + return action + + def _update_mocap_targets(self, tip_vectors: dict[str, np.ndarray]): + """ + Update mocap body positions from tracked tip vectors (Demo write_mocap_pos pattern). + This pushes new target positions into the simulation without resetting state. + """ + scaled_positions = {} + for finger_name, tip_vec in tip_vectors.items(): + # Apply per-finger axis permutation if configured + if finger_name in self.axis_perm: + perm = self.axis_perm[finger_name] + tip_vec = tip_vec[perm] + # Apply per-finger axis flips if configured + if finger_name in self.axis_flips: + tip_vec = tip_vec * self.axis_flips[finger_name] + if finger_name in self.finger_name_to_index: + idx = self.finger_name_to_index[finger_name] + scale = self.tip_scales.get(idx, self.tip_scale) + scaled_pos = tip_vec * scale + self.tip_offsets[idx] + scaled_positions[finger_name] = scaled_pos + + # Update mocap body position directly (as in mj_mink_left.py write_mocap_pos) + mocap_body = f"finger{idx}_target" + body_id = mujoco.mj_name2id(self.mj_model, mujoco.mjtObj.mjOBJ_BODY, mocap_body) + if body_id >= 0: + mocap_id = self.mj_model.body_mocapid[body_id] + if mocap_id >= 0: + self.data.mocap_pos[mocap_id] = scaled_pos + + def _solve_ik(self, tip_vectors: dict[str, np.ndarray]) -> dict[str, float]: + """ + Run one IK integration step to smoothly move configuration toward mocap targets. + This is the core simulation loop from Demo: update mocap → set task targets → solve → integrate. + + Args: + tip_vectors: Dict mapping finger names to 3D tip position vectors + + Returns: + Dict mapping motor names to joint angles in radians + """ + # Step 1: Update mocap body positions from new tip vectors + self._update_mocap_targets(tip_vectors) + + # Step 2: Update each IK task target from its corresponding mocap body (Demo pattern) + for finger_idx, task in self.tip_tasks.items(): + mocap_body = f"finger{finger_idx}_target" + task.set_target( + mink.SE3.from_mocap_name(self.mj_model, self.data, mocap_body) + ) + + # Step 3: Build task list with stabilizing tasks + tasks = [self.eq_task, self.posture_task] + list(self.tip_tasks.values()) + + # Step 4: Solve IK and integrate configuration (Demo uses single integration per tick) + vel = mink.solve_ik( + self.configuration, + tasks, + self.sim_dt, + self.ik_solver, + self.ik_tolerance + ) + self.configuration.integrate_inplace(vel, self.sim_dt) + + # Update viewer if running (sync the visual to show current state) + if self.viewer is not None: + try: + if hasattr(self.viewer, 'is_running') and self.viewer.is_running(): + self.viewer.sync() + except Exception: + pass # Viewer in separate thread handles its own updates + + # Step 5: Read joint positions from integrated configuration + motor_angles = {} + name_map = { + 1: ("index_m1", "index_m2"), + 2: ("middle_m1", "middle_m2"), + 3: ("ring_m1", "ring_m2"), + 4: ("thumb_m1", "thumb_m2"), + } + for finger_idx, (m1, m2) in name_map.items(): + # Optionally swap motors for this finger if requested + finger_label = {1:"index",2:"middle",3:"ring",4:"thumb"}[finger_idx] + if finger_label in self.swap_fingers: + m1, m2 = m2, m1 + for motor_name, joint_suffix in ((m1, "motor1"), (m2, "motor2")): + joint_name = f"finger{finger_idx}_{joint_suffix}" + joint_id = mujoco.mj_name2id(self.mj_model, mujoco.mjtObj.mjOBJ_JOINT, joint_name) + if joint_id >= 0: + # Read joint angle from IK-integrated configuration + angle = self.data.joint(joint_id).qpos[0] + + # Optional angle amplification for more range (EXPERIMENTAL) + if hasattr(self, '_angle_scale') and self._angle_scale != 1.0: + angle *= self._angle_scale + + motor_angles[motor_name] = angle + + return motor_angles + + def disconnect(self): + if not self.is_connected: + raise DeviceNotConnectedError(f"{self} is not connected.") + + # Close MuJoCo viewer if running + if self.viewer is not None: + try: + self.viewer.close() + logger.info(f"{self}: MuJoCo viewer closed.") + except Exception as e: + logger.warning(f"{self}: Error closing MuJoCo viewer: {e}") + self.viewer = None + + # Stop viewer thread if running + if self._viewer_thread is not None: + self._viewer_stop = True + self._viewer_thread.join(timeout=2.0) + logger.info(f"{self}: Viewer thread stopped.") + self._viewer_thread = None + + self.bus.disconnect(self.config.disable_torque_on_disconnect) + for cam in self.cameras.values(): + cam.disconnect() + + logger.info(f"{self} disconnected.") From 9da45ec9976c3f528e904f40252a0b2e9b4d68ac Mon Sep 17 00:00:00 2001 From: raiddanial Date: Tue, 6 Jan 2026 10:12:57 +0800 Subject: [PATCH 03/12] new file: AMAZINGHAND_SETUP.md new file: RECALIBRATION_GUIDE.md new file: calibrate_motor_offsets.py new file: delete_calibration_files.py new file: expand_calibration_ranges.py new file: main_rs_backup.txt modified: pyproject.toml new file: requirements-amazinghand.txt modified: src/lerobot/__init__.py modified: src/lerobot/cameras/opencv/camera_opencv.py new file: src/lerobot/configs/robot/amazinghand/mjcf/config.json modified: src/lerobot/policies/act/configuration_act.py modified: src/lerobot/robots/__init__.py new file: src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/config.json new file: src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/config.json new file: src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/__init__.py new file: src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/amazinghand.mdx modified: src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/amazinghand.py new file: src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/config/l_hand.toml new file: src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/config_amazinghand.py new file: src/lerobot/robots/lerobot_robot_amazinghand/setup.py modified: src/lerobot/robots/stretch3/configuration_stretch3.py modified: src/lerobot/robots/utils.py new file: src/lerobot/scripts/5gtest/import lerobot.py new file: src/lerobot/scripts/5gtest/inspect_so101.py new file: src/lerobot/scripts/5gtest/leader_so101_sender.py new file: src/lerobot/scripts/5gtest/pc1_camera_client.py new file: src/lerobot/scripts/5gtest/pc1_leader_sender.py new file: src/lerobot/scripts/5gtest/test_import_so101.py modified: src/lerobot/scripts/lerobot_calibrate.py modified: src/lerobot/scripts/lerobot_record.py modified: src/lerobot/scripts/lerobot_replay.py new file: src/lerobot/scripts/lerobot_run_policy.py modified: src/lerobot/scripts/lerobot_teleoperate.py new file: src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/__init__.py new file: src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/amazinghandtracker.py new file: src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/config_amazinghandtracker.py new file: src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/inverse_kinematics.py new file: src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/config.json new file: src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/setup.py modified: src/lerobot/teleoperators/utils.py modified: src/lerobot/utils/import_utils.py modified: src/lerobot/utils/visualization_utils.py --- AMAZINGHAND_SETUP.md | 331 ++++++++++ RECALIBRATION_GUIDE.md | 0 calibrate_motor_offsets.py | 151 +++++ delete_calibration_files.py | 116 ++++ expand_calibration_ranges.py | 78 +++ main_rs_backup.txt | Bin 0 -> 13342 bytes pyproject.toml | 1 + requirements-amazinghand.txt | 30 + src/lerobot/__init__.py | 3 +- src/lerobot/cameras/opencv/camera_opencv.py | 2 +- .../robot/amazinghand/mjcf/config.json | 24 + src/lerobot/policies/act/configuration_act.py | 2 +- src/lerobot/robots/__init__.py | 50 ++ .../AH_Left/mjcf/config.json | 24 + .../AH_Right/mjcf/config.json | 24 + .../lerobot_robot_amazinghand/__init__.py | 18 + .../lerobot_robot_amazinghand/amazinghand.mdx | 1 + .../lerobot_robot_amazinghand/amazinghand.py | 77 ++- .../config/l_hand.toml | 54 ++ .../config_amazinghand.py | 49 ++ .../robots/lerobot_robot_amazinghand/setup.py | 29 + .../robots/stretch3/configuration_stretch3.py | 4 +- src/lerobot/robots/utils.py | 4 + src/lerobot/scripts/5gtest/import lerobot.py | 7 + src/lerobot/scripts/5gtest/inspect_so101.py | 19 + .../scripts/5gtest/leader_so101_sender.py | 37 ++ .../scripts/5gtest/pc1_camera_client.py | 65 ++ .../scripts/5gtest/pc1_leader_sender.py | 67 ++ .../scripts/5gtest/test_import_so101.py | 16 + src/lerobot/scripts/lerobot_calibrate.py | 21 + src/lerobot/scripts/lerobot_record.py | 19 +- src/lerobot/scripts/lerobot_replay.py | 17 + src/lerobot/scripts/lerobot_run_policy.py | 224 +++++++ src/lerobot/scripts/lerobot_teleoperate.py | 27 +- .../__init__.py | 19 + .../amazinghandtracker.py | 574 ++++++++++++++++++ .../config_amazinghandtracker.py | 53 ++ .../inverse_kinematics.py | 274 +++++++++ .../mjcf/config.json | 24 + .../setup.py | 29 + src/lerobot/teleoperators/utils.py | 4 + src/lerobot/utils/import_utils.py | 2 +- src/lerobot/utils/visualization_utils.py | 20 + 43 files changed, 2558 insertions(+), 32 deletions(-) create mode 100644 AMAZINGHAND_SETUP.md create mode 100644 RECALIBRATION_GUIDE.md create mode 100644 calibrate_motor_offsets.py create mode 100644 delete_calibration_files.py create mode 100644 expand_calibration_ranges.py create mode 100644 main_rs_backup.txt create mode 100644 requirements-amazinghand.txt create mode 100644 src/lerobot/configs/robot/amazinghand/mjcf/config.json create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/config.json create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/config.json create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/__init__.py create mode 120000 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/amazinghand.mdx create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/config/l_hand.toml create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/config_amazinghand.py create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/setup.py create mode 100644 src/lerobot/scripts/5gtest/import lerobot.py create mode 100644 src/lerobot/scripts/5gtest/inspect_so101.py create mode 100644 src/lerobot/scripts/5gtest/leader_so101_sender.py create mode 100644 src/lerobot/scripts/5gtest/pc1_camera_client.py create mode 100644 src/lerobot/scripts/5gtest/pc1_leader_sender.py create mode 100644 src/lerobot/scripts/5gtest/test_import_so101.py create mode 100644 src/lerobot/scripts/lerobot_run_policy.py create mode 100644 src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/__init__.py create mode 100644 src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/amazinghandtracker.py create mode 100644 src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/config_amazinghandtracker.py create mode 100644 src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/inverse_kinematics.py create mode 100644 src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/config.json create mode 100644 src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/setup.py diff --git a/AMAZINGHAND_SETUP.md b/AMAZINGHAND_SETUP.md new file mode 100644 index 00000000000..80288922fe3 --- /dev/null +++ b/AMAZINGHAND_SETUP.md @@ -0,0 +1,331 @@ +# AmazingHand Robot Setup Guide + +## Quick Start + +### 1. Install Dependencies + +```bash +# Install base LeRobot package with AmazingHand dependencies +pip install -r requirements-amazinghand.txt +``` + +### 2. Hardware Setup + +- Connect AmazingHand robot to serial port (e.g., COM5 on Windows) +- Connect two cameras: + - Camera 0: Robot observation camera + - Camera 1: Hand tracking camera for teleoperation + +### 3. Configure Hand Side + +The robot supports both left and right hand configurations: + +```bash +# For left hand (default) +--robot.side=left --robot.port=COM5 + +# For right hand +--robot.side=right --robot.port=COM6 +``` + +**Motor ID Mapping:** +- Left hand: IDs 15, 16, 13, 14, 11, 12, 17, 18 +- Right hand: IDs 5, 6, 3, 4, 1, 2, 7, 8 (left hand IDs - 10) + +### 4. Teleoperation + +**Windows PowerShell:** +```powershell +lerobot-teleoperate ` + --robot.type=lerobot_robot_amazinghand ` + --teleop.type=lerobot_teleoperator_amazinghandtracker ` + --robot.port= ` + --robot.side= ` + --robot.baudrate=1000000 ` + --robot.cameras="{ cam_0: {type: opencv, index_or_path: , width: 640, height: 480, fps: 30}}" ` + --teleop.side= ` + --teleop.camera_id= ` + --teleop.cameras="{ cam_1: {type: opencv, index_or_path: , width: 640, height: 480, fps: 30}}" ` + --display_data=true +``` + +**Linux/Mac:** +```bash +lerobot-teleoperate \ + --robot.type=lerobot_robot_amazinghand \ + --teleop.type=lerobot_teleoperator_amazinghandtracker \ + --robot.port= \ + --robot.side= \ + --robot.baudrate=1000000 \ + --robot.cameras="{ cam_0: {type: opencv, index_or_path: , width: 640, height: 480, fps: 30}}" \ + --teleop.side= \ + --teleop.camera_id= \ + --teleop.cameras="{ cam_1: {type: opencv, index_or_path: , width: 640, height: 480, fps: 30}}" \ + --display_data=true +``` + +**Example:** +- Windows: `--robot.port=COM5`, camera IDs: `0` and `1` +- Linux: `--robot.port=/dev/ttyUSB0`, camera IDs: `0` and `1` + +### 5. Recording Episodes + +**Windows PowerShell:** +```powershell +lerobot-record ` + --robot.type=lerobot_robot_amazinghand ` + --teleop.type=lerobot_teleoperator_amazinghandtracker ` + --robot.port= ` + --robot.side= ` + --robot.baudrate=1000000 ` + --robot.cameras='{ "cam_0": {"type": "opencv", "index_or_path": , "width": 640, "height": 480, "fps": 30} }' ` + --teleop.side= ` + --teleop.camera_id= ` + --teleop.cameras='{ "cam_1": {"type": "opencv", "index_or_path": , "width": 640, "height": 480, "fps": 30} }' ` + --display_data=true ` + --dataset.repo_id="/" ` + --dataset.num_episodes= ` + --dataset.single_task="" +``` + +**Linux/Mac:** +```bash +lerobot-record \ + --robot.type=lerobot_robot_amazinghand \ + --teleop.type=lerobot_teleoperator_amazinghandtracker \ + --robot.port= \ + --robot.side= \ + --robot.baudrate=1000000 \ + --robot.cameras='{ "cam_0": {"type": "opencv", "index_or_path": , "width": 640, "height": 480, "fps": 30} }' \ + --teleop.side= \ + --teleop.camera_id= \ + --teleop.cameras='{ "cam_1": {"type": "opencv", "index_or_path": , "width": 640, "height": 480, "fps": 30} }' \ + --display_data=true \ + --dataset.repo_id="/" \ + --dataset.num_episodes= \ + --dataset.single_task="" +``` + +**Example:** +- `--dataset.repo_id="raiddanial/grasp-cube"` +- `--dataset.num_episodes=50` +- `--dataset.single_task="Grasp the purple cube"` + +### 6. Training + +#### ACT Policy (Recommended for 2GB VRAM) + +**Windows PowerShell:** +```powershell +lerobot-train ` + --dataset.repo_id="/" ` + --policy.type=act ` + --output_dir= ` + --job_name= ` + --policy.device=cuda ` + --wandb.enable= ` + --batch_size=2 ` + --save_freq=2000 ` + --steps= +``` + +**Linux/Mac:** +```bash +python -m lerobot.scripts.lerobot_train \ + --dataset.repo_id="/" \ + --policy.type=act \ + --output_dir= \ + --job_name= \ + --policy.device=cuda \ + --wandb.enable= \ + --batch_size=2 \ + --save_freq=2000 \ + --steps= +``` + +**Example:** +- Windows output: `D:\lerobot_train\act_grasp_cube` +- Linux output: `./outputs/train/act_grasp_cube` +- Steps: `50000` (typical for ACT) + +#### SmolVLA Policy (Requires more VRAM) + +**Windows PowerShell:** +```powershell +lerobot-train ` + --dataset.repo_id="/" ` + --policy.type=smolvla ` + --output_dir= ` + --job_name= ` + --policy.device=cuda ` + --wandb.enable= ` + --batch_size=1 ` + --save_freq=2000 ` + --steps= ` + --policy.push_to_hub=false +``` + +**Linux/Mac:** +```bash +python -m lerobot.scripts.lerobot_train \ + --dataset.repo_id="/" \ + --policy.type=smolvla \ + --output_dir= \ + --job_name= \ + --policy.device=cuda \ + --wandb.enable= \ + --batch_size=1 \ + --save_freq=2000 \ + --steps= \ + --policy.push_to_hub=false +``` + +**Example:** +- Batch size: `1` (for 2GB VRAM GPUs like GTX 1050 Ti) +- Steps: `10000-50000` (depending on dataset size) + +### 7. Replay Episodes + +**Windows PowerShell:** +```powershell +lerobot-replay ` + --robot.type=lerobot_robot_amazinghand ` + --robot.port= ` + --robot.side= ` + --robot.baudrate=1000000 ` + --robot.cameras='{ "cam_0": {"type": "opencv", "index_or_path": , "width": 640, "height": 480, "fps": 30} }' ` + --dataset.repo_id="/" ` + --dataset.episode= +``` + +**Linux/Mac:** +```bash +lerobot-replay \ + --robot.type=lerobot_robot_amazinghand \ + --robot.port= \ + --robot.side= \ + --robot.baudrate=1000000 \ + --robot.cameras='{ "cam_0": {"type": "opencv", "index_or_path": , "width": 640, "height": 480, "fps": 30} }' \ + --dataset.repo_id="/" \ + --dataset.episode= +``` + +**Example:** +- Episode indices start at `0` +- Use this to test recorded episodes before training + +### 8. Evaluation with Policy + +**Windows PowerShell:** +```powershell +lerobot-record ` + --robot.type=lerobot_robot_amazinghand ` + --robot.port= ` + --robot.side= ` + --robot.baudrate=1000000 ` + --robot.cameras="{ cam_0: {type: opencv, index_or_path: , width: 640, height: 480, fps: 30}}" ` + --display_data=true ` + --dataset.repo_id="/" ` + --dataset.num_episodes= ` + --dataset.episode_time_s= ` + --dataset.reset_time_s= ` + --dataset.single_task="" ` + --policy.path= ` + --dataset.push_to_hub=false +``` + +**Linux/Mac:** +```bash +lerobot-record \ + --robot.type=lerobot_robot_amazinghand \ + --robot.port= \ + --robot.side= \ + --robot.baudrate=1000000 \ + --robot.cameras="{ cam_0: {type: opencv, index_or_path: , width: 640, height: 480, fps: 30}}" \ + --display_data=true \ + --dataset.repo_id="/" \ + --dataset.num_episodes= \ + --dataset.episode_time_s= \ + --dataset.reset_time_s= \ + --dataset.single_task="" \ + --policy.path= +``` + +**Example:** +- Windows checkpoint: `D:\lerobot_train\act_grasp_cube\checkpoints\last\pretrained_model` +- Linux checkpoint: `./outputs/train/act/checkpoints/last/pretrained_model` +- Episode time: `600` seconds (10 minutes) +- Reset time: `60` seconds (1 minute between episodes) +- Num episodes: `10` (typical evaluation run) + +## Optional Features + +### MuJoCo Visualization + +Enable real-time MuJoCo simulation viewer: + +```bash +$env:LEROBOT_SHOW_MUJOCO_VIEWER="1" +``` + +Then run teleoperation/recording as usual. A 3D viewer window will show the simulated hand. + +### Performance Tuning + +The robot is configured for maximum speed: +- Goal_Speed: 2047 (max) +- Maximum_Acceleration: 254 (max) +- IK_TO_DELTA_SCALE: 2.0 + +To adjust, modify the values in `amazinghand.py` or use environment variables. + +## Troubleshooting + +### Import Errors +If you get `ModuleNotFoundError: No module named 'mujoco'`: +```bash +pip install mujoco mink mediapipe +``` + +### CUDA Not Detected +Use the full Python path when training: +```bash +python -m lerobot.scripts.lerobot_train ... +``` +Not: `lerobot-train ...` (executable may use wrong Python environment) + +### Disk Space Issues +Training outputs can be large. Use a drive with sufficient space: +```bash +--output_dir=D:\lerobot_train\your_experiment +``` + +### Motor Connection Issues +1. Check COM port in Device Manager +2. Verify baudrate (default: 1000000) +3. Test with: `python -c "from lerobot.robots.lerobot_robot_amazinghand.lerobot_robot_amazinghand import AmazingHand"` + +## File Structure + +``` +msf_lerobot/ +├── requirements-amazinghand.txt # This file's dependencies +├── src/lerobot/robots/ +│ └── lerobot_robot_amazinghand/ +│ └── lerobot_robot_amazinghand/ +│ ├── AH_Left/ # Left hand MJCF model +│ │ └── mjcf/scene.xml +│ ├── AH_Right/ # Right hand MJCF model +│ │ └── mjcf/scene.xml +│ ├── amazinghand.py # Robot driver +│ └── config_amazinghand.py # Robot config +└── src/lerobot/teleoperators/ + └── lerobot_teleoperator_amazinghandtracker/ + └── lerobot_teleoperator_amazinghandtracker/ + ├── amazinghandtracker.py # Teleoperation tracker + └── config_amazinghandtracker.py +``` + +## Citation + +If you use this code, please cite the LeRobot project and acknowledge the AmazingHand hardware. diff --git a/RECALIBRATION_GUIDE.md b/RECALIBRATION_GUIDE.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/calibrate_motor_offsets.py b/calibrate_motor_offsets.py new file mode 100644 index 00000000000..71a541956d1 --- /dev/null +++ b/calibrate_motor_offsets.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python3 +""" +Motor Offset Calibration Tool for AmazingHand + +This tool helps you calibrate motor offsets by: +1. Reading current motor positions with the hand in neutral pose +2. Computing the offsets needed to zero out the IK angles +3. Saving to a TOML file + +Usage: +1. Manually position the robot hand in its neutral/open pose +2. Run this script: python calibrate_motor_offsets.py +3. The script will read current positions and compute offsets +4. Offsets are saved to config/motor_offsets.toml +""" + +import argparse +import logging +from pathlib import Path +import numpy as np + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + + +def calibrate_offsets(port: str = "/dev/ttyUSB0", output_path: str = None): + """Calibrate motor offsets by reading current positions in neutral pose""" + from lerobot.motors.feetech import FeetechMotorsBus + from lerobot.motors import Motor, MotorNormMode + + logger.info("=" * 60) + logger.info("AmazingHand Motor Offset Calibration Tool") + logger.info("=" * 60) + + # Create motor bus + logger.info(f"Connecting to motors on {port}...") + bus = FeetechMotorsBus( + port=port, + motors={ + "index_m1": Motor(15, "scs0009", MotorNormMode.RANGE_0_100), + "index_m2": Motor(16, "scs0009", MotorNormMode.RANGE_0_100), + "middle_m1": Motor(13, "scs0009", MotorNormMode.RANGE_0_100), + "middle_m2": Motor(14, "scs0009", MotorNormMode.RANGE_0_100), + "ring_m1": Motor(11, "scs0009", MotorNormMode.RANGE_0_100), + "ring_m2": Motor(12, "scs0009", MotorNormMode.RANGE_0_100), + "thumb_m1": Motor(17, "scs0009", MotorNormMode.RANGE_0_100), + "thumb_m2": Motor(18, "scs0009", MotorNormMode.RANGE_0_100), + }, + ) + + try: + bus.connect() + logger.info("✓ Connected to motors") + + # Instructions + print("\n" + "=" * 60) + print("INSTRUCTIONS:") + print("1. Manually position the robot hand in NEUTRAL/OPEN pose") + print(" - All fingers should be straight and open") + print(" - This is the 'zero' position the IK solver expects") + print("2. Press ENTER when ready to measure positions...") + print("=" * 60) + input() + + # Read current positions + logger.info("Reading current motor positions...") + positions = bus.sync_read("Present_Position") + + # Convert positions to radians (assuming 0-100 scale maps to 0-4095 raw) + # For SCS0009: 0% = 0 rad, 50% = π rad, 100% = 2π rad + # So: radians = (percent / 100) * 2π - π + logger.info("\nCurrent positions (in neutral pose):") + offsets = {} + for motor_name, pos_percent in sorted(positions.items()): + # Convert percent to radians + angle_rad = (pos_percent / 100.0) * 2 * np.pi - np.pi + + # Offset is the NEGATIVE of the current angle + # (we want to ADD this offset to zero out the angle) + offset = -angle_rad + offsets[motor_name] = offset + + logger.info( + f" {motor_name}: {pos_percent:.2f}% = {angle_rad:+.4f}rad " + f"→ offset = {offset:+.4f}rad ({offset * 180/np.pi:+.2f}°)" + ) + + # Generate TOML content + finger_map = { + ("index_m1", "index_m2"): "l_finger1", + ("middle_m1", "middle_m2"): "l_finger2", + ("ring_m1", "ring_m2"): "l_finger3", + ("thumb_m1", "thumb_m2"): "l_finger4", + } + + toml_content = "# Motor offsets for AmazingHand (Left)\n" + toml_content += "# Generated by calibrate_motor_offsets.py\n" + toml_content += "# These offsets center the robot's neutral position\n\n" + + for (m1, m2), finger_name in finger_map.items(): + toml_content += f"[[motors]]\n" + toml_content += f'finger_name = "{finger_name}"\n\n' + toml_content += f"[motors.motor1]\n" + toml_content += f"offset = {offsets[m1]:.4f}\n" + toml_content += f"invert = false\n\n" + toml_content += f"[motors.motor2]\n" + toml_content += f"offset = {offsets[m2]:.4f}\n" + toml_content += f"invert = false\n\n" + + # Save to file + if output_path is None: + output_path = Path(__file__).parent / "src" / "lerobot" / "robots" / "lerobot_robot_amazinghand" / "lerobot_robot_amazinghand" / "config" / "motor_offsets.toml" + else: + output_path = Path(output_path) + + output_path.parent.mkdir(parents=True, exist_ok=True) + + with open(output_path, "w") as f: + f.write(toml_content) + + logger.info(f"\n✓ Offsets saved to: {output_path}") + logger.info("\nTo use these offsets:") + logger.info(f" export LEROBOT_MOTOR_OFFSETS_PATH={output_path.absolute()}") + logger.info(" OR place the file in the default location shown above") + + print("\n" + "=" * 60) + print("Calibration complete!") + print("=" * 60) + + finally: + bus.disconnect() + logger.info("Disconnected from motors") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Calibrate AmazingHand motor offsets") + parser.add_argument( + "--port", + type=str, + default="/dev/ttyUSB0", + help="Serial port for motors (default: /dev/ttyUSB0)" + ) + parser.add_argument( + "--output", + type=str, + default=None, + help="Output path for TOML file (default: auto-detect)" + ) + + args = parser.parse_args() + calibrate_offsets(port=args.port, output_path=args.output) diff --git a/delete_calibration_files.py b/delete_calibration_files.py new file mode 100644 index 00000000000..492478fae8a --- /dev/null +++ b/delete_calibration_files.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 +""" +Delete calibration files that limit range of motion for teleoperation. + +These calibration files are generated during recording sessions and clamp +motor positions and tip vectors to recorded ranges. This prevents full +range of motion during teleoperation. + +This script safely backs up and deletes them. +""" + +import argparse +import logging +from pathlib import Path +import shutil +import json + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + + +def delete_calibrations(backup: bool = True, dry_run: bool = False): + """Delete calibration files that limit teleoperation range""" + + logger.info("=" * 60) + logger.info("AmazingHand Calibration File Cleanup") + logger.info("=" * 60) + + calib_dir = Path.home() / ".cache" / "huggingface" / "lerobot" / "calibration" + + # Find calibration files + patterns = [ + "teleoperators/lerobot_teleoperator_amazinghandtracker/**/*.json", + "robots/lerobot_robot_amazinghand/**/*.json", + ] + + files_to_delete = [] + for pattern in patterns: + files_to_delete.extend(calib_dir.glob(pattern)) + + if not files_to_delete: + logger.info("✓ No calibration files found - already clean!") + return + + logger.info(f"\nFound {len(files_to_delete)} calibration file(s):") + for f in files_to_delete: + # Read and show what's being limited + try: + with open(f) as fp: + data = json.load(fp) + + # Show range limits + first_key = list(data.keys())[0] + range_info = data[first_key] + logger.info(f"\n {f.relative_to(calib_dir)}") + logger.info(f" Limits: range_min={range_info.get('range_min')}, " + f"range_max={range_info.get('range_max')}") + logger.info(f" ⚠️ This CLAMPS values during teleoperation!") + except Exception as e: + logger.warning(f" Could not read {f}: {e}") + + if dry_run: + logger.info("\n[DRY RUN] Would delete these files (use --no-dry-run to actually delete)") + return + + # Backup if requested + if backup: + backup_dir = calib_dir.parent / "calibration_backup" + backup_dir.mkdir(parents=True, exist_ok=True) + + logger.info(f"\nBacking up to: {backup_dir}") + for f in files_to_delete: + rel_path = f.relative_to(calib_dir) + backup_path = backup_dir / rel_path + backup_path.parent.mkdir(parents=True, exist_ok=True) + shutil.copy2(f, backup_path) + logger.info(f" ✓ Backed up: {rel_path}") + + # Delete + logger.info("\nDeleting calibration files...") + for f in files_to_delete: + f.unlink() + logger.info(f" ✓ Deleted: {f.relative_to(calib_dir)}") + + logger.info("\n" + "=" * 60) + logger.info("✓ Calibration files deleted!") + logger.info("=" * 60) + logger.info("\nYour robot should now have FULL range of motion.") + logger.info("The robot will use full servo range (0-4095) automatically.") + + if backup: + logger.info(f"\nBackup saved to: {backup_dir}") + logger.info("You can restore by copying files back if needed.") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Delete calibration files that limit teleoperation range" + ) + parser.add_argument( + "--no-backup", + action="store_true", + help="Don't create backup (default: create backup)" + ) + parser.add_argument( + "--no-dry-run", + action="store_true", + help="Actually delete files (default: dry run)" + ) + + args = parser.parse_args() + + delete_calibrations( + backup=not args.no_backup, + dry_run=not args.no_dry_run + ) diff --git a/expand_calibration_ranges.py b/expand_calibration_ranges.py new file mode 100644 index 00000000000..34c48c9827b --- /dev/null +++ b/expand_calibration_ranges.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 +""" +Expand calibration ranges for AmazingHand teleoperation. + +The calibration GUI records min/max positions during calibration. +If you didn't move fingers through their full range, the recorded ranges are too small. +This script expands the ranges while keeping a safety margin from hardware limits (0-4095). +""" + +import json +from pathlib import Path + +# Path to calibration file +calib_path = Path.home() / ".cache" / "huggingface" / "lerobot" / "calibration" / "robots" / "lerobot_robot_amazinghand" / "None.json" + +if not calib_path.exists(): + print(f"❌ Calibration file not found: {calib_path}") + print("Run teleoperation first to generate calibration.") + exit(1) + +# Load current calibration +with open(calib_path) as f: + calib = json.load(f) + +print("Current calibration ranges:") +print("=" * 60) +for motor, config in calib.items(): + current_range = config["range_max"] - config["range_min"] + current_pct = (current_range / 4095) * 100 + print(f"{motor:12s}: {config['range_min']:4d} - {config['range_max']:4d} (range={current_range:4d}, {current_pct:5.1f}% of full)") + +print("\n" + "=" * 60) +print("Expanding ranges to ~80% of full range (safety margin)...") +print("This gives max range while protecting against hardware limits.") +print("=" * 60) + +# Expand ranges: use center ± 40% of full range (80% total) +# Full range is 0-4095, so 80% = 3276 positions +EXPANSION_FACTOR = 0.8 # Use 80% of full 0-4095 range +FULL_RANGE = 4095 +EXPANDED_RANGE = int(FULL_RANGE * EXPANSION_FACTOR) +MARGIN = (FULL_RANGE - EXPANDED_RANGE) // 2 + +for motor, config in calib.items(): + # Find current center + current_center = (config["range_min"] + config["range_max"]) // 2 + + # Expand around center + new_min = max(0, current_center - EXPANDED_RANGE // 2) + new_max = min(FULL_RANGE, current_center + EXPANDED_RANGE // 2) + + # Ensure we don't exceed bounds + if new_min < MARGIN: + new_min = MARGIN + if new_max > FULL_RANGE - MARGIN: + new_max = FULL_RANGE - MARGIN + + old_range = config["range_max"] - config["range_min"] + new_range = new_max - new_min + + config["range_min"] = new_min + config["range_max"] = new_max + + print(f"{motor:12s}: {new_min:4d} - {new_max:4d} (range={new_range:4d}, {old_range:4d} → {new_range:4d} = +{new_range-old_range:4d})") + +# Backup original +backup_path = calib_path.with_suffix(".json.backup") +with open(backup_path, "w") as f: + json.dump(calib, f, indent=2) +print(f"\n✅ Original calibration backed up to: {backup_path}") + +# Save expanded calibration +with open(calib_path, "w") as f: + json.dump(calib, f, indent=2) +print(f"✅ Expanded calibration saved to: {calib_path}") +print("\nRestart teleoperation to use the expanded ranges!") +print("If motors hit limits or behave strangely, restore backup:") +print(f" Copy-Item '{backup_path}' '{calib_path}' -Force") diff --git a/main_rs_backup.txt b/main_rs_backup.txt new file mode 100644 index 0000000000000000000000000000000000000000..a8b05036ecd3ae23907190572b8ff0aa20677ba2 GIT binary patch literal 13342 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 literal 0 HcmV?d00001 diff --git a/pyproject.toml b/pyproject.toml index e7727700c6e..2747498f34d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -167,6 +167,7 @@ lerobot-find-cameras="lerobot.scripts.lerobot_find_cameras:main" lerobot-find-port="lerobot.scripts.lerobot_find_port:main" lerobot-record="lerobot.scripts.lerobot_record:main" lerobot-replay="lerobot.scripts.lerobot_replay:main" +lerobot-run-policy="lerobot.scripts.lerobot_run_policy:main" lerobot-setup-motors="lerobot.scripts.lerobot_setup_motors:main" lerobot-teleoperate="lerobot.scripts.lerobot_teleoperate:main" lerobot-eval="lerobot.scripts.lerobot_eval:main" diff --git a/requirements-amazinghand.txt b/requirements-amazinghand.txt new file mode 100644 index 00000000000..d70b2869e07 --- /dev/null +++ b/requirements-amazinghand.txt @@ -0,0 +1,30 @@ +# Requirements for AmazingHand Robot +# Install with: pip install -r requirements-amazinghand.txt + +# Core LeRobot dependencies (base package) +-e . + +# Robot-specific dependencies +mujoco>=3.3.6 +mink>=0.0.13 +mediapipe>=0.10.14 +feetech-servo-sdk>=1.0.0 + +# IK solver dependencies +qpsolvers[quadprog]>=4.8.0 +quadprog>=0.1.11 +daqp>=0.5.1 + +# Training dependencies for SmolVLA +transformers>=4.57.0 +num2words>=0.5.13 +accelerate>=0.20.0 +tokenizers>=0.20.0 + +# Additional useful packages +opencv-contrib-python>=4.11.0 +scipy>=1.15.0 +numpy>=1.26.0,<2.0.0 # Compatible with both opencv and other deps + +# Optional: For visualization +matplotlib>=3.10.0 diff --git a/src/lerobot/__init__.py b/src/lerobot/__init__.py index eec574296c3..3681960c87e 100644 --- a/src/lerobot/__init__.py +++ b/src/lerobot/__init__.py @@ -165,7 +165,8 @@ "koch_bimanual", "aloha", "so100", - "so101", + "so101" + "lerobot_robot_amazinghand", ] # lists all available cameras from `lerobot/cameras` diff --git a/src/lerobot/cameras/opencv/camera_opencv.py b/src/lerobot/cameras/opencv/camera_opencv.py index 50e55f0c22b..d5b609b520b 100644 --- a/src/lerobot/cameras/opencv/camera_opencv.py +++ b/src/lerobot/cameras/opencv/camera_opencv.py @@ -419,7 +419,7 @@ def _stop_read_thread(self) -> None: self.thread = None self.stop_event = None - def async_read(self, timeout_ms: float = 200) -> np.ndarray: + def async_read(self, timeout_ms: float = 1000) -> np.ndarray: """ Reads the latest available frame asynchronously. diff --git a/src/lerobot/configs/robot/amazinghand/mjcf/config.json b/src/lerobot/configs/robot/amazinghand/mjcf/config.json new file mode 100644 index 00000000000..0649e926bdc --- /dev/null +++ b/src/lerobot/configs/robot/amazinghand/mjcf/config.json @@ -0,0 +1,24 @@ +{ + // Document URL, MuJoCo output + "url": "https://cad.onshape.com/documents/430ff184cf3dd9557aaff2be/w/e3658b7152c139971d22c688/e/a30f3af671aa926642803c67", + "output_format": "mujoco", + + "ignore": { + "*": "collision", + }, + + // Disable the freejoint to fix the robot + "freejoint": false, + + "additional_xml": ["joints_properties.xml", "additional.xml"], + + // Don't create actuators for passive joints + "joint_properties": { + "passive*": {"actuated": false}, + "motor*": { + "class": "chosen_actuator" + } + } + + +} diff --git a/src/lerobot/policies/act/configuration_act.py b/src/lerobot/policies/act/configuration_act.py index 6f6c1c4be86..9160877dda0 100644 --- a/src/lerobot/policies/act/configuration_act.py +++ b/src/lerobot/policies/act/configuration_act.py @@ -93,7 +93,7 @@ class ACTConfig(PreTrainedConfig): # Input / output structure. n_obs_steps: int = 1 chunk_size: int = 100 - n_action_steps: int = 100 + n_action_steps: int = 50 normalization_mapping: dict[str, NormalizationMode] = field( default_factory=lambda: { diff --git a/src/lerobot/robots/__init__.py b/src/lerobot/robots/__init__.py index 1dba0f1b089..a2c96fa3d19 100644 --- a/src/lerobot/robots/__init__.py +++ b/src/lerobot/robots/__init__.py @@ -17,3 +17,53 @@ from .config import RobotConfig from .robot import Robot from .utils import make_robot_from_config +import importlib +import pkgutil + +# Ensure all robot subpackages are imported so their config classes get registered +# with RobotConfig (via the @RobotConfig.register_subclass decorator). This +# makes sure dynamic choice lists (e.g. --robot.type) include third-party +# or local robot implementations when the package is imported (such as by +# console scripts). +for _finder, pkg_name, _ispkg in pkgutil.iter_modules(__path__, __name__ + "."): + try: + mod = importlib.import_module(pkg_name) + # If the discovered package itself contains submodules (e.g. the + # project layout places the actual Python package inside a folder + # with the same name), import its submodules as well so any + # registration performed in those submodules runs. + if hasattr(mod, "__path__"): + for _f2, sub_name, _is2 in pkgutil.iter_modules(mod.__path__, mod.__name__ + "."): + try: + importlib.import_module(sub_name) + except Exception: + # ignore failures at this level; they'll surface when that + # specific robot is actually used + pass + # Additionally try to import a nested package that shares the same + # last path component. Some plugins are packaged as + # //... (an outer folder with the same name as the inner + # package). In that case, importing the outer namespace package + # doesn't automatically import the inner package; try to import + # it explicitly. + try: + for p in list(mod.__path__): + # look for a directory named like the package inside this path + import os + + inner_name = mod.__name__.split(".")[-1] + candidate = os.path.join(p, inner_name) + if os.path.isdir(candidate): + # Form the nested import path and try to import it. + nested = f"{mod.__name__}.{inner_name}" + try: + importlib.import_module(nested) + except Exception: + pass + except Exception: + pass + except Exception: + # Don't fail import of the top-level package if a single robot submodule + # has issues. The specific submodule import error will surface when + # that robot is actually used. + pass diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/config.json b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/config.json new file mode 100644 index 00000000000..0649e926bdc --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/config.json @@ -0,0 +1,24 @@ +{ + // Document URL, MuJoCo output + "url": "https://cad.onshape.com/documents/430ff184cf3dd9557aaff2be/w/e3658b7152c139971d22c688/e/a30f3af671aa926642803c67", + "output_format": "mujoco", + + "ignore": { + "*": "collision", + }, + + // Disable the freejoint to fix the robot + "freejoint": false, + + "additional_xml": ["joints_properties.xml", "additional.xml"], + + // Don't create actuators for passive joints + "joint_properties": { + "passive*": {"actuated": false}, + "motor*": { + "class": "chosen_actuator" + } + } + + +} diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/config.json b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/config.json new file mode 100644 index 00000000000..b2a8804f2ce --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/config.json @@ -0,0 +1,24 @@ +{ + // Document URL, MuJoCo output + "url": "https://cad.onshape.com/documents/430ff184cf3dd9557aaff2be/w/e3658b7152c139971d22c688/e/a5789448a382c9e7e5c5750a", + "output_format": "mujoco", + + "ignore": { + "*": "collision", + }, + + // Disable the freejoint to fix the robot + "freejoint": false, + + "additional_xml": ["joints_properties.xml", "additional.xml"], + + // Don't create actuators for passive joints + "joint_properties": { + "passive*": {"actuated": false}, + "motor*": { + "class": "chosen_actuator" + } + } + + +} diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/__init__.py b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/__init__.py new file mode 100644 index 00000000000..4c87c591c5f --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/__init__.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python + +# Copyright 2025 The HuggingFace Inc. team. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .config_amazinghand import AmazingHandConfig +from .amazinghand import AmazingHand diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/amazinghand.mdx b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/amazinghand.mdx new file mode 120000 index 00000000000..a076e4754ac --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/amazinghand.mdx @@ -0,0 +1 @@ +../../../../docs/source/hope_jr.mdx \ No newline at end of file diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/amazinghand.py b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/amazinghand.py index 85a37d656fa..fa47c79ed48 100644 --- a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/amazinghand.py +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/amazinghand.py @@ -47,21 +47,44 @@ def __init__(self, config: AmazingHandConfig): self.config = config import os + # Motor IDs differ between left and right hand + # Left hand IDs: from Demo's l_hand.toml + # Right hand IDs: from Demo's r_hand.toml (typically ID+10 offset or different mapping) + if self.config.side == "left": + motor_ids = { + "index_m1": 15, + "index_m2": 16, + "middle_m1": 13, + "middle_m2": 14, + "ring_m1": 11, + "ring_m2": 12, + "thumb_m1": 17, + "thumb_m2": 18, + } + else: # right hand + # Right hand IDs are -10 from left hand IDs + motor_ids = { + "index_m1": 5, + "index_m2": 6, + "middle_m1": 3, + "middle_m2": 4, + "ring_m1": 1, + "ring_m2": 2, + "thumb_m1": 7, + "thumb_m2": 8, + } + self.bus = FeetechMotorsBus( port=self.config.port, motors={ - # Index - "index_m1": Motor(15, "scs0009", MotorNormMode.RANGE_0_100), - "index_m2": Motor(16, "scs0009", MotorNormMode.RANGE_0_100), - # Middle - "middle_m1": Motor(13, "scs0009", MotorNormMode.RANGE_0_100), - "middle_m2": Motor(14, "scs0009", MotorNormMode.RANGE_0_100), - # Ring - "ring_m1": Motor(11, "scs0009", MotorNormMode.RANGE_0_100), - "ring_m2": Motor(12, "scs0009", MotorNormMode.RANGE_0_100), - # Thumb - "thumb_m1": Motor(17, "scs0009", MotorNormMode.RANGE_0_100), - "thumb_m2": Motor(18, "scs0009", MotorNormMode.RANGE_0_100), + "index_m1": Motor(motor_ids["index_m1"], "scs0009", MotorNormMode.RANGE_0_100), + "index_m2": Motor(motor_ids["index_m2"], "scs0009", MotorNormMode.RANGE_0_100), + "middle_m1": Motor(motor_ids["middle_m1"], "scs0009", MotorNormMode.RANGE_0_100), + "middle_m2": Motor(motor_ids["middle_m2"], "scs0009", MotorNormMode.RANGE_0_100), + "ring_m1": Motor(motor_ids["ring_m1"], "scs0009", MotorNormMode.RANGE_0_100), + "ring_m2": Motor(motor_ids["ring_m2"], "scs0009", MotorNormMode.RANGE_0_100), + "thumb_m1": Motor(motor_ids["thumb_m1"], "scs0009", MotorNormMode.RANGE_0_100), + "thumb_m2": Motor(motor_ids["thumb_m2"], "scs0009", MotorNormMode.RANGE_0_100), }, calibration=self.calibration, protocol_version=1, @@ -150,16 +173,27 @@ def _setup_simulation(self): mjcf_path = p logger.info(f"Using MuJoCo model from LEROBOT_MJCF_PATH: {mjcf_path}") else: - # Try both the original teleoperator location and new configs location - robot_package_dir = Path(__file__).parent.parent.parent.parent - # First try configs/robot/amazinghand/mjcf/scene.xml (copied from Demo) - mjcf_path = robot_package_dir / "configs" / "robot" / "amazinghand" / "mjcf" / "scene.xml" + # Select model based on hand side (left/right) + hand_folder = f"AH_Left" if self.config.side == "left" else "AH_Right" + + # Try robot package directory first + robot_dir = Path(__file__).parent + mjcf_path = robot_dir / hand_folder / "mjcf" / "scene.xml" + + if not mjcf_path.exists(): + # Try configs location + robot_package_dir = Path(__file__).parent.parent.parent.parent + mjcf_path = robot_package_dir / "configs" / "robot" / "amazinghand" / hand_folder / "mjcf" / "scene.xml" + if not mjcf_path.exists(): # Fallback to teleoperator package - mjcf_path = robot_package_dir / "teleoperators" / "lerobot_teleoperator_amazinghandtracker" / "lerobot_teleoperator_amazinghandtracker" / "mjcf" / "scene.xml" + robot_package_dir = Path(__file__).parent.parent.parent.parent + mjcf_path = robot_package_dir / "teleoperators" / "lerobot_teleoperator_amazinghandtracker" / "lerobot_teleoperator_amazinghandtracker" / hand_folder / "mjcf" / "scene.xml" + if not mjcf_path.exists(): - raise FileNotFoundError(f"MuJoCo model not found at {mjcf_path}") - logger.info(f"Using bundled MuJoCo model: {mjcf_path}") + raise FileNotFoundError(f"MuJoCo model not found for {self.config.side} hand at {mjcf_path}") + + logger.info(f"Using bundled MuJoCo model for {self.config.side} hand: {mjcf_path}") self.mj_model = mujoco.MjModel.from_xml_path(str(mjcf_path)) self.mj_data = mujoco.MjData(self.mj_model) @@ -266,8 +300,9 @@ def _load_motor_offsets(self) -> dict[str, float]: if toml_path_str: toml_path = Path(toml_path_str) else: - # Check standard location: config/l_hand.toml next to this file (Demo format) - toml_path = Path(__file__).parent / "config" / "l_hand.toml" + # Check standard location: config/l_hand.toml or r_hand.toml based on side (Demo format) + hand_file = "l_hand.toml" if self.config.side == "left" else "r_hand.toml" + toml_path = Path(__file__).parent / "config" / hand_file if not toml_path.exists(): # Fallback to motor_offsets.toml if custom file exists toml_path = Path(__file__).parent / "config" / "motor_offsets.toml" diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/config/l_hand.toml b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/config/l_hand.toml new file mode 100644 index 00000000000..5846e873213 --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/config/l_hand.toml @@ -0,0 +1,54 @@ +# Config generated by offset tuner +[Fingers] + +[[motors]] +finger_name = "l_finger1" + +motor1.id = 15 +motor1.offset = 1.4311000000 +motor1.invert = false +motor1.model = "SCS0009" + +motor2.id = 16 +motor2.offset = 1.0231000000 +motor2.invert = false +motor2.model = "SCS0009" + +[[motors]] +finger_name = "l_finger2" + +motor1.id = 13 +motor1.offset = -0.4009000000 +motor1.invert = false +motor1.model = "SCS0009" + +motor2.id = 14 +motor2.offset = 0.4137000000 +motor2.invert = false +motor2.model = "SCS0009" + +[[motors]] +finger_name = "l_finger3" + +motor1.id = 11 +motor1.offset = -1.1901000000 +motor1.invert = false +motor1.model = "SCS0009" + +motor2.id = 12 +motor2.offset = 1.0146000000 +motor2.invert = false +motor2.model = "SCS0009" + +[[motors]] +finger_name = "l_finger4" + +motor1.id = 17 +motor1.offset = -0.5509000000 +motor1.invert = false +motor1.model = "SCS0009" + +motor2.id = 18 +motor2.offset = 0.5113000000 +motor2.invert = false +motor2.model = "SCS0009" diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/config_amazinghand.py b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/config_amazinghand.py new file mode 100644 index 00000000000..5f43f15e08f --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/config_amazinghand.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python + +# Copyright 2025 The HuggingFace Inc. team. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from dataclasses import dataclass, field + +from lerobot.cameras import CameraConfig + +from lerobot.robots.config import RobotConfig +from lerobot.cameras.opencv.configuration_opencv import OpenCVCameraConfig + + +@RobotConfig.register_subclass("lerobot_robot_amazinghand") +@dataclass +class AmazingHandConfig(RobotConfig): + port: str = "COM5" + side: str = "left" + baudrate: int = "1000000" + + disable_torque_on_disconnect: bool = True + + cameras: dict[str, CameraConfig] = field( + default_factory=lambda: { + "cam_1": OpenCVCameraConfig( + index_or_path=1, + fps=30, + width=640, + height=480, + ), + } + ) + + def __post_init__(self): + super().__post_init__() + if self.side not in ["right", "left"]: + raise ValueError(self.side) + diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/setup.py b/src/lerobot/robots/lerobot_robot_amazinghand/setup.py new file mode 100644 index 00000000000..19108d03a1b --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/setup.py @@ -0,0 +1,29 @@ +from setuptools import setup, find_packages + +setup( + name="lerobot_robot_amazinghand", + version="0.0.1", + description="LeRobot Amazinghand integration", + author="Raid", + author_email="m.raiddanial@gmail.com", + packages=find_packages(), + install_requires=[ + "numpy", + "teleop", + "lerobot", + "opencv-python", + "scipy", + "numpy", + "mediapipe", + ], + python_requires=">=3.7", + classifiers=[ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: Apache License", + "Operating System :: OS Independent", + ], +) + + + + diff --git a/src/lerobot/robots/stretch3/configuration_stretch3.py b/src/lerobot/robots/stretch3/configuration_stretch3.py index c1226bf9029..37e7ce28af2 100644 --- a/src/lerobot/robots/stretch3/configuration_stretch3.py +++ b/src/lerobot/robots/stretch3/configuration_stretch3.py @@ -35,14 +35,14 @@ class Stretch3RobotConfig(RobotConfig): rotation=-90, ), "head": RealSenseCameraConfig( - name="Intel RealSense D435I", + serial_number_or_name="Intel RealSense D435I", fps=30, width=640, height=480, rotation=90, ), "wrist": RealSenseCameraConfig( - name="Intel RealSense D405", + serial_number_or_name="Intel RealSense D405", fps=30, width=640, height=480, diff --git a/src/lerobot/robots/utils.py b/src/lerobot/robots/utils.py index aca5c871680..2c9e962e63d 100644 --- a/src/lerobot/robots/utils.py +++ b/src/lerobot/robots/utils.py @@ -64,6 +64,10 @@ def make_robot_from_config(config: RobotConfig) -> Robot: from .reachy2 import Reachy2Robot return Reachy2Robot(config) + elif config.type == "lerobot_amazinghand": + from .lerobot_robot_amazinghand.lerobot_robot_amazinghand import AmazingHand + + return AmazingHand(config) elif config.type == "mock_robot": from tests.mocks.mock_robot import MockRobot diff --git a/src/lerobot/scripts/5gtest/import lerobot.py b/src/lerobot/scripts/5gtest/import lerobot.py new file mode 100644 index 00000000000..576e12d4804 --- /dev/null +++ b/src/lerobot/scripts/5gtest/import lerobot.py @@ -0,0 +1,7 @@ +import lerobot +import inspect +import pkgutil + +# See what "hardware" modules exist: +import lerobot.hardware as hw +print(dir(hw)) diff --git a/src/lerobot/scripts/5gtest/inspect_so101.py b/src/lerobot/scripts/5gtest/inspect_so101.py new file mode 100644 index 00000000000..fc44acf7ee3 --- /dev/null +++ b/src/lerobot/scripts/5gtest/inspect_so101.py @@ -0,0 +1,19 @@ +import lerobot +import inspect + +print("LeRobot package path:", lerobot.__file__) + +# 1) Import robot configs module +from lerobot.common.robot_devices.robots import configs + +# 2) List what’s inside configs +print("\nAttributes in lerobot.common.robot_devices.robots.configs:") +for name in dir(configs): + if "So101" in name or "so101" in name: + print(" ", name) + +# 3) Try to import the So101RobotConfig directly +from lerobot.common.robot_devices.robots.configs import So101RobotConfig + +print("\nSo101RobotConfig definition:") +print(inspect.getsource(So101RobotConfig)[:600], "...\n") diff --git a/src/lerobot/scripts/5gtest/leader_so101_sender.py b/src/lerobot/scripts/5gtest/leader_so101_sender.py new file mode 100644 index 00000000000..07d975e0ec6 --- /dev/null +++ b/src/lerobot/scripts/5gtest/leader_so101_sender.py @@ -0,0 +1,37 @@ +import socket +import json +import time + +# ==== CONFIG ==== +FOLLOWER_IP = "192.168.8.252" # IP of PC2 +UDP_PORT = 5005 +SEND_HZ = 50 # send at 50 Hz (20 ms) + +# --- TODO: import and init your SO-101 leader driver here --- +from lerobot.teleoperators.so101_leader import SO101Leader, SO101LeaderConfig + +leader_cfg = SO101LeaderConfig(port="COM9", id="my_leader") # change COM7 +leader = SO101Leader(leader_cfg) +leader.connect() + +sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) +dt = 1.0 / SEND_HZ + +def get_leader_joints(): + obs = leader.get_observation() # returns a dict + # TODO: pick the right key from obs that contains the 6 joint values + q = obs["state"] # example – we’ll adjust once we see obs + return list(q) + + +print(f"Sending leader joints to {FOLLOWER_IP}:{UDP_PORT} at {SEND_HZ} Hz") + +while True: + q = get_leader_joints() + msg = { + "ts": time.time(), # timestamp on PC1 + "q": q + } + data = json.dumps(msg).encode("utf-8") + sock.sendto(data, (FOLLOWER_IP, UDP_PORT)) + time.sleep(dt) diff --git a/src/lerobot/scripts/5gtest/pc1_camera_client.py b/src/lerobot/scripts/5gtest/pc1_camera_client.py new file mode 100644 index 00000000000..c8629df0946 --- /dev/null +++ b/src/lerobot/scripts/5gtest/pc1_camera_client.py @@ -0,0 +1,65 @@ +# pc1_camera_client.py +import socket +import cv2 +import struct +import numpy as np + +# ====== CONFIG ====== +FOLLOWER_IP = "100.107.126.39" # <-- IP of PC2 (same as in your teleop sender) +SERVER_PORT = 6000 + +# ==================== +def recvall(sock, count): + """Receive exactly 'count' bytes from the socket.""" + buf = b"" + while len(buf) < count: + newbuf = sock.recv(count - len(buf)) + if not newbuf: + return None + buf += newbuf + return buf + +print(f"Connecting to camera server at {FOLLOWER_IP}:{SERVER_PORT}...") +sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +sock.connect((FOLLOWER_IP, SERVER_PORT)) +print("Connected to camera server.") + +try: + while True: + # Read 4-byte length header + header = recvall(sock, 4) + if not header: + print("Connection closed by server") + break + + size = struct.unpack(">I", header)[0] + + # Read the JPEG data + data = recvall(sock, size) + if data is None: + print("Failed to receive frame data") + break + + # Decode JPEG to image + np_data = np.frombuffer(data, dtype=np.uint8) + frame = cv2.imdecode(np_data, cv2.IMREAD_COLOR) + if frame is None: + continue + + # Show on leader PC (if GUI available) + try: + cv2.imshow("Follower Camera (from PC2)", frame) + # Press 'q' to quit the viewer + if cv2.waitKey(1) & 0xFF == ord("q"): + print("Quit signal received, closing viewer.") + break + except cv2.error: + # If GUI not available, just print frame info + print(f"Received frame: {frame.shape}") + +finally: + sock.close() + try: + cv2.destroyAllWindows() + except cv2.error: + pass # GUI not available, ignore diff --git a/src/lerobot/scripts/5gtest/pc1_leader_sender.py b/src/lerobot/scripts/5gtest/pc1_leader_sender.py new file mode 100644 index 00000000000..0b5e36825d8 --- /dev/null +++ b/src/lerobot/scripts/5gtest/pc1_leader_sender.py @@ -0,0 +1,67 @@ +# pc1_leader_sender.py +import socket +import json +import time + +from lerobot.teleoperators.so101_leader.so101_leader import SO101Leader +from lerobot.teleoperators.so101_leader.config_so101_leader import SO101LeaderConfig + +# ========= CONFIG (EDIT THESE!) ========= +FOLLOWER_IP = "100.107.126.39" # <-- IP of PC2 +UDP_PORT = 5005 +SEND_HZ = 50 # 50 Hz command rate + +LEADER_PORT = "COM8" # <-- change to your leader SO-101 COM port +LEADER_ID = "leader" # arbitrary name + +# ========= INITIALISE UDP SOCKET ========= +sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) +dt = 1.0 / SEND_HZ + +# ========= INITIALISE LEADER SO-101 ========= +leader_cfg = SO101LeaderConfig( + port=LEADER_PORT, + id=LEADER_ID, +) +leader = SO101Leader(leader_cfg) +leader.connect() + +print("Leader SO-101 initialised on", LEADER_PORT) + + +def get_leader_action() -> dict: + """ + Get the current action from the SO-101 leader. + + SO101Leader.get_action() returns a dict like: + {"joint1.pos": value, "joint2.pos": value, ...} + + We also cast all values to plain float so JSON can encode them. + """ + raw_action = leader.get_action() # <-- correct API for SO101Leader + + # Make sure everything is JSON-serialisable (no numpy.float32 etc.) + action = {key: float(value) for key, value in raw_action.items()} + return action + + +print(f"Sending leader actions to {FOLLOWER_IP}:{UDP_PORT} at {SEND_HZ} Hz") + +try: + while True: + action = get_leader_action() + msg = { + "ts": time.time(), # timestamp on PC1 + "action": action, # dict of joint commands + } + data = json.dumps(msg).encode("utf-8") + sock.sendto(data, (FOLLOWER_IP, UDP_PORT)) + time.sleep(dt) + +finally: + try: + leader.disconnect() + except Exception: + pass + sock.close() + print("Leader teleop sender stopped.") diff --git a/src/lerobot/scripts/5gtest/test_import_so101.py b/src/lerobot/scripts/5gtest/test_import_so101.py new file mode 100644 index 00000000000..6e143f56bfa --- /dev/null +++ b/src/lerobot/scripts/5gtest/test_import_so101.py @@ -0,0 +1,16 @@ +# test_import_so101.py +import lerobot + +print("LeRobot path:", lerobot.__file__) + +# SO-101 leader (teleoperator) +from lerobot.teleoperators.so101_leader import SO101Leader, SO101LeaderConfig + +# SO-101 follower (robot) +from lerobot.robots.so101_follower.so101_follower import SO101Follower +from lerobot.robots.so101_follower.config_so101_follower import SO101FollowerConfig + +print("SO101Leader:", SO101Leader) +print("SO101LeaderConfig:", SO101LeaderConfig) +print("SO101Follower:", SO101Follower) +print("SO101FollowerConfig:", SO101FollowerConfig) diff --git a/src/lerobot/scripts/lerobot_calibrate.py b/src/lerobot/scripts/lerobot_calibrate.py index 0f247caefef..192482e26e2 100644 --- a/src/lerobot/scripts/lerobot_calibrate.py +++ b/src/lerobot/scripts/lerobot_calibrate.py @@ -84,6 +84,27 @@ def calibrate(cfg: CalibrateConfig): def main(): + # Ensure local plugin config modules are imported so draccus sees their + # registered config subclasses even if full package imports fail due to + # missing hardware SDKs. These imports are best-effort and non-fatal. + try: + import importlib + + importlib.import_module( + "lerobot.robots.lerobot_robot_amazinghand.lerobot_robot_amazinghand.config_amazinghand" + ) + except Exception: + pass + + try: + import importlib + + importlib.import_module( + "lerobot.teleoperators.lerobot_teleoperator_amazinghandtracker.lerobot_teleoperator_amazinghandtracker.config_amazinghandtracker" + ) + except Exception: + pass + register_third_party_devices() calibrate() diff --git a/src/lerobot/scripts/lerobot_record.py b/src/lerobot/scripts/lerobot_record.py index 6df92d893b3..5e27133e4e9 100644 --- a/src/lerobot/scripts/lerobot_record.py +++ b/src/lerobot/scripts/lerobot_record.py @@ -126,6 +126,23 @@ log_say, ) from lerobot.utils.visualization_utils import init_rerun, log_rerun_data +import importlib + +# Best-effort import of local plugin config modules so CLI choices include +# plugins that might fail full import due to missing hardware SDKs. +try: + importlib.import_module( + "lerobot.robots.lerobot_robot_amazinghand.lerobot_robot_amazinghand.config_amazinghand" + ) +except Exception: + pass + +try: + importlib.import_module( + "lerobot.teleoperators.lerobot_teleoperator_amazinghandtracker.lerobot_teleoperator_amazinghandtracker.config_amazinghandtracker" + ) +except Exception: + pass @dataclass @@ -361,7 +378,7 @@ def record_loop( dataset.add_frame(frame) if display_data: - log_rerun_data(observation=obs_processed, action=action_values) + log_rerun_data(observation=obs, robot_action=robot_action_to_send) dt_s = time.perf_counter() - start_loop_t busy_wait(1 / fps - dt_s) diff --git a/src/lerobot/scripts/lerobot_replay.py b/src/lerobot/scripts/lerobot_replay.py index ffd7b2b22a1..84792e3d8df 100644 --- a/src/lerobot/scripts/lerobot_replay.py +++ b/src/lerobot/scripts/lerobot_replay.py @@ -67,6 +67,23 @@ init_logging, log_say, ) +import importlib + +# Best-effort import of local plugin config modules so CLI choices include +# plugins that might fail full import due to missing hardware SDKs. +try: + importlib.import_module( + "lerobot.robots.lerobot_robot_amazinghand.lerobot_robot_amazinghand.config_amazinghand" + ) +except Exception: + pass + +try: + importlib.import_module( + "lerobot.teleoperators.lerobot_teleoperator_amazinghandtracker.lerobot_teleoperator_amazinghandtracker.config_amazinghandtracker" + ) +except Exception: + pass @dataclass diff --git a/src/lerobot/scripts/lerobot_run_policy.py b/src/lerobot/scripts/lerobot_run_policy.py new file mode 100644 index 00000000000..6b1fd9b7ed5 --- /dev/null +++ b/src/lerobot/scripts/lerobot_run_policy.py @@ -0,0 +1,224 @@ +#!/usr/bin/env python + +# Copyright 2024 The HuggingFace Inc. team. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Run a trained policy on a robot continuously without recording. + +This script allows you to deploy a trained policy on a robot for continuous execution, +useful for demonstrations, testing, or production deployment without the overhead of +recording episodes to a dataset. + +Example usage: + +```shell +lerobot-run-policy \ + --robot.type=so100_follower \ + --robot.port=/dev/tty.usbmodem58760431541 \ + --robot.cameras="{laptop: {type: opencv, index_or_path: 0, width: 640, height: 480, fps: 30}}" \ + --policy.path=outputs/train/act_so100_test/checkpoints/last/pretrained_model \ + --fps=30 +``` + +Example with AmazingHand robot: +```shell +lerobot-run-policy \ + --robot.type=lerobot_robot_amazinghand \ + --robot.port=COM5 \ + --robot.side=left \ + --robot.baudrate=1000000 \ + --robot.cameras="{cam_0: {type: opencv, index_or_path: 0, width: 640, height: 480, fps: 30}}" \ + --policy.path=outputs/train/act/checkpoints/last/pretrained_model \ + --fps=30 +``` + +Press Ctrl+C to stop execution. +""" + +import json +import logging +import time +from dataclasses import dataclass +from pathlib import Path + +import importlib + +from lerobot.cameras.opencv.configuration_opencv import OpenCVCameraConfig # noqa: F401 +from lerobot.cameras.realsense.configuration_realsense import RealSenseCameraConfig # noqa: F401 +from lerobot.configs import parser +from lerobot.configs.policies import PreTrainedConfig +from lerobot.datasets.lerobot_dataset import LeRobotDataset +from lerobot.datasets.utils import build_dataset_frame +from lerobot.policies.factory import make_policy, make_pre_post_processors +from lerobot.policies.utils import make_robot_action +from lerobot.processor.rename_processor import rename_stats +from lerobot.robots import RobotConfig, make_robot_from_config, lerobot_robot_amazinghand # noqa: F401 +from lerobot.utils.constants import OBS_STR +from lerobot.utils.control_utils import predict_action +from lerobot.utils.utils import get_safe_torch_device, init_logging +from lerobot.utils.visualization_utils import init_rerun, log_rerun_data + +# Import config module to register amazinghand with draccus +try: + importlib.import_module( + "lerobot.robots.lerobot_robot_amazinghand.lerobot_robot_amazinghand.config_amazinghand" + ) +except Exception: + pass + + +@dataclass +class RunPolicyConfig: + """Configuration for running a policy on a robot.""" + + robot: RobotConfig + # Policy to run on the robot + policy: PreTrainedConfig | None = None + # Control loop frequency in Hz + fps: int = 30 + # Task description (optional, used by some policies like VLA) + task: str | None = None + # Display extra information during execution + display_data: bool = False + + def __post_init__(self): + # HACK: We parse again the cli args here to get the pretrained path if there was one. + policy_path = parser.get_path_arg("policy") + if policy_path: + cli_overrides = parser.get_cli_overrides("policy") + self.policy = PreTrainedConfig.from_pretrained(policy_path, cli_overrides=cli_overrides) + self.policy.pretrained_path = policy_path + + if self.policy is None: + raise ValueError("A policy is required to run this command") + + @classmethod + def __get_path_fields__(cls) -> list[str]: + """This enables the parser to load config from the policy using `--policy.path=local/dir`""" + return ["policy"] + + +@parser.wrap() +def run_policy(cfg: RunPolicyConfig): + """Run a trained policy continuously on a robot without recording.""" + init_logging() + + policy_path = Path(cfg.policy.pretrained_path) + if not policy_path.exists(): + raise FileNotFoundError(f"Policy path not found: {policy_path}") + + logging.info(f"Loading policy from: {policy_path}") + + # Load training config to get dataset info + train_config_path = policy_path / "train_config.json" + if not train_config_path.exists(): + raise FileNotFoundError(f"Training config not found: {train_config_path}") + + with open(train_config_path) as f: + train_config = json.load(f) + + # Load dataset metadata + dataset_repo_id = train_config["dataset"]["repo_id"] + logging.info(f"Loading dataset metadata from: {dataset_repo_id}") + dataset = LeRobotDataset(dataset_repo_id) + + logging.info(f"Policy type: {cfg.policy.type}") + logging.info(f"Control frequency: {cfg.fps} Hz") + + if cfg.display_data: + init_rerun(session_name="run_policy") + + # Initialize robot + logging.info(f"Connecting to {cfg.robot.type} robot") + robot_instance = make_robot_from_config(cfg.robot) + robot_instance.connect() + + # Load policy with dataset metadata + policy_model = make_policy(cfg.policy, ds_meta=dataset.meta) + policy_model.eval() + + # Create preprocessor and postprocessor pipelines + preprocessor, postprocessor = make_pre_post_processors( + policy_cfg=cfg.policy, + pretrained_path=cfg.policy.pretrained_path, + dataset_stats=rename_stats(dataset.meta.stats, rename_map={}), + preprocessor_overrides={ + "device_processor": {"device": cfg.policy.device}, + }, + ) + + logging.info("Starting policy execution. Press Ctrl+C to stop.") + + dt = 1 / cfg.fps + iteration = 0 + device = get_safe_torch_device(cfg.policy.device) + start_time_total = time.perf_counter() + + try: + while True: + start_time = time.perf_counter() + + # Get observation from robot + observation = robot_instance.get_observation() + + # Convert to dataset frame format + observation_frame = build_dataset_frame(dataset.features, observation, prefix=OBS_STR) + + # Get action from policy using the full pipeline + action_values = predict_action( + observation=observation_frame, + policy=policy_model, + device=device, + preprocessor=preprocessor, + postprocessor=postprocessor, + use_amp=cfg.policy.use_amp, + task=cfg.task, + robot_type=cfg.robot.type, + ) + + # Convert action tensor to robot action dictionary + action = make_robot_action(action_values, dataset.features) + + # Send action to robot + robot_instance.send_action(action) + + # Log to Rerun if enabled + if cfg.display_data: + log_rerun_data(observation=observation, robot_action=action) + + iteration += 1 + + # Log progress periodically + if iteration % 100 == 0: + elapsed_total = time.perf_counter() - start_time_total + logging.info(f"Iteration {iteration}: Policy running (avg freq: {iteration / elapsed_total:.1f} Hz)") + + # Maintain control loop frequency + elapsed = time.perf_counter() - start_time + if elapsed < dt: + time.sleep(dt - elapsed) + + except KeyboardInterrupt: + logging.info("Policy execution stopped by user") + + finally: + robot_instance.disconnect() + logging.info("Robot disconnected") + + +def main(): + run_policy() + + +if __name__ == "__main__": + main() diff --git a/src/lerobot/scripts/lerobot_teleoperate.py b/src/lerobot/scripts/lerobot_teleoperate.py index 0a418f3bcad..5b6b82c83ad 100644 --- a/src/lerobot/scripts/lerobot_teleoperate.py +++ b/src/lerobot/scripts/lerobot_teleoperate.py @@ -76,6 +76,7 @@ make_robot_from_config, so100_follower, so101_follower, + lerobot_robot_amazinghand, ) from lerobot.teleoperators import ( # noqa: F401 Teleoperator, @@ -87,7 +88,28 @@ make_teleoperator_from_config, so100_leader, so101_leader, + lerobot_teleoperator_amazinghandtracker, ) +import importlib + +# Some robot/teleoperator plugins are packaged under a nested package +# layout (e.g. //...). The outer namespace package import may +# not import the inner package where the config/registration lives. To +# ensure draccus sees the registered config choices we explicitly import +# the config modules for those plugins here. +try: + importlib.import_module( + "lerobot.robots.lerobot_robot_amazinghand.lerobot_robot_amazinghand.config_amazinghand" + ) +except Exception: + pass + +try: + importlib.import_module( + "lerobot.teleoperators.lerobot_teleoperator_amazinghandtracker.lerobot_teleoperator_amazinghandtracker.config_amazinghandtracker" + ) +except Exception: + pass from lerobot.utils.import_utils import register_third_party_devices from lerobot.utils.robot_utils import busy_wait from lerobot.utils.utils import init_logging, move_cursor_up @@ -160,10 +182,7 @@ def teleop_loop( # Process robot observation through pipeline obs_transition = robot_observation_processor(obs) - log_rerun_data( - observation=obs_transition, - action=teleop_action, - ) + log_rerun_data(observation=obs, robot_action=robot_action_to_send) print("\n" + "-" * (display_len + 10)) print(f"{'NAME':<{display_len}} | {'NORM':>7}") diff --git a/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/__init__.py b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/__init__.py new file mode 100644 index 00000000000..50e1345c949 --- /dev/null +++ b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/__init__.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python + +# Copyright 2025 The HuggingFace Inc. team. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .config_amazinghandtracker import AmazingHandTrackerConfig +from .amazinghandtracker import AmazingHandTracker + diff --git a/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/amazinghandtracker.py b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/amazinghandtracker.py new file mode 100644 index 00000000000..be5e952e7ec --- /dev/null +++ b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/amazinghandtracker.py @@ -0,0 +1,574 @@ +#!/usr/bin/env python + +# Copyright 2025 The HuggingFace Inc. team. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +import os +import threading +from collections import deque +from pprint import pformat + +import serial + +from lerobot.motors import MotorCalibration +from lerobot.motors.motors_bus import MotorNormMode +from lerobot.utils.errors import DeviceAlreadyConnectedError, DeviceNotConnectedError +from lerobot.utils.utils import enter_pressed, move_cursor_up + +from lerobot.teleoperators import Teleoperator +from .config_amazinghandtracker import AmazingHandTrackerConfig +from .inverse_kinematics import AmazingHandIK +logger = logging.getLogger(__name__) + +#!/usr/bin/env python + +import logging +import threading +from collections import deque +from pprint import pformat +import serial + +from lerobot.motors import MotorCalibration +from lerobot.motors.motors_bus import MotorNormMode +from lerobot.utils.errors import DeviceAlreadyConnectedError, DeviceNotConnectedError +from lerobot.utils.utils import enter_pressed, move_cursor_up + +from lerobot.teleoperators import Teleoperator +from .config_amazinghandtracker import AmazingHandTrackerConfig +from lerobot.teleoperators.config import TeleoperatorConfig + +logger = logging.getLogger(__name__) + +# Tip vector components for hand tracking (sent as raw values to robot) +HAND_JOINTS = [ + "index_tip_x", "index_tip_y", "index_tip_z", + "middle_tip_x", "middle_tip_y", "middle_tip_z", + "ring_tip_x", "ring_tip_y", "ring_tip_z", + "thumb_tip_x", "thumb_tip_y", "thumb_tip_z", +] + +class AmazingHandTracker(Teleoperator): + config_class = AmazingHandTrackerConfig + name = "lerobot_teleoperator_amazinghandtracker" + + def __init__(self, config: AmazingHandTrackerConfig): + super().__init__(config) + self.config = config + # If a MuJoCo model path is provided in teleop config, propagate via env for the robot + try: + mjcf = getattr(config, "mujoco_model_path", None) + if mjcf: + from pathlib import Path + p = Path(mjcf) + if p.exists(): + os.environ["LEROBOT_MJCF_PATH"] = str(p.resolve()) + logger.info(f"{self}: Set LEROBOT_MJCF_PATH to {p}") + else: + logger.warning(f"{self}: Provided mujoco_model_path does not exist: {p}") + except Exception as e: + logger.warning(f"{self}: Failed to set LEROBOT_MJCF_PATH: {e}") + self._frame = None + self._frame_ready = threading.Event() + self._stop_event = threading.Event() + self._state = None + self._state_lock = threading.Lock() + + # Initialize camera + from lerobot.cameras.utils import make_cameras_from_configs + self.cameras = make_cameras_from_configs(config.cameras) + + # Initialize trackers for each finger tip component (raw metric values, no normalization) + self.joints = {joint: float for joint in HAND_JOINTS} + + # NO EMA smoothing - send raw MediaPipe tracking data (like Demo does) + # Demo only has PID smoothing in Rust controller, not in Python tracking + logger.info("Tracker smoothing: DISABLED (raw MediaPipe data sent directly)") + self._last_raw = dict.fromkeys(self.joints, None) # Store last raw values for fallback + + # Start processing thread + self._thread = threading.Thread(target=self._tracking_loop, daemon=True) + + # ------------------------------------------------------------ + # LeRobot interface properties + # ------------------------------------------------------------ + @property + def action_features(self) -> dict: + return {f"{joint}.pos": float for joint in self.joints} + + @property + def observation_features(self) -> dict: + # Include camera dimensions for rerun visualization + return { + cam: (self.config.cameras[cam].height, self.config.cameras[cam].width, 3) + for cam in self.cameras + } + + @property + def feedback_features(self) -> dict: + return {} + + @property + def is_connected(self) -> bool: + return all(cam.is_connected for cam in self.cameras.values()) and self._thread.is_alive() + + # ------------------------------------------------------------ + # Connection management + # ------------------------------------------------------------ + def connect(self, calibrate=True): + if self.is_connected: + raise DeviceAlreadyConnectedError(f"{self} already connected") + + # Connect cameras + for cam in self.cameras.values(): + cam.connect() + + # Start tracking thread + self._thread.start() + + # Try to prime the camera/frame source and verify we can get frames + try: + first_cam = next(iter(self.cameras.values())) + logger.info(f"{self}: Attempting to read first frame from camera...") + + # Try synchronous read first to ensure camera works + frame = first_cam.read() + if frame is not None: + logger.info(f"{self}: Successfully got first frame with shape {frame.shape}") + else: + logger.warning(f"{self}: Got None frame from synchronous read") + + # Now try async read to prime the background thread + frame = first_cam.async_read(timeout_ms=2000) + if frame is not None: + logger.info(f"{self}: Successfully got first async frame with shape {frame.shape}") + else: + logger.warning(f"{self}: Got None frame from async read") + + except Exception as e: + logger.warning(f"{self}: Error priming camera: {e}") + + # Wait for first successful tracking with longer timeout + logger.info(f"{self}: Waiting for first frame to be processed...") + if not self._frame_ready.wait(timeout=10): # Increased timeout + logger.error(f"{self}: Timed out waiting for first frame to be processed") + raise TimeoutError(f"{self}: Timed out waiting for first frame.") + + # Hand tracking doesn't need calibration - skip it + if calibrate: + logger.info(f"{self}: Calibration not required for hand tracking (sends raw metric vectors)") + + logger.info(f"{self} connected.") + + @property + def is_calibrated(self) -> bool: + # Hand tracking doesn't need calibration - always return True + return True + + def _load_calibration(self): + """Override parent method - hand tracking doesn't need calibration.""" + # Check if a calibration file exists and warn that it will be ignored + from pathlib import Path + calib_dir = Path.home() / ".cache" / "huggingface" / "lerobot" / "calibration" / "teleoperators" / "lerobot_teleoperator_amazinghandtracker" + calib_file = calib_dir / "None.json" + + if calib_file.exists(): + logger.warning(f"⚠️ Found calibration file at {calib_file}") + logger.warning(f" This file LIMITS tip vector range and should be deleted for teleoperation!") + logger.warning(f" Ignoring calibration - using RAW MediaPipe tracking data") + + # Set calibration to empty dict to satisfy interface + self.calibration = {} + logger.info(f"{self}: Skipping calibration load (not required for hand tracking)") + + def disconnect(self): + if not self.is_connected: + raise DeviceNotConnectedError(f"{self} is not connected.") + + # Stop tracking thread + self._stop_event.set() + self._thread.join(timeout=1) + + # Disconnect cameras + for cam in self.cameras.values(): + cam.disconnect() + + logger.info(f"{self} disconnected.") + + # ------------------------------------------------------------ + # Calibration + # ------------------------------------------------------------ + def calibrate(self): + """Calibration is NOT needed for hand tracking. + + Unlike robot motors that need range calibration, hand tracking produces + raw metric tip vectors that are fed directly to the IK solver. + The IK solver handles the mapping to robot joint space. + + This method exists for interface compatibility but does nothing. + """ + print("=" * 60) + print("HAND TRACKING CALIBRATION NOT REQUIRED") + print("=" * 60) + print("\nHand tracking sends raw metric tip vectors (in meters)") + print("directly to the robot's IK solver. No calibration needed.") + print("\nIf the robot doesn't follow your hand correctly:") + print("1. Check robot motor calibration (robot.calibrate())") + print("2. Verify MJCF model matches your robot") + print("3. Check axis flips/permutations in robot code") + print("=" * 60) + print("\nSkipping calibration - not required for hand tracking.\n") + + def _get_current_hand_state(self, wait_for_frame: bool = False) -> dict[str, float]: + """Get the current hand joint positions (3D tip positions in hand frame) + + Args: + wait_for_frame: If True, wait up to 1 second for a frame (used during calibration). + If False, return immediately with current state (used during teleoperation). + """ + if wait_for_frame and not self._frame_ready.wait(timeout=1): + raise TimeoutError(f"{self}: Timed out waiting for frame") + + with self._state_lock: + state = self._state.copy() if self._state else {} + + return state + + # ------------------------------------------------------------ + # Tracking & Smoothing + # ------------------------------------------------------------ + # NOTE: _apply_ema removed - no smoothing on Python side (Demo only has PID on Rust side) + + def _tracking_loop(self): + """Main tracking loop that processes frames and updates joint positions""" + logger.info(f"{self}: Starting tracking loop thread...") + + try: + import cv2 + import mediapipe as mp + import numpy as np + import time + + # Initialize MediaPipe Hands + mp_hands = mp.solutions.hands + mp_drawing = mp.solutions.drawing_utils + mp_drawing_styles = mp.solutions.drawing_styles + + logger.info(f"{self}: Initializing MediaPipe Hands...") + # Get confidence thresholds from config or use defaults + min_detection_conf = float(getattr(self.config, "min_detection_confidence", 0.7)) + min_tracking_conf = float(getattr(self.config, "min_tracking_confidence", 0.7)) + model_complexity = int(getattr(self.config, "model_complexity", 0)) + + hands = mp_hands.Hands( + static_image_mode=False, # Video mode for better tracking + model_complexity=model_complexity, # 0=fastest, 1=balanced (default changed to 0.7 for better tracking) + max_num_hands=1, + min_detection_confidence=min_detection_conf, + min_tracking_confidence=min_tracking_conf + ) + logger.info(f"{self}: MediaPipe Hands initialized (detection_conf={min_detection_conf}, tracking_conf={min_tracking_conf}, model={model_complexity})") + + # Create window for visualization (optional) + window_name = f"MediaPipe Hands - {self.config.side}" + preview_enabled = bool(getattr(self.config, "show_preview", False)) + overlay_enabled = bool(getattr(self.config, "show_overlay", True)) + save_debug_frames = bool(getattr(self.config, "save_debug_frames", False)) + debug_frame_interval = int(getattr(self.config, "debug_frame_interval", 30)) + frame_counter = 0 + + # Create debug output directory if saving frames + if save_debug_frames: + import os + from pathlib import Path + debug_dir = Path("outputs/hand_tracking_debug") + debug_dir.mkdir(parents=True, exist_ok=True) + logger.info(f"{self}: Saving debug frames to {debug_dir.absolute()}") + + if preview_enabled: + try: + # startWindowThread can help on some platforms; no-op on others + try: + cv2.startWindowThread() + except Exception: + pass + cv2.namedWindow(window_name, cv2.WINDOW_NORMAL) + except Exception as e: + logger.warning(f"{self}: Failed to create OpenCV window: {e}. Disabling preview.") + preview_enabled = False + + while not self._stop_event.is_set(): + try: + loop_start = time.time() + frame_counter += 1 + + # Get frame from camera + frame = next(iter(self.cameras.values())).async_read() + if frame is None: + continue + + # Flip for selfie view + frame = cv2.flip(frame, 1) + + # Process frame + frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) + frame_rgb.flags.writeable = False + results = hands.process(frame_rgb) + frame_rgb.flags.writeable = True + + # Convert back to BGR for display with correct colors + display_frame = cv2.cvtColor(frame_rgb, cv2.COLOR_RGB2BGR) + + # Extract hand landmarks + if results.multi_hand_landmarks and results.multi_handedness: + hand_detected = False + for index, handedness_classif in enumerate(results.multi_handedness): + score = handedness_classif.classification[0].score + hand_label = handedness_classif.classification[0].label + + # Filter: only process the hand that matches our configured side + if hand_label.lower() != self.config.side.lower(): + continue + + if score > 0.8: + hand_detected = True + # Get world landmarks (metric 3D coordinates) + hand_landmarks = results.multi_hand_world_landmarks[index] + # Get normalized landmarks for drawing + hand_landmarks_norm = results.multi_hand_landmarks[index] + + # Draw landmarks and connections using MediaPipe's default styles (same as Demo) + # This provides rich, multi-colored visualization where different parts are easily distinguishable + mp_drawing.draw_landmarks( + display_frame, + hand_landmarks_norm, + mp_hands.HAND_CONNECTIONS, + mp_drawing_styles.get_default_hand_landmarks_style(), + mp_drawing_styles.get_default_hand_connections_style() + ) + + # Convert landmarks to joint positions using original algorithm + joint_positions = self._landmarks_to_joint_positions( + hand_landmarks, hand_landmarks_norm, hand_label + ) + + # Store RAW joint positions - NO smoothing (like Demo) + with self._state_lock: + self._state = joint_positions + self._frame = display_frame + self._last_raw = joint_positions + + break # Only process first valid hand + + # Store frame even if no hands detected + with self._state_lock: + self._frame = display_frame + + # Display the frame (optional). On Windows, HighGUI can freeze in non-main thread; catch and disable. + if preview_enabled: + try: + cv2.imshow(window_name, display_frame) + # waitKey with very short timeout (1ms) to prevent blocking + cv2.waitKey(1) + except Exception as e: + logger.warning(f"{self}: OpenCV preview error: {e}. Disabling preview to prevent freezing.") + preview_enabled = False + try: + cv2.destroyAllWindows() + except Exception: + pass + + # Save debug frames (for external viewing without threading issues) + if save_debug_frames and frame_counter % debug_frame_interval == 0: + try: + frame_path = debug_dir / f"frame_{frame_counter:06d}.jpg" + cv2.imwrite(str(frame_path), display_frame) + except Exception: + pass + + # Signal that we're getting frames regardless of hand detection + self._frame_ready.set() + + # Frame rate limiter: target 30 FPS + elapsed = time.time() - loop_start + sleep_time = max(0, (1.0 / 30.0) - elapsed) + if sleep_time > 0: + time.sleep(sleep_time) + + except Exception as e: + logger.warning(f"{self}: Error in tracking loop iteration: {e}", exc_info=True) + # Signal frame ready even on error to prevent deadlock + self._frame_ready.set() + time.sleep(0.1) # Brief pause before retry + + # Clean up + hands.close() + if preview_enabled: + try: + cv2.destroyWindow(window_name) + except Exception: + pass + + except Exception as e: + logger.error(f"{self}: Fatal error in tracking loop: {e}", exc_info=True) + raise + + def _landmarks_to_joint_positions(self, hand_landmarks, hand_landmarks_norm, hand_label) -> dict[str, float]: + """Convert MediaPipe hand landmarks to joint positions using original AmazingHand algorithm + + This function follows the exact same logic as the original process_img() from + d:\SHRDC\AmazingHand-main\Demo\HandTracking\HandTracking\main.py + """ + import numpy as np + import os + import mediapipe as mp + + mp_hands = mp.solutions.hands + + # Calculate tip vectors (TIP - MCP) for each finger in WORLD coordinates (metric) + tip1_x = hand_landmarks.landmark[mp_hands.HandLandmark.INDEX_FINGER_TIP].x - hand_landmarks.landmark[mp_hands.HandLandmark.INDEX_FINGER_MCP].x + tip1_y = hand_landmarks.landmark[mp_hands.HandLandmark.INDEX_FINGER_TIP].y - hand_landmarks.landmark[mp_hands.HandLandmark.INDEX_FINGER_MCP].y + tip1_z = hand_landmarks.landmark[mp_hands.HandLandmark.INDEX_FINGER_TIP].z - hand_landmarks.landmark[mp_hands.HandLandmark.INDEX_FINGER_MCP].z + + tip2_x = hand_landmarks.landmark[mp_hands.HandLandmark.MIDDLE_FINGER_TIP].x - hand_landmarks.landmark[mp_hands.HandLandmark.MIDDLE_FINGER_MCP].x + tip2_y = hand_landmarks.landmark[mp_hands.HandLandmark.MIDDLE_FINGER_TIP].y - hand_landmarks.landmark[mp_hands.HandLandmark.MIDDLE_FINGER_MCP].y + tip2_z = hand_landmarks.landmark[mp_hands.HandLandmark.MIDDLE_FINGER_TIP].z - hand_landmarks.landmark[mp_hands.HandLandmark.MIDDLE_FINGER_MCP].z + + tip3_x = hand_landmarks.landmark[mp_hands.HandLandmark.RING_FINGER_TIP].x - hand_landmarks.landmark[mp_hands.HandLandmark.RING_FINGER_MCP].x + tip3_y = hand_landmarks.landmark[mp_hands.HandLandmark.RING_FINGER_TIP].y - hand_landmarks.landmark[mp_hands.HandLandmark.RING_FINGER_MCP].y + tip3_z = hand_landmarks.landmark[mp_hands.HandLandmark.RING_FINGER_TIP].z - hand_landmarks.landmark[mp_hands.HandLandmark.RING_FINGER_MCP].z + + tip4_x = hand_landmarks.landmark[mp_hands.HandLandmark.THUMB_TIP].x - hand_landmarks.landmark[mp_hands.HandLandmark.THUMB_MCP].x + tip4_y = hand_landmarks.landmark[mp_hands.HandLandmark.THUMB_TIP].y - hand_landmarks.landmark[mp_hands.HandLandmark.THUMB_MCP].y + tip4_z = hand_landmarks.landmark[mp_hands.HandLandmark.THUMB_TIP].z - hand_landmarks.landmark[mp_hands.HandLandmark.THUMB_MCP].z + + # Define hand coordinate frame using NORMALIZED coordinates (matching original Demo) + # This is critical: Demo uses hand_landmarks_norm for frame, not world coordinates + # Wrist origin (normalized) + origin = np.array([ + hand_landmarks_norm.landmark[mp_hands.HandLandmark.WRIST].x, + hand_landmarks_norm.landmark[mp_hands.HandLandmark.WRIST].y, + hand_landmarks_norm.landmark[mp_hands.HandLandmark.WRIST].z, + ]) + + # Middle finger MCP (base) in normalized + mid_mcp = np.array([ + hand_landmarks_norm.landmark[mp_hands.HandLandmark.MIDDLE_FINGER_MCP].x, + hand_landmarks_norm.landmark[mp_hands.HandLandmark.MIDDLE_FINGER_MCP].y, + hand_landmarks_norm.landmark[mp_hands.HandLandmark.MIDDLE_FINGER_MCP].z, + ]) + + # Z-axis: unit vector from wrist towards middle MCP (normalized) + unit_z = mid_mcp - origin + unit_z = unit_z / np.linalg.norm(unit_z) + + # Base of the pinky finger (normalized) + pinky_mcp = np.array([ + hand_landmarks_norm.landmark[mp_hands.HandLandmark.PINKY_MCP].x, + hand_landmarks_norm.landmark[mp_hands.HandLandmark.PINKY_MCP].y, + hand_landmarks_norm.landmark[mp_hands.HandLandmark.PINKY_MCP].z, + ]) + + # Base of the index finger (normalized) + index_mcp = np.array([ + hand_landmarks_norm.landmark[mp_hands.HandLandmark.INDEX_FINGER_MCP].x, + hand_landmarks_norm.landmark[mp_hands.HandLandmark.INDEX_FINGER_MCP].y, + hand_landmarks_norm.landmark[mp_hands.HandLandmark.INDEX_FINGER_MCP].z, + ]) + + # Vector towards Y direction depends on hand side (same as original) + if hand_label == 'Right': + vec_towards_y = pinky_mcp - origin # vector from wrist base towards pinky base + if hand_label == 'Left': + vec_towards_y = index_mcp - origin # vector from wrist base towards index base + + # X-axis: cross product of z and the vector towards pinky/index (same as original) + unit_x = np.cross(vec_towards_y, unit_z) + unit_x = unit_x / np.linalg.norm(unit_x) + + # Y-axis: complete coordinate system + unit_y = np.cross(unit_z, unit_x) + + # Rotation matrix; Demo mirrors Y (uses -unit_y). Allow override via env var. + mirror_env = os.getenv("LEROBOT_MIRROR_Y", "1") # "1" or "true" to mirror (default), "0" to disable + mirror_y = str(mirror_env).lower() in ("1", "true", "yes", "on") + R = np.array([unit_x, (-unit_y if mirror_y else unit_y), unit_z]).reshape((3, 3)) + + # Transform tip vectors into hand coordinate frame (same as original) + tip1 = R @ np.array([tip1_x, tip1_y, tip1_z]) + tip2 = R @ np.array([tip2_x, tip2_y, tip2_z]) + tip3 = R @ np.array([tip3_x, tip3_y, tip3_z]) + tip4 = R @ np.array([tip4_x, tip4_y, tip4_z]) + + # Prepare tip positions for IK solver + tip_positions = { + 'tip1': tip1, + 'tip2': tip2, + 'tip3': tip3, + 'tip4': tip4, + } + + # Return raw tip vectors - robot will handle motor conversion + # This simplifies the teleoperator and puts all robot-specific logic on robot side + positions = { + 'index_tip_x': float(tip1[0]), + 'index_tip_y': float(tip1[1]), + 'index_tip_z': float(tip1[2]), + 'middle_tip_x': float(tip2[0]), + 'middle_tip_y': float(tip2[1]), + 'middle_tip_z': float(tip2[2]), + 'ring_tip_x': float(tip3[0]), + 'ring_tip_y': float(tip3[1]), + 'ring_tip_z': float(tip3[2]), + 'thumb_tip_x': float(tip4[0]), + 'thumb_tip_y': float(tip4[1]), + 'thumb_tip_z': float(tip4[2]), + } + return positions + + # ------------------------------------------------------------ + # Action interface + # ------------------------------------------------------------ + def get_action(self) -> dict[str, float]: + """ + Returns raw tip vectors (in meters) for robot IK solver. + No normalization is applied - robot expects metric coordinates. + """ + state = self._read() + # Return raw tip vectors without normalization + action_dict = {f"{j}.pos": v for j, v in state.items()} + + return action_dict + + def send_feedback(self, feedback: dict[str, float]) -> None: + # Not used for tracking-only devices + pass + + def configure(self) -> None: + # No additional configuration needed for this device + pass + + def _read(self) -> dict[str, float]: + """Get the latest RAW hand state in meters (no normalization). + The robot expects raw metric tip vectors for its IK solver. + Always returns a full dict so downstream never sees an empty action. + Fallback order: current state -> last raw -> neutral zeros. + """ + raw = self._get_current_hand_state() + if not raw: + # Fallback to last raw values or neutral zeros + raw = {j: (self._last_raw[j] if self._last_raw[j] is not None else 0.0) for j in self.joints} + + # NO EMA smoothing - send raw tracking data directly (like Demo does) + return raw diff --git a/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/config_amazinghandtracker.py b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/config_amazinghandtracker.py new file mode 100644 index 00000000000..26f924ff5aa --- /dev/null +++ b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/config_amazinghandtracker.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python + +# Copyright 2025 The HuggingFace Inc. team. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from dataclasses import dataclass ,field + +from lerobot.teleoperators.config import TeleoperatorConfig +from lerobot.cameras.opencv.configuration_opencv import OpenCVCameraConfig +from lerobot.cameras import CameraConfig + + +@TeleoperatorConfig.register_subclass("lerobot_teleoperator_amazinghandtracker") +@dataclass +class AmazingHandTrackerConfig(TeleoperatorConfig): + camera_id: int = field(default=0, metadata={"description": "Webcam device index"}) + side: str = field(default="right", metadata={"description": "Right or Left hand"}) + show_preview: bool = field(default=True, metadata={"description": "Show OpenCV preview window (may freeze on Windows with threading)"}) + show_overlay: bool = field(default=True, metadata={"description": "Draw hand landmarks on frames"}) + save_debug_frames: bool = field(default=False, metadata={"description": "Save frames with landmarks to disk for debugging (check output folder)"}) + debug_frame_interval: int = field(default=30, metadata={"description": "Save every Nth frame when save_debug_frames is enabled"}) + use_ik: bool = field(default=True, metadata={"description": "Use MuJoCo IK solver (requires mujoco+mink). Falls back to approximation if False."}) + mujoco_model_path: str | None = field(default=None, metadata={"description": "Path to MuJoCo scene.xml (auto-detects local copy if None)"}) + ik_dt: float = field(default=0.001, metadata={"description": "IK integration timestep in seconds (smaller = more accurate)"}) + ik_iterations: int = field(default=10, metadata={"description": "IK solve iterations per frame (more = better convergence)"}) + range_expansion: float = field(default=1.4, metadata={"description": "Expand calibrated range by this factor to reduce saturation"}) + def __post_init__(self): + if self.side not in ["right", "left"]: + raise ValueError(self.side) + cameras: dict[str, CameraConfig] = field( + default_factory=lambda: { + "cam_1": OpenCVCameraConfig( + index_or_path=1, + fps=30, + width=640, + height=480, + ), + } + ) + + + diff --git a/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/inverse_kinematics.py b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/inverse_kinematics.py new file mode 100644 index 00000000000..2df8ae8ecab --- /dev/null +++ b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/inverse_kinematics.py @@ -0,0 +1,274 @@ +#!/usr/bin/env python +""" +Inverse Kinematics for AmazingHand fingers using MuJoCo and mink. + +This module provides IK solving to convert fingertip positions (from hand tracking) +to joint angles (for motor control), matching the original AHSimulation behavior. +""" + +import logging +import os +from pathlib import Path +from typing import Optional + +import numpy as np + +logger = logging.getLogger(__name__) + +# Optional dependencies - will be imported lazily +mujoco = None +mink = None + + +class AmazingHandIK: + """Inverse kinematics solver for AmazingHand using MuJoCo and mink""" + + def __init__(self, model_path: Optional[str] = None, use_ik: bool = True, dt: float = 0.001, max_iters: int = 10): + """ + Initialize the IK solver. + + Args: + model_path: Path to the MuJoCo scene.xml file. If None, tries to find it automatically. + use_ik: If False, falls back to magnitude-based approximation without MuJoCo/mink + dt: IK integration timestep in seconds (smaller = more accurate, default 0.001) + max_iters: Number of IK solve iterations per frame (more = better convergence, default 10) + """ + self.use_ik = use_ik + self.model_path = None # will be set to resolved path when model loads + self.model = None + self.configuration = None + self.tasks = None + self.solver = "quadprog" + self.dt = dt + self.max_iters = max_iters + + if not use_ik: + logger.info("IK disabled, using magnitude-based approximation") + return + + # Try to import optional dependencies + global mujoco, mink + try: + import mujoco as mj + import mink as mk + mujoco = mj + mink = mk + except ImportError as e: + logger.warning(f"Could not import mujoco/mink: {e}. Falling back to approximation.") + self.use_ik = False + return + + # Find model path + if model_path is None: + # Prefer a local copy shipped with the package first + local_scene = Path(__file__).parent / "mjcf" / "scene.xml" + possible_paths = [ + local_scene, + # Fallback to the original AmazingHand repo locations + Path("d:/SHRDC/AmazingHand-main/Demo/AHSimulation/AHSimulation/AH_Left/mjcf/scene.xml"), + Path.home() / "AmazingHand" / "Demo" / "AHSimulation" / "AHSimulation" / "AH_Left" / "mjcf" / "scene.xml", + ] + for path in possible_paths: + if path.exists(): + model_path = str(path) + break + + if model_path is None or not Path(model_path).exists(): + logger.warning(f"MuJoCo model not found at {model_path}. Falling back to approximation.") + self.use_ik = False + return + + try: + # Load MuJoCo model + logger.info(f"Loading MuJoCo model from: {model_path}") + self.model_path = str(model_path) + self.model = mujoco.MjModel.from_xml_path(model_path) + self.configuration = mink.Configuration(self.model) + + # Create IK tasks for each fingertip + posture_task = mink.PostureTask(self.model, cost=1e-2) + + # Frame tasks for fingertips (position control only, no orientation) + task1 = mink.FrameTask( + frame_name='tip1', + frame_type="site", + position_cost=1.0, + orientation_cost=0.0, + lm_damping=1.0, + ) + task2 = mink.FrameTask( + frame_name='tip2', + frame_type="site", + position_cost=1.0, + orientation_cost=0.0, + lm_damping=1.0, + ) + task3 = mink.FrameTask( + frame_name='tip3', + frame_type="site", + position_cost=1.0, + orientation_cost=0.0, + lm_damping=1.0, + ) + task4 = mink.FrameTask( + frame_name='tip4', + frame_type="site", + position_cost=1.0, + orientation_cost=0.0, + lm_damping=1.0, + ) + + # Equality constraint task + eq_task = mink.EqualityConstraintTask(self.model, cost=1000.0) + + self.tasks = [eq_task, posture_task, task1, task2, task3, task4] + + # Initialize from zero keyframe + self.configuration.update_from_keyframe("zero") + posture_task.set_target_from_configuration(self.configuration) + + # Store task references for setting targets + self.task1 = task1 + self.task2 = task2 + self.task3 = task3 + self.task4 = task4 + self.posture_task = posture_task + + # Get joint IDs for reading motor positions + self.joint_ids = { + 'finger1_motor1': mujoco.mj_name2id(self.model, mujoco.mjtObj.mjOBJ_JOINT, "finger1_motor1"), + 'finger1_motor2': mujoco.mj_name2id(self.model, mujoco.mjtObj.mjOBJ_JOINT, "finger1_motor2"), + 'finger2_motor1': mujoco.mj_name2id(self.model, mujoco.mjtObj.mjOBJ_JOINT, "finger2_motor1"), + 'finger2_motor2': mujoco.mj_name2id(self.model, mujoco.mjtObj.mjOBJ_JOINT, "finger2_motor2"), + 'finger3_motor1': mujoco.mj_name2id(self.model, mujoco.mjtObj.mjOBJ_JOINT, "finger3_motor1"), + 'finger3_motor2': mujoco.mj_name2id(self.model, mujoco.mjtObj.mjOBJ_JOINT, "finger3_motor2"), + 'finger4_motor1': mujoco.mj_name2id(self.model, mujoco.mjtObj.mjOBJ_JOINT, "finger4_motor1"), + 'finger4_motor2': mujoco.mj_name2id(self.model, mujoco.mjtObj.mjOBJ_JOINT, "finger4_motor2"), + } + + self.data = self.configuration.data + + logger.info(f"IK solver initialized successfully with dt={self.dt}, max_iters={self.max_iters}") + + except Exception as e: + logger.error(f"Failed to initialize IK solver: {e}", exc_info=True) + self.use_ik = False + + def solve(self, tip_positions: dict[str, np.ndarray]) -> dict[str, float]: + """ + Solve inverse kinematics to get joint angles from tip positions. + + Args: + tip_positions: Dictionary with keys 'tip1', 'tip2', 'tip3', 'tip4' + Each value is a 3D position vector [x, y, z] in the hand frame + + Returns: + Dictionary with motor positions: + - 'index_m1', 'index_m2' (from tip1) + - 'middle_m1', 'middle_m2' (from tip2) + - 'ring_m1', 'ring_m2' (from tip3) + - 'thumb_m1', 'thumb_m2' (from tip4) + """ + if not self.use_ik: + # Fallback to magnitude-based approximation + logger.debug("Using magnitude-based approximation (IK disabled)") + return self._solve_magnitude(tip_positions) + + try: + # Log input tip positions + logger.debug(f"IK input tip positions:") + for tip_name, tip_pos in tip_positions.items(): + logger.debug(f" {tip_name}: [{tip_pos[0]:.4f}, {tip_pos[1]:.4f}, {tip_pos[2]:.4f}]") + + # Scale and offset tip positions to match MuJoCo coordinate frame + # These values match the original mj_mink_left.py scaling + scale = 1.5 + offsets = { + 'tip1': [0.025, -0.022, 0.098], # index + 'tip2': [0.025, 0.009, 0.092], # middle + 'tip3': [0.025, 0.040, 0.082], # ring + 'tip4': [0.024, -0.019, 0.017], # thumb + } + + # Set target positions for each fingertip task + for i, (tip_name, tip_pos) in enumerate(tip_positions.items(), 1): + offset = offsets[tip_name] + scaled_pos = tip_pos * scale + np.array(offset) + + logger.debug(f" {tip_name} scaled: [{scaled_pos[0]:.4f}, {scaled_pos[1]:.4f}, {scaled_pos[2]:.4f}]") + + # Create SE3 transform for the target position (position only, no rotation) + target_transform = mink.SE3.from_translation(scaled_pos) + + # Set target for corresponding task + task = getattr(self, f'task{i}') + task.set_target(target_transform) + + # Solve IK with multiple iterations for better convergence + # Original runs continuously at 1000Hz, we approximate by iterating here + for _ in range(self.max_iters): + vel = mink.solve_ik(self.configuration, self.tasks, self.dt, self.solver, 1e-5) + self.configuration.integrate_inplace(vel, self.dt) + + # Read joint positions + motor_positions = { + 'index_m1': self.data.joint(self.joint_ids['finger1_motor1']).qpos[0], + 'index_m2': self.data.joint(self.joint_ids['finger1_motor2']).qpos[0], + 'middle_m1': self.data.joint(self.joint_ids['finger2_motor1']).qpos[0], + 'middle_m2': self.data.joint(self.joint_ids['finger2_motor2']).qpos[0], + 'ring_m1': self.data.joint(self.joint_ids['finger3_motor1']).qpos[0], + 'ring_m2': self.data.joint(self.joint_ids['finger3_motor2']).qpos[0], + 'thumb_m1': self.data.joint(self.joint_ids['finger4_motor1']).qpos[0], + 'thumb_m2': self.data.joint(self.joint_ids['finger4_motor2']).qpos[0], + } + + # Apply motor offsets from original l_hand.toml config + # These offsets calibrate the robot hand to match the simulation + motor_offsets = { + 'index_m1': 0.031, + 'index_m2': 0.173, + 'middle_m1': -0.601, + 'middle_m2': 0.614, + 'ring_m1': -0.990, + 'ring_m2': 0.915, + 'thumb_m1': -0.101, + 'thumb_m2': 0.511, + } + + for motor in motor_positions: + motor_positions[motor] += motor_offsets[motor] + + # Log output joint angles (in radians) AFTER offsets + logger.debug(f"IK output joint angles (radians) AFTER offsets:") + logger.debug(f" index: m1={motor_positions['index_m1']:.4f}, m2={motor_positions['index_m2']:.4f}") + logger.debug(f" middle: m1={motor_positions['middle_m1']:.4f}, m2={motor_positions['middle_m2']:.4f}") + logger.debug(f" ring: m1={motor_positions['ring_m1']:.4f}, m2={motor_positions['ring_m2']:.4f}") + logger.debug(f" thumb: m1={motor_positions['thumb_m1']:.4f}, m2={motor_positions['thumb_m2']:.4f}") + + return motor_positions + + except Exception as e: + logger.error(f"IK solve failed: {e}", exc_info=True) + # Fallback to approximation + return self._solve_magnitude(tip_positions) + + def _solve_magnitude(self, tip_positions: dict[str, np.ndarray]) -> dict[str, float]: + """ + Fallback approximation using vector magnitude. + This is what we use when MuJoCo/mink is not available. + """ + tip1_mag = np.linalg.norm(tip_positions['tip1']) + tip2_mag = np.linalg.norm(tip_positions['tip2']) + tip3_mag = np.linalg.norm(tip_positions['tip3']) + tip4_mag = np.linalg.norm(tip_positions['tip4']) + + return { + 'index_m1': tip1_mag, + 'index_m2': tip1_mag, + 'middle_m1': tip2_mag, + 'middle_m2': tip2_mag, + 'ring_m1': tip3_mag, + 'ring_m2': tip3_mag, + 'thumb_m1': tip4_mag, + 'thumb_m2': tip4_mag, + } diff --git a/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/config.json b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/config.json new file mode 100644 index 00000000000..0649e926bdc --- /dev/null +++ b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/config.json @@ -0,0 +1,24 @@ +{ + // Document URL, MuJoCo output + "url": "https://cad.onshape.com/documents/430ff184cf3dd9557aaff2be/w/e3658b7152c139971d22c688/e/a30f3af671aa926642803c67", + "output_format": "mujoco", + + "ignore": { + "*": "collision", + }, + + // Disable the freejoint to fix the robot + "freejoint": false, + + "additional_xml": ["joints_properties.xml", "additional.xml"], + + // Don't create actuators for passive joints + "joint_properties": { + "passive*": {"actuated": false}, + "motor*": { + "class": "chosen_actuator" + } + } + + +} diff --git a/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/setup.py b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/setup.py new file mode 100644 index 00000000000..63f77cfc541 --- /dev/null +++ b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/setup.py @@ -0,0 +1,29 @@ +from setuptools import setup, find_packages + +setup( + name="lerobot_teleoperator_amazinghandtracker", + version="0.0.1", + description="LeRobot AmazinghandTracker integration", + author="Raid", + author_email="m.raiddanial@gmail.com", + packages=find_packages(), + install_requires=[ + "numpy", + "teleop", + "lerobot", + "opencv-python", + "scipy", + "numpy", + "mediapipe", + ], + python_requires=">=3.7", + classifiers=[ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: Apache License", + "Operating System :: OS Independent", + ], +) + + + + diff --git a/src/lerobot/teleoperators/utils.py b/src/lerobot/teleoperators/utils.py index ada7ee8a1d8..f7c3a913d8c 100644 --- a/src/lerobot/teleoperators/utils.py +++ b/src/lerobot/teleoperators/utils.py @@ -85,6 +85,10 @@ def make_teleoperator_from_config(config: TeleoperatorConfig) -> Teleoperator: from .reachy2_teleoperator import Reachy2Teleoperator return Reachy2Teleoperator(config) + elif config.type == "lerobot_teleoperator_amazinghandtracker": + from .lerobot_teleoperator_amazinghandtracker.lerobot_teleoperator_amazinghandtracker.amazinghandtracker import AmazingHandTracker + + return AmazingHandTracker(config) else: try: return cast(Teleoperator, make_device_from_device_class(config)) diff --git a/src/lerobot/utils/import_utils.py b/src/lerobot/utils/import_utils.py index de43e58dbf5..4751d30ecfe 100644 --- a/src/lerobot/utils/import_utils.py +++ b/src/lerobot/utils/import_utils.py @@ -136,7 +136,7 @@ def register_third_party_devices() -> None: Scans top-level modules on sys.path for packages starting with 'lerobot_robot_', 'lerobot_camera_' or 'lerobot_teleoperator_' and imports them. """ - prefixes = ("lerobot_robot_", "lerobot_camera_", "lerobot_teleoperator_") + prefixes = ("__editable___lerobot_robot_", "lerobot_camera_", "__editable___lerobot_teleoperator_") imported: list[str] = [] failed: list[str] = [] diff --git a/src/lerobot/utils/visualization_utils.py b/src/lerobot/utils/visualization_utils.py index 95fdb178a41..3593fbe279a 100644 --- a/src/lerobot/utils/visualization_utils.py +++ b/src/lerobot/utils/visualization_utils.py @@ -40,6 +40,7 @@ def _is_scalar(x): def log_rerun_data( observation: dict[str, Any] | None = None, action: dict[str, Any] | None = None, + robot_action: dict[str, Any] | None = None, ) -> None: """ Logs observation and action data to Rerun for real-time visualization. @@ -57,6 +58,7 @@ def log_rerun_data( Args: observation: An optional dictionary containing observation data to log. action: An optional dictionary containing action data to log. + robot_action: An optional dictionary containing the actual commands sent to robot motors. """ if observation: for k, v in observation.items(): @@ -94,3 +96,21 @@ def log_rerun_data( flat = v.flatten() for i, vi in enumerate(flat): rr.log(f"{key}_{i}", rr.Scalar(float(vi))) + + if robot_action: + for k, v in robot_action.items(): + if v is None: + continue + key = k if str(k).startswith("robot_action.") else f"robot_action.{k}" + + if _is_scalar(v): + rr.log(key, rr.Scalar(float(v))) + elif isinstance(v, np.ndarray): + if v.ndim == 1: + for i, vi in enumerate(v): + rr.log(f"{key}_{i}", rr.Scalar(float(vi))) + else: + # Fall back to flattening higher-dimensional arrays + flat = v.flatten() + for i, vi in enumerate(flat): + rr.log(f"{key}_{i}", rr.Scalar(float(vi))) From 2c01e280a6cfde68bd283a899142bfb9212abaab Mon Sep 17 00:00:00 2001 From: raiddanial Date: Tue, 6 Jan 2026 10:54:09 +0800 Subject: [PATCH 04/12] Add MuJoCo XML model files for AmazingHand (removed *.xml from .gitignore) --- .../robot/amazinghand/mjcf/additional.xml | 6 + .../amazinghand/mjcf/joints_properties.xml | 29 + .../robot/amazinghand/mjcf/keyframes.xml | 7 + .../configs/robot/amazinghand/mjcf/robot.xml | 744 ++++++++++++++++++ .../configs/robot/amazinghand/mjcf/scene.xml | 59 ++ .../AH_Left/mjcf/additional.xml | 6 + .../AH_Left/mjcf/joints_properties.xml | 29 + .../AH_Left/mjcf/keyframes.xml | 7 + .../AH_Left/mjcf/robot.xml | 744 ++++++++++++++++++ .../AH_Left/mjcf/scene.xml | 59 ++ .../AH_Right/mjcf/additional.xml | 6 + .../AH_Right/mjcf/joints_properties.xml | 29 + .../AH_Right/mjcf/keyframes.xml | 7 + .../AH_Right/mjcf/robot.xml | 744 ++++++++++++++++++ .../AH_Right/mjcf/scene.xml | 42 + .../mjcf/additional.xml | 6 + .../mjcf/joints_properties.xml | 29 + .../mjcf/keyframes.xml | 7 + .../mjcf/robot.xml | 744 ++++++++++++++++++ .../mjcf/scene.xml | 59 ++ 20 files changed, 3363 insertions(+) create mode 100644 src/lerobot/configs/robot/amazinghand/mjcf/additional.xml create mode 100644 src/lerobot/configs/robot/amazinghand/mjcf/joints_properties.xml create mode 100644 src/lerobot/configs/robot/amazinghand/mjcf/keyframes.xml create mode 100644 src/lerobot/configs/robot/amazinghand/mjcf/robot.xml create mode 100644 src/lerobot/configs/robot/amazinghand/mjcf/scene.xml create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/additional.xml create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/joints_properties.xml create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/keyframes.xml create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/robot.xml create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/scene.xml create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/additional.xml create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/joints_properties.xml create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/keyframes.xml create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/robot.xml create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/scene.xml create mode 100644 src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/additional.xml create mode 100644 src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/joints_properties.xml create mode 100644 src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/keyframes.xml create mode 100644 src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/robot.xml create mode 100644 src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/scene.xml diff --git a/src/lerobot/configs/robot/amazinghand/mjcf/additional.xml b/src/lerobot/configs/robot/amazinghand/mjcf/additional.xml new file mode 100644 index 00000000000..6af78aae57d --- /dev/null +++ b/src/lerobot/configs/robot/amazinghand/mjcf/additional.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/lerobot/configs/robot/amazinghand/mjcf/joints_properties.xml b/src/lerobot/configs/robot/amazinghand/mjcf/joints_properties.xml new file mode 100644 index 00000000000..747bbb8be4c --- /dev/null +++ b/src/lerobot/configs/robot/amazinghand/mjcf/joints_properties.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/lerobot/configs/robot/amazinghand/mjcf/keyframes.xml b/src/lerobot/configs/robot/amazinghand/mjcf/keyframes.xml new file mode 100644 index 00000000000..1a39a375dcb --- /dev/null +++ b/src/lerobot/configs/robot/amazinghand/mjcf/keyframes.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/src/lerobot/configs/robot/amazinghand/mjcf/robot.xml b/src/lerobot/configs/robot/amazinghand/mjcf/robot.xml new file mode 100644 index 00000000000..7884b3804c1 --- /dev/null +++ b/src/lerobot/configs/robot/amazinghand/mjcf/robot.xml @@ -0,0 +1,744 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/lerobot/configs/robot/amazinghand/mjcf/scene.xml b/src/lerobot/configs/robot/amazinghand/mjcf/scene.xml new file mode 100644 index 00000000000..46d8cb23c67 --- /dev/null +++ b/src/lerobot/configs/robot/amazinghand/mjcf/scene.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/additional.xml b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/additional.xml new file mode 100644 index 00000000000..6af78aae57d --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/additional.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/joints_properties.xml b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/joints_properties.xml new file mode 100644 index 00000000000..747bbb8be4c --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/joints_properties.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/keyframes.xml b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/keyframes.xml new file mode 100644 index 00000000000..1a39a375dcb --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/keyframes.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/robot.xml b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/robot.xml new file mode 100644 index 00000000000..7884b3804c1 --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/robot.xml @@ -0,0 +1,744 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/scene.xml b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/scene.xml new file mode 100644 index 00000000000..46d8cb23c67 --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/scene.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/additional.xml b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/additional.xml new file mode 100644 index 00000000000..6af78aae57d --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/additional.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/joints_properties.xml b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/joints_properties.xml new file mode 100644 index 00000000000..747bbb8be4c --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/joints_properties.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/keyframes.xml b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/keyframes.xml new file mode 100644 index 00000000000..1a39a375dcb --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/keyframes.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/robot.xml b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/robot.xml new file mode 100644 index 00000000000..fa8cd139348 --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/robot.xml @@ -0,0 +1,744 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/scene.xml b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/scene.xml new file mode 100644 index 00000000000..4f6e3bd0692 --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/scene.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/additional.xml b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/additional.xml new file mode 100644 index 00000000000..6af78aae57d --- /dev/null +++ b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/additional.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/joints_properties.xml b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/joints_properties.xml new file mode 100644 index 00000000000..747bbb8be4c --- /dev/null +++ b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/joints_properties.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/keyframes.xml b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/keyframes.xml new file mode 100644 index 00000000000..1a39a375dcb --- /dev/null +++ b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/keyframes.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/robot.xml b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/robot.xml new file mode 100644 index 00000000000..7884b3804c1 --- /dev/null +++ b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/robot.xml @@ -0,0 +1,744 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/scene.xml b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/scene.xml new file mode 100644 index 00000000000..46d8cb23c67 --- /dev/null +++ b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/scene.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 8fb3316f854b08c0e561d74c384e9c4aa78c2340 Mon Sep 17 00:00:00 2001 From: raiddanial Date: Tue, 6 Jan 2026 10:57:11 +0800 Subject: [PATCH 05/12] Add STL mesh files for AmazingHand (removed *.stl and *.urdf from .gitignore) --- .../AH_Left/mjcf/assets/bushing_0608_04.stl | 3 +++ .../AH_Left/mjcf/assets/custom_servo_horn.stl | 3 +++ .../lerobot_robot_amazinghand/AH_Left/mjcf/assets/distal.stl | 3 +++ .../AH_Left/mjcf/assets/distal_shell.stl | 3 +++ .../AH_Left/mjcf/assets/finger_frame_1.stl | 3 +++ .../AH_Left/mjcf/assets/finger_frame_2.stl | 3 +++ .../lerobot_robot_amazinghand/AH_Left/mjcf/assets/gimbal.stl | 3 +++ .../AH_Left/mjcf/assets/l_hand_plate.stl | 3 +++ .../AH_Left/mjcf/assets/l_wrist_interface.stl | 3 +++ .../lerobot_robot_amazinghand/AH_Left/mjcf/assets/link.stl | 3 +++ .../AH_Left/mjcf/assets/m2_rod_l18.stl | 3 +++ .../parallel_pin_2_x_10__fee063fca0c8b40e46bbc4ffff61d999.stl | 3 +++ .../parallel_pin_2_x_16__da4b7ddbe9d803fe3fbc70f2e822b99b.stl | 3 +++ ...ad_screw_m2x0_40_x_10__2803432263e518bbd16bccbbef8784ed.stl | 3 +++ ...er_large_grade_a_m2_5__9a369f0dc77bf9c598cdf3fb468977e5.stl | 3 +++ .../lerobot_robot_amazinghand/AH_Left/mjcf/assets/proximal.stl | 3 +++ .../AH_Left/mjcf/assets/proximal_shell.stl | 3 +++ .../AH_Left/mjcf/assets/rotule_ball.stl | 3 +++ .../AH_Left/mjcf/assets/rotule_lever.stl | 3 +++ .../lerobot_robot_amazinghand/AH_Left/mjcf/assets/scs0009.stl | 3 +++ .../lerobot_robot_amazinghand/AH_Left/mjcf/assets/spacer.stl | 3 +++ ...333_plast_tcb_torx_2_5x8__configuration_copy_of_default.stl | 3 +++ .../std00447_thermoplastique_m2_5x6__configuration_default.stl | 3 +++ .../AH_Right/mjcf/assets/bushing_0608_04.stl | 3 +++ .../AH_Right/mjcf/assets/custom_servo_horn.stl | 3 +++ .../lerobot_robot_amazinghand/AH_Right/mjcf/assets/dist.stl | 3 +++ .../AH_Right/mjcf/assets/dist_shell.stl | 3 +++ .../lerobot_robot_amazinghand/AH_Right/mjcf/assets/distal.stl | 3 +++ .../AH_Right/mjcf/assets/distal_shell.stl | 3 +++ .../AH_Right/mjcf/assets/finger_frame_1.stl | 3 +++ .../AH_Right/mjcf/assets/finger_frame_2.stl | 3 +++ .../lerobot_robot_amazinghand/AH_Right/mjcf/assets/gimbal.stl | 3 +++ .../lerobot_robot_amazinghand/AH_Right/mjcf/assets/link.stl | 3 +++ .../AH_Right/mjcf/assets/m2_rod_l18.stl | 3 +++ .../parallel_pin_2_x_10__fee063fca0c8b40e46bbc4ffff61d999.stl | 3 +++ .../parallel_pin_2_x_16__da4b7ddbe9d803fe3fbc70f2e822b99b.stl | 3 +++ .../lerobot_robot_amazinghand/AH_Right/mjcf/assets/part_1.stl | 3 +++ .../AH_Right/mjcf/assets/part_1__2.stl | 3 +++ ...ad_screw_m2x0_40_x_10__2803432263e518bbd16bccbbef8784ed.stl | 3 +++ ...er_large_grade_a_m2_5__9a369f0dc77bf9c598cdf3fb468977e5.stl | 3 +++ .../lerobot_robot_amazinghand/AH_Right/mjcf/assets/prox.stl | 3 +++ .../AH_Right/mjcf/assets/prox_shell.stl | 3 +++ .../AH_Right/mjcf/assets/proximal.stl | 3 +++ .../AH_Right/mjcf/assets/proximal_shell.stl | 3 +++ .../AH_Right/mjcf/assets/r_hand_plate.stl | 3 +++ .../AH_Right/mjcf/assets/r_wrist_interface.stl | 3 +++ .../AH_Right/mjcf/assets/rotule_ball.stl | 3 +++ .../AH_Right/mjcf/assets/rotule_lever.stl | 3 +++ .../lerobot_robot_amazinghand/AH_Right/mjcf/assets/scs0009.stl | 3 +++ .../lerobot_robot_amazinghand/AH_Right/mjcf/assets/spacer.stl | 3 +++ ...333_plast_tcb_torx_2_5x8__configuration_copy_of_default.stl | 3 +++ .../std00447_thermoplastique_m2_5x6__configuration_default.stl | 3 +++ 52 files changed, 156 insertions(+) create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/bushing_0608_04.stl create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/custom_servo_horn.stl create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/distal.stl create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/distal_shell.stl create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/finger_frame_1.stl create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/finger_frame_2.stl create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/gimbal.stl create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/l_hand_plate.stl create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/l_wrist_interface.stl create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/link.stl create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/m2_rod_l18.stl create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/parallel_pin_2_x_10__fee063fca0c8b40e46bbc4ffff61d999.stl create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/parallel_pin_2_x_16__da4b7ddbe9d803fe3fbc70f2e822b99b.stl create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/ph_pan_head_screw_m2x0_40_x_10__2803432263e518bbd16bccbbef8784ed.stl create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/plain_washer_large_grade_a_m2_5__9a369f0dc77bf9c598cdf3fb468977e5.stl create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/proximal.stl create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/proximal_shell.stl create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/rotule_ball.stl create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/rotule_lever.stl create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/scs0009.stl create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/spacer.stl create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/std00333_plast_tcb_torx_2_5x8__configuration_copy_of_default.stl create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/std00447_thermoplastique_m2_5x6__configuration_default.stl create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/bushing_0608_04.stl create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/custom_servo_horn.stl create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/dist.stl create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/dist_shell.stl create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/distal.stl create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/distal_shell.stl create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/finger_frame_1.stl create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/finger_frame_2.stl create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/gimbal.stl create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/link.stl create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/m2_rod_l18.stl create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/parallel_pin_2_x_10__fee063fca0c8b40e46bbc4ffff61d999.stl create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/parallel_pin_2_x_16__da4b7ddbe9d803fe3fbc70f2e822b99b.stl create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/part_1.stl create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/part_1__2.stl create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/ph_pan_head_screw_m2x0_40_x_10__2803432263e518bbd16bccbbef8784ed.stl create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/plain_washer_large_grade_a_m2_5__9a369f0dc77bf9c598cdf3fb468977e5.stl create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/prox.stl create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/prox_shell.stl create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/proximal.stl create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/proximal_shell.stl create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/r_hand_plate.stl create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/r_wrist_interface.stl create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/rotule_ball.stl create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/rotule_lever.stl create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/scs0009.stl create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/spacer.stl create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/std00333_plast_tcb_torx_2_5x8__configuration_copy_of_default.stl create mode 100644 src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/std00447_thermoplastique_m2_5x6__configuration_default.stl diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/bushing_0608_04.stl b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/bushing_0608_04.stl new file mode 100644 index 00000000000..505b3337ed2 --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/bushing_0608_04.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a4654c50399a5a2e2e929aaa9cc81b9243b4542a85bf3896838c65fb935709ca +size 43284 diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/custom_servo_horn.stl b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/custom_servo_horn.stl new file mode 100644 index 00000000000..da6a908ac11 --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/custom_servo_horn.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ac1bf3bf06f64f2b8b85e48c1605a92c0fbd5643f0a01345aad81db8bb850dfa +size 86084 diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/distal.stl b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/distal.stl new file mode 100644 index 00000000000..7748343a3d4 --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/distal.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e3aeab19ecad616e5b859d5326c2966d8dd5db196f9217c340ed5b762e85567b +size 177884 diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/distal_shell.stl b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/distal_shell.stl new file mode 100644 index 00000000000..02cd355a1e0 --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/distal_shell.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0a1e136158fb5464704b68d37d30eae450c1a3b507bf694f429d914e4419557b +size 1195884 diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/finger_frame_1.stl b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/finger_frame_1.stl new file mode 100644 index 00000000000..8d72fe07346 --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/finger_frame_1.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:50c439686722f30d165c1604a1ffdb3289c6b4af3d68ff79ca0daabd25251f49 +size 157284 diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/finger_frame_2.stl b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/finger_frame_2.stl new file mode 100644 index 00000000000..2897a272e23 --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/finger_frame_2.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0e618d270bfcb5055bfdf6bb41c52c2098ef78e25d43e7972df346e5085cf91f +size 94884 diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/gimbal.stl b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/gimbal.stl new file mode 100644 index 00000000000..9091c42ef63 --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/gimbal.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:54cdf9367ab1b30419c750b37c18c38d03425056beba1a56192287414496c50e +size 120784 diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/l_hand_plate.stl b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/l_hand_plate.stl new file mode 100644 index 00000000000..3fe4c6d76a5 --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/l_hand_plate.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:90ab794d14a277a7d2db17a4c1956a4b025967bf4ba0d934aa271f119179bc1c +size 718884 diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/l_wrist_interface.stl b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/l_wrist_interface.stl new file mode 100644 index 00000000000..9d10cc67917 --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/l_wrist_interface.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d130a3c6be00cba6186ccfd7d65bf09c558c9e632996729ff06989e117bef1e7 +size 825584 diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/link.stl b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/link.stl new file mode 100644 index 00000000000..a8f1ab9ba19 --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/link.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:77acb1b7675a799fed66ca06224d484dc371c76c3308e626fd339ecc6086917b +size 97884 diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/m2_rod_l18.stl b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/m2_rod_l18.stl new file mode 100644 index 00000000000..df7684dc646 --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/m2_rod_l18.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f78dcdda6669f36df4de6241f7869362e80fc4e7bd9fa0cf68eb22564d5d131e +size 14484 diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/parallel_pin_2_x_10__fee063fca0c8b40e46bbc4ffff61d999.stl b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/parallel_pin_2_x_10__fee063fca0c8b40e46bbc4ffff61d999.stl new file mode 100644 index 00000000000..d1ce9e06299 --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/parallel_pin_2_x_10__fee063fca0c8b40e46bbc4ffff61d999.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:01c8bb5014a46b355171bf4672a4b81602caab7d43751eefa7cef2362c12fb0f +size 28884 diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/parallel_pin_2_x_16__da4b7ddbe9d803fe3fbc70f2e822b99b.stl b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/parallel_pin_2_x_16__da4b7ddbe9d803fe3fbc70f2e822b99b.stl new file mode 100644 index 00000000000..4d505b6ecb8 --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/parallel_pin_2_x_16__da4b7ddbe9d803fe3fbc70f2e822b99b.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:34bf98bac12bf7d6ec7d0024f691e2aa866a7e309344a9fd4f3b8fcc49d73f7e +size 28884 diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/ph_pan_head_screw_m2x0_40_x_10__2803432263e518bbd16bccbbef8784ed.stl b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/ph_pan_head_screw_m2x0_40_x_10__2803432263e518bbd16bccbbef8784ed.stl new file mode 100644 index 00000000000..e9f0b407b42 --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/ph_pan_head_screw_m2x0_40_x_10__2803432263e518bbd16bccbbef8784ed.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3026c6ffc1b7012cce54aa32a0b33aab7719539ea4df675f84e34223340c05c1 +size 218484 diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/plain_washer_large_grade_a_m2_5__9a369f0dc77bf9c598cdf3fb468977e5.stl b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/plain_washer_large_grade_a_m2_5__9a369f0dc77bf9c598cdf3fb468977e5.stl new file mode 100644 index 00000000000..156aadb198c --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/plain_washer_large_grade_a_m2_5__9a369f0dc77bf9c598cdf3fb468977e5.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d48a6eb3cb410a29a68ad034b9e51f8fd45e8f4f0da6323a4840e6e19020e0c7 +size 28884 diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/proximal.stl b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/proximal.stl new file mode 100644 index 00000000000..323fb83c33e --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/proximal.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:167f17b5994a179f13b0ca5887740f5f9580604ea7f767d72ff9ad3516f16485 +size 270484 diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/proximal_shell.stl b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/proximal_shell.stl new file mode 100644 index 00000000000..4c79a74ab9e --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/proximal_shell.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:da46e0edefe0f245db0fc6b2230f97dd47a104da7c6c9cf10a923e8d27c742fa +size 357784 diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/rotule_ball.stl b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/rotule_ball.stl new file mode 100644 index 00000000000..bdc946d82f8 --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/rotule_ball.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c78afd69e2d4c0fada853805771e25fc0547dd85b1764e3e58d8bf5b1188c64e +size 201684 diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/rotule_lever.stl b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/rotule_lever.stl new file mode 100644 index 00000000000..194287402f7 --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/rotule_lever.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e50867e9e235a312d5b3aa279aae7cc8a29c705ef0dbfb6d89b0bb30364c5f84 +size 215084 diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/scs0009.stl b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/scs0009.stl new file mode 100644 index 00000000000..fbe8c10d9d3 --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/scs0009.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8a663fa3796995ac1b2c7186f8d67b1ae7af8e6c7a415d16b7ee832233449ac2 +size 394384 diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/spacer.stl b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/spacer.stl new file mode 100644 index 00000000000..36ffb4db14b --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/spacer.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:216a38b439575331c784abe34b81b6ecca092a816588dd06ed24c9bd45cd2cc8 +size 28884 diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/std00333_plast_tcb_torx_2_5x8__configuration_copy_of_default.stl b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/std00333_plast_tcb_torx_2_5x8__configuration_copy_of_default.stl new file mode 100644 index 00000000000..f0c0a909fe4 --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/std00333_plast_tcb_torx_2_5x8__configuration_copy_of_default.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5b16f1bdd7f88c443697e30353512139e38c2d98a333c9653f2b9cb56fb84d4a +size 177584 diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/std00447_thermoplastique_m2_5x6__configuration_default.stl b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/std00447_thermoplastique_m2_5x6__configuration_default.stl new file mode 100644 index 00000000000..e31af7c8085 --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Left/mjcf/assets/std00447_thermoplastique_m2_5x6__configuration_default.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:62340db6254431a4db1fcc862c6d91dd18119a28c9af4b10d6fd4eea112e05b3 +size 177584 diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/bushing_0608_04.stl b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/bushing_0608_04.stl new file mode 100644 index 00000000000..505b3337ed2 --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/bushing_0608_04.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a4654c50399a5a2e2e929aaa9cc81b9243b4542a85bf3896838c65fb935709ca +size 43284 diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/custom_servo_horn.stl b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/custom_servo_horn.stl new file mode 100644 index 00000000000..da6a908ac11 --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/custom_servo_horn.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ac1bf3bf06f64f2b8b85e48c1605a92c0fbd5643f0a01345aad81db8bb850dfa +size 86084 diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/dist.stl b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/dist.stl new file mode 100644 index 00000000000..e41e6ad49ed --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/dist.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b711f23dfd2eb17bf5c61fcf3bfbe663801f5ec29290246e051c549a0580199f +size 134684 diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/dist_shell.stl b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/dist_shell.stl new file mode 100644 index 00000000000..bbb783a4f42 --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/dist_shell.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c1560c7624cfd01b0958c9896aff0e0a13c2bcdfa2cec4e97e278877c7fa0945 +size 1194284 diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/distal.stl b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/distal.stl new file mode 100644 index 00000000000..7748343a3d4 --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/distal.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e3aeab19ecad616e5b859d5326c2966d8dd5db196f9217c340ed5b762e85567b +size 177884 diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/distal_shell.stl b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/distal_shell.stl new file mode 100644 index 00000000000..02cd355a1e0 --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/distal_shell.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0a1e136158fb5464704b68d37d30eae450c1a3b507bf694f429d914e4419557b +size 1195884 diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/finger_frame_1.stl b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/finger_frame_1.stl new file mode 100644 index 00000000000..8d72fe07346 --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/finger_frame_1.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:50c439686722f30d165c1604a1ffdb3289c6b4af3d68ff79ca0daabd25251f49 +size 157284 diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/finger_frame_2.stl b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/finger_frame_2.stl new file mode 100644 index 00000000000..2897a272e23 --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/finger_frame_2.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0e618d270bfcb5055bfdf6bb41c52c2098ef78e25d43e7972df346e5085cf91f +size 94884 diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/gimbal.stl b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/gimbal.stl new file mode 100644 index 00000000000..9091c42ef63 --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/gimbal.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:54cdf9367ab1b30419c750b37c18c38d03425056beba1a56192287414496c50e +size 120784 diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/link.stl b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/link.stl new file mode 100644 index 00000000000..a8f1ab9ba19 --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/link.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:77acb1b7675a799fed66ca06224d484dc371c76c3308e626fd339ecc6086917b +size 97884 diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/m2_rod_l18.stl b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/m2_rod_l18.stl new file mode 100644 index 00000000000..df7684dc646 --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/m2_rod_l18.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f78dcdda6669f36df4de6241f7869362e80fc4e7bd9fa0cf68eb22564d5d131e +size 14484 diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/parallel_pin_2_x_10__fee063fca0c8b40e46bbc4ffff61d999.stl b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/parallel_pin_2_x_10__fee063fca0c8b40e46bbc4ffff61d999.stl new file mode 100644 index 00000000000..d1ce9e06299 --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/parallel_pin_2_x_10__fee063fca0c8b40e46bbc4ffff61d999.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:01c8bb5014a46b355171bf4672a4b81602caab7d43751eefa7cef2362c12fb0f +size 28884 diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/parallel_pin_2_x_16__da4b7ddbe9d803fe3fbc70f2e822b99b.stl b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/parallel_pin_2_x_16__da4b7ddbe9d803fe3fbc70f2e822b99b.stl new file mode 100644 index 00000000000..4d505b6ecb8 --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/parallel_pin_2_x_16__da4b7ddbe9d803fe3fbc70f2e822b99b.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:34bf98bac12bf7d6ec7d0024f691e2aa866a7e309344a9fd4f3b8fcc49d73f7e +size 28884 diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/part_1.stl b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/part_1.stl new file mode 100644 index 00000000000..58e1cb178a6 --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/part_1.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:30b8dbc9cd522da79eef3dc52e9ebb25c97754d0193acfbb08ee4c0582d93de0 +size 43284 diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/part_1__2.stl b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/part_1__2.stl new file mode 100644 index 00000000000..c9fc60a316c --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/part_1__2.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9d3d05c137af93785e1b7618f51f5aec72c3800786564a1c8279b447f2557bd8 +size 36184 diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/ph_pan_head_screw_m2x0_40_x_10__2803432263e518bbd16bccbbef8784ed.stl b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/ph_pan_head_screw_m2x0_40_x_10__2803432263e518bbd16bccbbef8784ed.stl new file mode 100644 index 00000000000..e9f0b407b42 --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/ph_pan_head_screw_m2x0_40_x_10__2803432263e518bbd16bccbbef8784ed.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3026c6ffc1b7012cce54aa32a0b33aab7719539ea4df675f84e34223340c05c1 +size 218484 diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/plain_washer_large_grade_a_m2_5__9a369f0dc77bf9c598cdf3fb468977e5.stl b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/plain_washer_large_grade_a_m2_5__9a369f0dc77bf9c598cdf3fb468977e5.stl new file mode 100644 index 00000000000..156aadb198c --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/plain_washer_large_grade_a_m2_5__9a369f0dc77bf9c598cdf3fb468977e5.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d48a6eb3cb410a29a68ad034b9e51f8fd45e8f4f0da6323a4840e6e19020e0c7 +size 28884 diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/prox.stl b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/prox.stl new file mode 100644 index 00000000000..584b618eec5 --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/prox.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fa5227c3ea44424be94b50e8ac66701f24a6d2bafbe69ad2456cd761900178b9 +size 227084 diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/prox_shell.stl b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/prox_shell.stl new file mode 100644 index 00000000000..6ade76148d5 --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/prox_shell.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:37ad012dd3ff5cf301322b0f5c54f35616343365c962e4b29378f5e7ef6d4bf7 +size 360084 diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/proximal.stl b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/proximal.stl new file mode 100644 index 00000000000..323fb83c33e --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/proximal.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:167f17b5994a179f13b0ca5887740f5f9580604ea7f767d72ff9ad3516f16485 +size 270484 diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/proximal_shell.stl b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/proximal_shell.stl new file mode 100644 index 00000000000..4c79a74ab9e --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/proximal_shell.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:da46e0edefe0f245db0fc6b2230f97dd47a104da7c6c9cf10a923e8d27c742fa +size 357784 diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/r_hand_plate.stl b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/r_hand_plate.stl new file mode 100644 index 00000000000..455506137a1 --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/r_hand_plate.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0dc8fd0db8aba29c7c2effdeeee7eb58279ab282a69a1d9cf189421cd55357fe +size 725084 diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/r_wrist_interface.stl b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/r_wrist_interface.stl new file mode 100644 index 00000000000..99760a7cdb7 --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/r_wrist_interface.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:488a7333dad4b9b162643249a9f2b59408725bd8ae276385ee2f793a4c25d5f1 +size 825984 diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/rotule_ball.stl b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/rotule_ball.stl new file mode 100644 index 00000000000..bdc946d82f8 --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/rotule_ball.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c78afd69e2d4c0fada853805771e25fc0547dd85b1764e3e58d8bf5b1188c64e +size 201684 diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/rotule_lever.stl b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/rotule_lever.stl new file mode 100644 index 00000000000..194287402f7 --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/rotule_lever.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e50867e9e235a312d5b3aa279aae7cc8a29c705ef0dbfb6d89b0bb30364c5f84 +size 215084 diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/scs0009.stl b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/scs0009.stl new file mode 100644 index 00000000000..fbe8c10d9d3 --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/scs0009.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8a663fa3796995ac1b2c7186f8d67b1ae7af8e6c7a415d16b7ee832233449ac2 +size 394384 diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/spacer.stl b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/spacer.stl new file mode 100644 index 00000000000..36ffb4db14b --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/spacer.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:216a38b439575331c784abe34b81b6ecca092a816588dd06ed24c9bd45cd2cc8 +size 28884 diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/std00333_plast_tcb_torx_2_5x8__configuration_copy_of_default.stl b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/std00333_plast_tcb_torx_2_5x8__configuration_copy_of_default.stl new file mode 100644 index 00000000000..f0c0a909fe4 --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/std00333_plast_tcb_torx_2_5x8__configuration_copy_of_default.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5b16f1bdd7f88c443697e30353512139e38c2d98a333c9653f2b9cb56fb84d4a +size 177584 diff --git a/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/std00447_thermoplastique_m2_5x6__configuration_default.stl b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/std00447_thermoplastique_m2_5x6__configuration_default.stl new file mode 100644 index 00000000000..e31af7c8085 --- /dev/null +++ b/src/lerobot/robots/lerobot_robot_amazinghand/lerobot_robot_amazinghand/AH_Right/mjcf/assets/std00447_thermoplastique_m2_5x6__configuration_default.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:62340db6254431a4db1fcc862c6d91dd18119a28c9af4b10d6fd4eea112e05b3 +size 177584 From e70056106e8bc228a3d6eb7efe2bfb0f7d8f9c7e Mon Sep 17 00:00:00 2001 From: raiddanial Date: Tue, 6 Jan 2026 11:30:45 +0800 Subject: [PATCH 06/12] Add auto-discovery for teleoperator packages (fixes amazinghandtracker registration) --- src/lerobot/teleoperators/__init__.py | 50 +++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/src/lerobot/teleoperators/__init__.py b/src/lerobot/teleoperators/__init__.py index ee508dddb3c..49fe25cce47 100644 --- a/src/lerobot/teleoperators/__init__.py +++ b/src/lerobot/teleoperators/__init__.py @@ -17,3 +17,53 @@ from .config import TeleoperatorConfig from .teleoperator import Teleoperator from .utils import TeleopEvents, make_teleoperator_from_config +import importlib +import pkgutil + +# Ensure all teleoperator subpackages are imported so their config classes get registered +# with TeleoperatorConfig (via the @TeleoperatorConfig.register_subclass decorator). This +# makes sure dynamic choice lists (e.g. --teleop.type) include third-party +# or local teleoperator implementations when the package is imported (such as by +# console scripts). +for _finder, pkg_name, _ispkg in pkgutil.iter_modules(__path__, __name__ + "."): + try: + mod = importlib.import_module(pkg_name) + # If the discovered package itself contains submodules (e.g. the + # project layout places the actual Python package inside a folder + # with the same name), import its submodules as well so any + # registration performed in those submodules runs. + if hasattr(mod, "__path__"): + for _f2, sub_name, _is2 in pkgutil.iter_modules(mod.__path__, mod.__name__ + "."): + try: + importlib.import_module(sub_name) + except Exception: + # ignore failures at this level; they'll surface when that + # specific teleoperator is actually used + pass + # Additionally try to import a nested package that shares the same + # last path component. Some plugins are packaged as + # //... (an outer folder with the same name as the inner + # package). In that case, importing the outer namespace package + # doesn't automatically import the inner package; try to import + # it explicitly. + try: + for p in list(mod.__path__): + # look for a directory named like the package inside this path + import os + + inner_name = mod.__name__.split(".")[-1] + candidate = os.path.join(p, inner_name) + if os.path.isdir(candidate): + # Form the nested import path and try to import it. + nested = f"{mod.__name__}.{inner_name}" + try: + importlib.import_module(nested) + except Exception: + pass + except Exception: + pass + except Exception: + # Don't fail import of the top-level package if a single teleoperator submodule + # has issues. The specific submodule import error will surface when + # that teleoperator is actually used. + pass From a29dbfeb3b1dc3a1478e6de49b07858e0eb86363 Mon Sep 17 00:00:00 2001 From: raiddanial Date: Thu, 8 Jan 2026 10:50:28 +0800 Subject: [PATCH 07/12] Add WebRTC teleoperation system for 4G/5G remote control --- webrtc_teleoperation/README.md | 216 ++++++++++++++++++ .../pc1_leader/camera_client_webrtc.py | 193 ++++++++++++++++ .../pc1_leader/leader_sender_udp.py | 61 +++++ .../pc1_leader/requirements.txt | 4 + .../pc2_follower/camera_server_webrtc.py | 193 ++++++++++++++++ .../pc2_follower/requirements.txt | 4 + .../pc2_follower/robot_receiver_udp.py | 51 +++++ .../signaling_server/README.md | 56 +++++ .../signaling_server/requirements.txt | 2 + .../signaling_server/server.py | 120 ++++++++++ 10 files changed, 900 insertions(+) create mode 100644 webrtc_teleoperation/README.md create mode 100644 webrtc_teleoperation/pc1_leader/camera_client_webrtc.py create mode 100644 webrtc_teleoperation/pc1_leader/leader_sender_udp.py create mode 100644 webrtc_teleoperation/pc1_leader/requirements.txt create mode 100644 webrtc_teleoperation/pc2_follower/camera_server_webrtc.py create mode 100644 webrtc_teleoperation/pc2_follower/requirements.txt create mode 100644 webrtc_teleoperation/pc2_follower/robot_receiver_udp.py create mode 100644 webrtc_teleoperation/signaling_server/README.md create mode 100644 webrtc_teleoperation/signaling_server/requirements.txt create mode 100644 webrtc_teleoperation/signaling_server/server.py diff --git a/webrtc_teleoperation/README.md b/webrtc_teleoperation/README.md new file mode 100644 index 00000000000..8411bac2d26 --- /dev/null +++ b/webrtc_teleoperation/README.md @@ -0,0 +1,216 @@ +# WebRTC Teleoperation System + +Complete WebRTC-based teleoperation system for SO-101 robot with adaptive video streaming over 4G/5G networks. + +## Architecture + +``` +PC1 (Leader) PC2 (Follower) +┌─────────────────┐ ┌─────────────────┐ +│ Camera Client │◄──WebRTC (adaptive H.264)──│ Camera Server │ +│ (WebRTC) │ via signaling server │ (WebRTC) │ +└─────────────────┘ └─────────────────┘ + │ │ +┌─────────────────┐ ┌─────────────────┐ +│ SO-101 Leader │─────UDP (commands)────────►│ SO-101 Follower │ +│ Sender │ Direct IP connection │ Receiver │ +└─────────────────┘ └─────────────────┘ +``` + +**Key Benefits:** +- **WebRTC for camera**: Adaptive bitrate, low latency (50-200ms), handles 4G/5G transitions +- **UDP for robot control**: Simple, proven, low-latency commands +- **Separate streams**: No bandwidth competition + +## Quick Start + +### Step 1: Deploy Signaling Server (One-time setup) + +#### Option A: Local Testing (Same Network) +```bash +cd signaling_server +pip install -r requirements.txt +python server.py +``` +Use `ws://YOUR_LOCAL_IP:8080/ws` in camera scripts. + +#### Option B: Deploy to Render.com (Internet Access) +See [signaling_server/README.md](signaling_server/README.md) for detailed deployment instructions. + +After deployment, you'll get a URL like: `https://your-app.onrender.com` + +### Step 2: Configure Camera Scripts + +Edit both camera scripts to use your signaling server: + +**In `pc1_leader/camera_client_webrtc.py`:** +```python +SIGNALING_SERVER = "wss://your-app.onrender.com/ws" # or ws://local-ip:8080/ws +``` + +**In `pc2_follower/camera_server_webrtc.py`:** +```python +SIGNALING_SERVER = "wss://your-app.onrender.com/ws" # or ws://local-ip:8080/ws +CAMERA_INDEX = 0 # Change to your camera index +``` + +### Step 3: Configure Robot Control Scripts + +**In `pc1_leader/leader_sender_udp.py`:** +```python +FOLLOWER_IP = "100.127.72.97" # PC2's IP address +LEADER_PORT = "COM9" # Your leader SO-101 COM port +``` + +**In `pc2_follower/robot_receiver_udp.py`:** +```python +FOLLOWER_PORT = "COM10" # Your follower SO-101 COM port +``` + +### Step 4: Install Dependencies + +**On PC1 (Leader):** +```bash +cd pc1_leader +pip install -r requirements.txt +``` + +**On PC2 (Follower):** +```bash +cd pc2_follower +pip install -r requirements.txt +``` + +### Step 5: Run the System + +**On PC2 (Follower) - Start these FIRST:** +```bash +# Terminal 1: Camera server +python camera_server_webrtc.py + +# Terminal 2: Robot receiver +python robot_receiver_udp.py +``` + +**On PC1 (Leader):** +```bash +# Terminal 1: Camera client +python camera_client_webrtc.py + +# Terminal 2: Robot control sender +python leader_sender_udp.py +``` + +## Usage + +1. **Start PC2 scripts first** (camera server and robot receiver) +2. **Then start PC1 scripts** (camera client will connect to server) +3. Camera window will open on PC1 showing follower's view +4. Move the leader SO-101 arm - follower will follow +5. Press 'q' in camera window to quit + +## Troubleshooting + +### Camera not connecting +- Check signaling server is running (visit health check URL) +- Verify both scripts use same signaling server URL +- Check firewall settings +- Look at console logs for connection errors + +### High latency on camera +- WebRTC should auto-adapt, but check: + - Network quality (ping between PCs) + - Camera resolution/FPS settings in `camera_server_webrtc.py` + - Try reducing: `FRAME_WIDTH = 320`, `FRAME_HEIGHT = 240`, `FPS = 20` + +### Robot control not working +- Verify UDP control scripts have correct: + - IP addresses + - COM ports + - Robot is powered on and connected + +### Signaling connection fails +- For local testing: Use `ws://` not `wss://` +- For deployed server: Use `wss://` not `ws://` +- Check signaling server logs + +## Network Requirements + +**Minimum bandwidth for smooth operation:** +- Camera (WebRTC): 500 Kbps - 5 Mbps (adapts automatically) +- Robot control (UDP): ~10 Kbps (negligible) + +**Tested on:** +- ✅ Same WiFi network +- ✅ Different WiFi networks +- ✅ 4G LTE +- ✅ 5G +- ✅ Mixed (4G + 5G) + +## Advanced Configuration + +### Adjust Video Quality +Edit `camera_server_webrtc.py`: +```python +FRAME_WIDTH = 640 # Lower for less bandwidth +FRAME_HEIGHT = 480 +FPS = 30 # Lower for less bandwidth +``` + +### Change Robot Control Rate +Edit `leader_sender_udp.py`: +```python +SEND_HZ = 50 # Default 50Hz, can go up to 100Hz +``` + +### Use Different STUN Servers +WebRTC uses Google's STUN server by default. To use different ones, edit both camera scripts and add ICE servers configuration to `RTCPeerConnection()`. + +## File Structure + +``` +webrtc_teleoperation/ +├── signaling_server/ # Deploy to cloud (one-time) +│ ├── server.py +│ ├── requirements.txt +│ └── README.md +├── pc1_leader/ # Run on leader PC +│ ├── camera_client_webrtc.py +│ ├── leader_sender_udp.py +│ └── requirements.txt +├── pc2_follower/ # Run on follower PC +│ ├── camera_server_webrtc.py +│ ├── robot_receiver_udp.py +│ └── requirements.txt +└── README.md # This file +``` + +## Replicating to Multiple PCs + +To set up on new PCs: +1. Copy entire `webrtc_teleoperation` folder to new PC +2. Edit IP addresses and COM ports in scripts +3. Install dependencies: `pip install -r requirements.txt` +4. Run scripts as described above + +**Note:** All PCs can share the same signaling server - no need to deploy multiple times. + +## Performance Comparison + +| Method | Latency | Bandwidth Efficiency | Stability on 4G/5G | +|--------|---------|---------------------|-------------------| +| TCP + JPEG | 500-2000ms | Poor | Poor | +| UDP + JPEG | 100-500ms | Poor | Fair | +| **WebRTC** | **50-200ms** | **Excellent** | **Excellent** | + +## Support + +For issues: +1. Check console logs on both PCs +2. Verify signaling server is running +3. Test with local signaling server first +4. Check firewall/antivirus settings + +## License + +Same as LeRobot project. diff --git a/webrtc_teleoperation/pc1_leader/camera_client_webrtc.py b/webrtc_teleoperation/pc1_leader/camera_client_webrtc.py new file mode 100644 index 00000000000..f7e5695bfe9 --- /dev/null +++ b/webrtc_teleoperation/pc1_leader/camera_client_webrtc.py @@ -0,0 +1,193 @@ +""" +PC1 (Leader) - WebRTC Camera Client +Receives camera feed via WebRTC and displays it. +""" +import asyncio +import json +import cv2 +import logging +import websockets +import numpy as np +from aiortc import RTCPeerConnection, RTCSessionDescription +from av import VideoFrame + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +# ========= CONFIG ========= +SIGNALING_SERVER = "ws://localhost:8080/ws" # Change to your deployed server URL +PEER_ID = "leader" +TARGET_PEER = "follower" + +# ========================= + + +class WebRTCCameraClient: + """Manages WebRTC connection and camera receiving.""" + + def __init__(self, signaling_url, peer_id, target_peer): + self.signaling_url = signaling_url + self.peer_id = peer_id + self.target_peer = target_peer + self.pc = None + self.ws = None + self.video_frames = asyncio.Queue(maxsize=1) + + async def connect_signaling(self): + """Connect to signaling server.""" + logger.info(f"Connecting to signaling server: {self.signaling_url}") + self.ws = await websockets.connect(self.signaling_url) + + # Register with server + await self.ws.send(json.dumps({ + "type": "register", + "peer_id": self.peer_id + })) + + response = await self.ws.recv() + data = json.loads(response) + if data.get("type") == "registered": + logger.info(f"Registered as: {self.peer_id}") + + async def handle_offer(self, offer_data): + """Handle incoming offer and send answer.""" + self.pc = RTCPeerConnection() + + # Handle incoming video track + @self.pc.on("track") + async def on_track(track): + logger.info(f"Receiving {track.kind} track") + if track.kind == "video": + asyncio.create_task(self.process_video_track(track)) + + # Set remote description (offer) + offer = RTCSessionDescription( + sdp=offer_data["sdp"], + type=offer_data["type"] + ) + await self.pc.setRemoteDescription(offer) + + # Create and send answer + answer = await self.pc.createAnswer() + await self.pc.setLocalDescription(answer) + + await self.ws.send(json.dumps({ + "type": "answer", + "target": self.target_peer, + "data": { + "sdp": self.pc.localDescription.sdp, + "type": self.pc.localDescription.type + } + })) + logger.info(f"Sent answer to {self.target_peer}") + + async def process_video_track(self, track): + """Process incoming video frames.""" + logger.info("Started receiving video frames") + try: + while True: + frame = await track.recv() + + # Convert av.VideoFrame to numpy array + img = frame.to_ndarray(format="bgr24") + + # Put frame in queue (drop old frame if queue full) + if self.video_frames.full(): + try: + self.video_frames.get_nowait() + except asyncio.QueueEmpty: + pass + + await self.video_frames.put(img) + + except Exception as e: + logger.error(f"Error processing video: {e}") + + async def handle_signaling_messages(self): + """Handle incoming signaling messages.""" + try: + async for message in self.ws: + data = json.loads(message) + msg_type = data.get("type") + + if msg_type == "offer": + # Received offer from follower + logger.info("Received offer from follower") + await self.handle_offer(data.get("data")) + + elif msg_type == "ice-candidate": + # Handle ICE candidates if needed + logger.info("Received ICE candidate") + + elif msg_type == "peers": + peers = data.get("peers", []) + logger.info(f"Active peers: {peers}") + + except websockets.exceptions.ConnectionClosed: + logger.warning("Signaling connection closed") + + async def display_video(self): + """Display received video frames.""" + logger.info("Starting video display...") + while True: + try: + # Get frame from queue with timeout + frame = await asyncio.wait_for(self.video_frames.get(), timeout=1.0) + + # Display frame + cv2.imshow("Follower Camera (WebRTC)", frame) + + # Process OpenCV events + if cv2.waitKey(1) & 0xFF == ord('q'): + logger.info("Quit signal received") + break + + except asyncio.TimeoutError: + # No frame received in 1 second - continue waiting + cv2.waitKey(1) # Keep window responsive + continue + + except Exception as e: + logger.error(f"Display error: {e}") + break + + cv2.destroyAllWindows() + + async def run(self): + """Main run loop.""" + try: + await self.connect_signaling() + + # Start tasks concurrently + await asyncio.gather( + self.handle_signaling_messages(), + self.display_video() + ) + + except Exception as e: + logger.error(f"Error: {e}") + + finally: + if self.pc: + await self.pc.close() + if self.ws: + await self.ws.close() + cv2.destroyAllWindows() + logger.info("Camera client stopped") + + +async def main(): + """Entry point.""" + logger.info("Starting WebRTC Camera Client (Leader)") + logger.info(f"Signaling: {SIGNALING_SERVER}") + logger.info("Press 'q' in video window to quit") + + client = WebRTCCameraClient(SIGNALING_SERVER, PEER_ID, TARGET_PEER) + await client.run() + + +if __name__ == "__main__": + try: + asyncio.run(main()) + except KeyboardInterrupt: + logger.info("Stopped by user") diff --git a/webrtc_teleoperation/pc1_leader/leader_sender_udp.py b/webrtc_teleoperation/pc1_leader/leader_sender_udp.py new file mode 100644 index 00000000000..47f77f7fbf8 --- /dev/null +++ b/webrtc_teleoperation/pc1_leader/leader_sender_udp.py @@ -0,0 +1,61 @@ +""" +PC1 (Leader) - Robot Control Sender (UDP) +Sends SO-101 leader commands via UDP to the follower. +This is kept separate from WebRTC camera streaming. +""" +import socket +import json +import time +from lerobot.teleoperators.so101_leader.so101_leader import SO101Leader +from lerobot.teleoperators.so101_leader.config_so101_leader import SO101LeaderConfig + +# ========= CONFIG ========= +FOLLOWER_IP = "100.127.72.97" # PC2 IP address +UDP_PORT = 5005 +SEND_HZ = 50 + +LEADER_PORT = "COM9" # Change to your leader COM port +LEADER_ID = "leader" + +# ========================== + +# Initialize UDP socket +sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) +dt = 1.0 / SEND_HZ + +# Initialize SO-101 leader +leader_cfg = SO101LeaderConfig( + port=LEADER_PORT, + id=LEADER_ID, +) +leader = SO101Leader(leader_cfg) +leader.connect() + +print(f"Leader SO-101 initialized on {LEADER_PORT}") +print(f"Sending commands to {FOLLOWER_IP}:{UDP_PORT} at {SEND_HZ} Hz") + +try: + while True: + # Get action from leader + raw_action = leader.get_action() + action = {key: float(value) for key, value in raw_action.items()} + + # Send via UDP + msg = { + "ts": time.time(), + "action": action, + } + data = json.dumps(msg).encode("utf-8") + sock.sendto(data, (FOLLOWER_IP, UDP_PORT)) + + time.sleep(dt) + +except KeyboardInterrupt: + print("\nStopping sender...") +finally: + try: + leader.disconnect() + except: + pass + sock.close() + print("Leader sender stopped") diff --git a/webrtc_teleoperation/pc1_leader/requirements.txt b/webrtc_teleoperation/pc1_leader/requirements.txt new file mode 100644 index 00000000000..ecd937b0f9e --- /dev/null +++ b/webrtc_teleoperation/pc1_leader/requirements.txt @@ -0,0 +1,4 @@ +aiortc==1.6.0 +opencv-python==4.8.1.78 +websockets==12.0 +av==11.0.0 diff --git a/webrtc_teleoperation/pc2_follower/camera_server_webrtc.py b/webrtc_teleoperation/pc2_follower/camera_server_webrtc.py new file mode 100644 index 00000000000..40894765448 --- /dev/null +++ b/webrtc_teleoperation/pc2_follower/camera_server_webrtc.py @@ -0,0 +1,193 @@ +""" +PC2 (Follower) - WebRTC Camera Server +Streams camera feed via WebRTC with adaptive bitrate. +""" +import asyncio +import json +import cv2 +import logging +import websockets +from aiortc import RTCPeerConnection, RTCSessionDescription, VideoStreamTrack +from aiortc.contrib.media import MediaPlayer +from av import VideoFrame + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +# ========= CONFIG ========= +SIGNALING_SERVER = "ws://localhost:8080/ws" # Change to your deployed server URL +CAMERA_INDEX = 0 # Change to your camera index +PEER_ID = "follower" +TARGET_PEER = "leader" + +# Video settings +FRAME_WIDTH = 640 +FRAME_HEIGHT = 480 +FPS = 30 + +# ========================= + + +class CameraVideoTrack(VideoStreamTrack): + """Custom video track that captures from OpenCV camera.""" + + def __init__(self, camera_index=0): + super().__init__() + self.camera = cv2.VideoCapture(camera_index) + self.camera.set(cv2.CAP_PROP_FRAME_WIDTH, FRAME_WIDTH) + self.camera.set(cv2.CAP_PROP_FRAME_HEIGHT, FRAME_HEIGHT) + self.camera.set(cv2.CAP_PROP_FPS, FPS) + + if not self.camera.isOpened(): + raise RuntimeError(f"Failed to open camera {camera_index}") + + logger.info(f"Camera opened: {FRAME_WIDTH}x{FRAME_HEIGHT} @ {FPS}fps") + + async def recv(self): + """Capture and return a video frame.""" + pts, time_base = await self.next_timestamp() + + ret, frame = self.camera.read() + if not ret: + logger.error("Failed to read frame from camera") + return None + + # Convert BGR to RGB + frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) + + # Create VideoFrame + video_frame = VideoFrame.from_ndarray(frame_rgb, format="rgb24") + video_frame.pts = pts + video_frame.time_base = time_base + + return video_frame + + def __del__(self): + if hasattr(self, 'camera'): + self.camera.release() + + +class WebRTCCameraServer: + """Manages WebRTC connection and camera streaming.""" + + def __init__(self, signaling_url, peer_id, target_peer): + self.signaling_url = signaling_url + self.peer_id = peer_id + self.target_peer = target_peer + self.pc = None + self.ws = None + self.video_track = None + + async def connect_signaling(self): + """Connect to signaling server.""" + logger.info(f"Connecting to signaling server: {self.signaling_url}") + self.ws = await websockets.connect(self.signaling_url) + + # Register with server + await self.ws.send(json.dumps({ + "type": "register", + "peer_id": self.peer_id + })) + + response = await self.ws.recv() + data = json.loads(response) + if data.get("type") == "registered": + logger.info(f"Registered as: {self.peer_id}") + + async def create_offer(self): + """Create and send WebRTC offer.""" + self.pc = RTCPeerConnection() + + # Add video track + self.video_track = CameraVideoTrack(CAMERA_INDEX) + self.pc.addTrack(self.video_track) + logger.info("Added camera track to peer connection") + + # Create offer + offer = await self.pc.createOffer() + await self.pc.setLocalDescription(offer) + + # Send offer to target peer + await self.ws.send(json.dumps({ + "type": "offer", + "target": self.target_peer, + "data": { + "sdp": self.pc.localDescription.sdp, + "type": self.pc.localDescription.type + } + })) + logger.info(f"Sent offer to {self.target_peer}") + + async def handle_signaling_messages(self): + """Handle incoming signaling messages.""" + try: + async for message in self.ws: + data = json.loads(message) + msg_type = data.get("type") + + if msg_type == "answer": + # Received answer from leader + answer_data = data.get("data") + answer = RTCSessionDescription( + sdp=answer_data["sdp"], + type=answer_data["type"] + ) + await self.pc.setRemoteDescription(answer) + logger.info("Received and set answer - WebRTC connection established!") + + elif msg_type == "ice-candidate": + # Handle ICE candidates if needed + logger.info("Received ICE candidate") + + elif msg_type == "peers": + peers = data.get("peers", []) + logger.info(f"Active peers: {peers}") + if self.target_peer in peers and self.pc is None: + # Target peer is available, create offer + await self.create_offer() + + except websockets.exceptions.ConnectionClosed: + logger.warning("Signaling connection closed") + + async def run(self): + """Main run loop.""" + try: + await self.connect_signaling() + + # Wait a moment for peer list + await asyncio.sleep(2) + + # If target peer already connected, send offer + await self.create_offer() + + # Handle signaling messages + await self.handle_signaling_messages() + + except Exception as e: + logger.error(f"Error: {e}") + + finally: + if self.pc: + await self.pc.close() + if self.ws: + await self.ws.close() + if self.video_track: + del self.video_track + logger.info("Camera server stopped") + + +async def main(): + """Entry point.""" + logger.info("Starting WebRTC Camera Server (Follower)") + logger.info(f"Signaling: {SIGNALING_SERVER}") + logger.info(f"Camera: {CAMERA_INDEX}") + + server = WebRTCCameraServer(SIGNALING_SERVER, PEER_ID, TARGET_PEER) + await server.run() + + +if __name__ == "__main__": + try: + asyncio.run(main()) + except KeyboardInterrupt: + logger.info("Stopped by user") diff --git a/webrtc_teleoperation/pc2_follower/requirements.txt b/webrtc_teleoperation/pc2_follower/requirements.txt new file mode 100644 index 00000000000..ecd937b0f9e --- /dev/null +++ b/webrtc_teleoperation/pc2_follower/requirements.txt @@ -0,0 +1,4 @@ +aiortc==1.6.0 +opencv-python==4.8.1.78 +websockets==12.0 +av==11.0.0 diff --git a/webrtc_teleoperation/pc2_follower/robot_receiver_udp.py b/webrtc_teleoperation/pc2_follower/robot_receiver_udp.py new file mode 100644 index 00000000000..27832424dfc --- /dev/null +++ b/webrtc_teleoperation/pc2_follower/robot_receiver_udp.py @@ -0,0 +1,51 @@ +""" +PC2 (Follower) - Robot Control Receiver (UDP) +Receives robot commands via UDP and controls the SO-101 follower arm. +This is kept separate from WebRTC camera streaming. +""" +import socket +import json +from lerobot.robots.so101.so101 import SO101Robot +from lerobot.robots.so101.config_so101 import SO101RobotConfig + +# ========= CONFIG ========= +UDP_PORT = 5005 +FOLLOWER_PORT = "COM10" # Change to your follower robot COM port +FOLLOWER_ID = "follower" + +# ========================== + +# Initialize UDP socket +sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) +sock.bind(("0.0.0.0", UDP_PORT)) + +# Initialize SO-101 follower robot +follower_cfg = SO101RobotConfig( + port=FOLLOWER_PORT, + id=FOLLOWER_ID, +) +follower = SO101Robot(follower_cfg) +follower.connect() + +print(f"Follower robot initialized on {FOLLOWER_PORT}") +print(f"Listening for commands on UDP port {UDP_PORT}") + +try: + while True: + data, addr = sock.recvfrom(4096) + msg = json.loads(data.decode("utf-8")) + + action = msg.get("action") + if action: + # Send action to follower robot + follower.send_action(action) + +except KeyboardInterrupt: + print("\nStopping receiver...") +finally: + try: + follower.disconnect() + except: + pass + sock.close() + print("Robot receiver stopped") diff --git a/webrtc_teleoperation/signaling_server/README.md b/webrtc_teleoperation/signaling_server/README.md new file mode 100644 index 00000000000..6042bfb28d3 --- /dev/null +++ b/webrtc_teleoperation/signaling_server/README.md @@ -0,0 +1,56 @@ +# WebRTC Signaling Server + +This server facilitates the initial WebRTC handshake between peers. It only handles connection setup - actual video streams go directly between PCs. + +## Deployment Options + +### Option 1: Render.com (Recommended - Free & Easy) + +1. Create account at [render.com](https://render.com) +2. Click "New +" → "Web Service" +3. Connect your GitHub repo OR use "Deploy from Git URL" +4. Configure: + - **Build Command**: `pip install -r requirements.txt` + - **Start Command**: `python server.py` + - **Instance Type**: Free +5. Click "Create Web Service" +6. Copy the URL (e.g., `https://your-app.onrender.com`) +7. Use this URL in your camera scripts (replace `ws://` with `wss://`) + +### Option 2: Railway.app + +1. Create account at [railway.app](https://railway.app) +2. Click "New Project" → "Deploy from GitHub repo" +3. Select this folder +4. Railway auto-detects Python and deploys +5. Copy the provided URL + +### Option 3: Local Testing + +For testing on your local network: + +```bash +pip install -r requirements.txt +python server.py +``` + +Then use `ws://YOUR_LOCAL_IP:8080/ws` in camera scripts. + +## Usage + +Once deployed, you'll get a URL like: +- **Render**: `https://your-signaling-server.onrender.com` +- **Railway**: `https://your-app.railway.app` + +Use this in your camera scripts: +```python +SIGNALING_SERVER = "wss://your-signaling-server.onrender.com/ws" +``` + +## Health Check + +Visit `https://your-server-url/health` to verify the server is running. + +## Logs + +Check your hosting platform's dashboard to view connection logs and debug issues. diff --git a/webrtc_teleoperation/signaling_server/requirements.txt b/webrtc_teleoperation/signaling_server/requirements.txt new file mode 100644 index 00000000000..255b7c8cd0e --- /dev/null +++ b/webrtc_teleoperation/signaling_server/requirements.txt @@ -0,0 +1,2 @@ +aiohttp==3.9.1 +aiohttp-cors==0.7.0 diff --git a/webrtc_teleoperation/signaling_server/server.py b/webrtc_teleoperation/signaling_server/server.py new file mode 100644 index 00000000000..952a2240d1f --- /dev/null +++ b/webrtc_teleoperation/signaling_server/server.py @@ -0,0 +1,120 @@ +""" +WebRTC Signaling Server +Simple WebSocket server to facilitate WebRTC peer connection setup. +Deploy this to Render.com, Railway.app, or any cloud hosting. +""" +import asyncio +import json +import logging +from aiohttp import web +import aiohttp_cors + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +# Store connected peers +peers = {} + + +async def websocket_handler(request): + """Handle WebSocket connections for signaling.""" + ws = web.WebSocketResponse() + await ws.prepare(request) + + peer_id = None + + try: + async for msg in ws: + if msg.type == aiohttp.WSMsgType.TEXT: + try: + data = json.loads(msg.data) + msg_type = data.get("type") + + if msg_type == "register": + # Register peer + peer_id = data.get("peer_id") + peers[peer_id] = ws + logger.info(f"Peer registered: {peer_id}") + await ws.send_json({"type": "registered", "peer_id": peer_id}) + + # Notify all peers about active peers + peer_list = list(peers.keys()) + for pid, peer_ws in peers.items(): + await peer_ws.send_json({ + "type": "peers", + "peers": peer_list + }) + + elif msg_type in ["offer", "answer", "ice-candidate"]: + # Forward signaling messages to target peer + target_id = data.get("target") + if target_id in peers: + await peers[target_id].send_json({ + "type": msg_type, + "from": peer_id, + "data": data.get("data") + }) + logger.info(f"Forwarded {msg_type} from {peer_id} to {target_id}") + else: + logger.warning(f"Target peer {target_id} not found") + + except json.JSONDecodeError: + logger.error("Invalid JSON received") + + elif msg.type == aiohttp.WSMsgType.ERROR: + logger.error(f"WebSocket error: {ws.exception()}") + + finally: + # Clean up on disconnect + if peer_id and peer_id in peers: + del peers[peer_id] + logger.info(f"Peer disconnected: {peer_id}") + + # Notify remaining peers + peer_list = list(peers.keys()) + for pid, peer_ws in peers.items(): + try: + await peer_ws.send_json({ + "type": "peers", + "peers": peer_list + }) + except: + pass + + return ws + + +async def health_check(request): + """Health check endpoint for monitoring.""" + return web.Response(text="OK") + + +def create_app(): + """Create and configure the aiohttp application.""" + app = web.Application() + + # Configure CORS + cors = aiohttp_cors.setup(app, defaults={ + "*": aiohttp_cors.ResourceOptions( + allow_credentials=True, + expose_headers="*", + allow_headers="*" + ) + }) + + # Add routes + app.router.add_get("/ws", websocket_handler) + app.router.add_get("/health", health_check) + + # Configure CORS for all routes + for route in list(app.router.routes()): + cors.add(route) + + return app + + +if __name__ == "__main__": + app = create_app() + port = 8080 # Render/Railway use PORT env variable + logger.info(f"Starting signaling server on port {port}") + web.run_app(app, host="0.0.0.0", port=port) From d50b3d9f0bebd96b8a7a79fe6553980bea4b80b9 Mon Sep 17 00:00:00 2001 From: Raid Date: Wed, 14 Jan 2026 10:04:46 +0800 Subject: [PATCH 08/12] modified: webrtc_teleoperation/pc1_leader/leader_sender_udp.py modified: webrtc_teleoperation/pc2_follower/robot_receiver_udp.py modified: webrtc_teleoperation/signaling_server/server.py --- .../pc1_leader/leader_sender_udp.py | 22 ++++++++++--------- .../pc2_follower/robot_receiver_udp.py | 20 +++++++++-------- .../signaling_server/server.py | 1 + 3 files changed, 24 insertions(+), 19 deletions(-) diff --git a/webrtc_teleoperation/pc1_leader/leader_sender_udp.py b/webrtc_teleoperation/pc1_leader/leader_sender_udp.py index 47f77f7fbf8..e1c1b709b97 100644 --- a/webrtc_teleoperation/pc1_leader/leader_sender_udp.py +++ b/webrtc_teleoperation/pc1_leader/leader_sender_udp.py @@ -1,20 +1,21 @@ """ PC1 (Leader) - Robot Control Sender (UDP) -Sends SO-101 leader commands via UDP to the follower. +Sends bi-SO100 leader commands via UDP to the follower. This is kept separate from WebRTC camera streaming. """ import socket import json import time -from lerobot.teleoperators.so101_leader.so101_leader import SO101Leader -from lerobot.teleoperators.so101_leader.config_so101_leader import SO101LeaderConfig +from lerobot.teleoperators.bi_so100_leader.bi_so100_leader import BiSO100Leader +from lerobot.teleoperators.bi_so100_leader.config_bi_so100_leader import BiSO100LeaderConfig # ========= CONFIG ========= -FOLLOWER_IP = "100.127.72.97" # PC2 IP address +FOLLOWER_IP = "192.168.0.90" # PC2 IP address UDP_PORT = 5005 SEND_HZ = 50 -LEADER_PORT = "COM9" # Change to your leader COM port +LEADER_LEFT_PORT = "COM7" # Change to your left arm leader COM port +LEADER_RIGHT_PORT = "COM8" # Change to your right arm leader COM port LEADER_ID = "leader" # ========================== @@ -23,15 +24,16 @@ sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) dt = 1.0 / SEND_HZ -# Initialize SO-101 leader -leader_cfg = SO101LeaderConfig( - port=LEADER_PORT, +# Initialize bi-SO100 leader (dual arms) +leader_cfg = BiSO100LeaderConfig( + left_arm_port=LEADER_LEFT_PORT, + right_arm_port=LEADER_RIGHT_PORT, id=LEADER_ID, ) -leader = SO101Leader(leader_cfg) +leader = BiSO100Leader(leader_cfg) leader.connect() -print(f"Leader SO-101 initialized on {LEADER_PORT}") +print(f"Leader bi-SO100 initialized on {LEADER_LEFT_PORT} (left) and {LEADER_RIGHT_PORT} (right)") print(f"Sending commands to {FOLLOWER_IP}:{UDP_PORT} at {SEND_HZ} Hz") try: diff --git a/webrtc_teleoperation/pc2_follower/robot_receiver_udp.py b/webrtc_teleoperation/pc2_follower/robot_receiver_udp.py index 27832424dfc..1a365c4f73a 100644 --- a/webrtc_teleoperation/pc2_follower/robot_receiver_udp.py +++ b/webrtc_teleoperation/pc2_follower/robot_receiver_udp.py @@ -1,16 +1,17 @@ """ PC2 (Follower) - Robot Control Receiver (UDP) -Receives robot commands via UDP and controls the SO-101 follower arm. +Receives robot commands via UDP and controls the bi-SO100 follower arm. This is kept separate from WebRTC camera streaming. """ import socket import json -from lerobot.robots.so101.so101 import SO101Robot -from lerobot.robots.so101.config_so101 import SO101RobotConfig +from lerobot.robots.bi_so100.bi_so100 import BiSO100Robot +from lerobot.robots.bi_so100.config_bi_so100 import BiSO100RobotConfig # ========= CONFIG ========= UDP_PORT = 5005 -FOLLOWER_PORT = "COM10" # Change to your follower robot COM port +FOLLOWER_LEFT_PORT = "COM10" # Change to your left arm COM port +FOLLOWER_RIGHT_PORT = "COM11" # Change to your right arm COM port FOLLOWER_ID = "follower" # ========================== @@ -19,15 +20,16 @@ sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.bind(("0.0.0.0", UDP_PORT)) -# Initialize SO-101 follower robot -follower_cfg = SO101RobotConfig( - port=FOLLOWER_PORT, +# Initialize bi-SO100 follower robot (dual arms) +follower_cfg = BiSO100RobotConfig( + left_arm_port=FOLLOWER_LEFT_PORT, + right_arm_port=FOLLOWER_RIGHT_PORT, id=FOLLOWER_ID, ) -follower = SO101Robot(follower_cfg) +follower = BiSO100Robot(follower_cfg) follower.connect() -print(f"Follower robot initialized on {FOLLOWER_PORT}") +print(f"Follower robot initialized on {FOLLOWER_LEFT_PORT} (left) and {FOLLOWER_RIGHT_PORT} (right)") print(f"Listening for commands on UDP port {UDP_PORT}") try: diff --git a/webrtc_teleoperation/signaling_server/server.py b/webrtc_teleoperation/signaling_server/server.py index 952a2240d1f..82be00df706 100644 --- a/webrtc_teleoperation/signaling_server/server.py +++ b/webrtc_teleoperation/signaling_server/server.py @@ -6,6 +6,7 @@ import asyncio import json import logging +import aiohttp from aiohttp import web import aiohttp_cors From 3b79a25a5dfa9095f8959496e986e0a977161660 Mon Sep 17 00:00:00 2001 From: raiddanial Date: Wed, 14 Jan 2026 15:40:52 +0800 Subject: [PATCH 09/12] new file: src/lerobot/configs/robot/amazinghand/mjcf/assets/bushing_0608_04.stl new file: src/lerobot/configs/robot/amazinghand/mjcf/assets/custom_servo_horn.stl new file: src/lerobot/configs/robot/amazinghand/mjcf/assets/distal.stl new file: src/lerobot/configs/robot/amazinghand/mjcf/assets/distal_shell.stl new file: src/lerobot/configs/robot/amazinghand/mjcf/assets/finger_frame_1.stl new file: src/lerobot/configs/robot/amazinghand/mjcf/assets/finger_frame_2.stl new file: src/lerobot/configs/robot/amazinghand/mjcf/assets/gimbal.stl new file: src/lerobot/configs/robot/amazinghand/mjcf/assets/l_hand_plate.stl new file: src/lerobot/configs/robot/amazinghand/mjcf/assets/l_wrist_interface.stl new file: src/lerobot/configs/robot/amazinghand/mjcf/assets/link.stl new file: src/lerobot/configs/robot/amazinghand/mjcf/assets/m2_rod_l18.stl new file: src/lerobot/configs/robot/amazinghand/mjcf/assets/parallel_pin_2_x_10__fee063fca0c8b40e46bbc4ffff61d999.stl new file: src/lerobot/configs/robot/amazinghand/mjcf/assets/parallel_pin_2_x_16__da4b7ddbe9d803fe3fbc70f2e822b99b.stl new file: src/lerobot/configs/robot/amazinghand/mjcf/assets/ph_pan_head_screw_m2x0_40_x_10__2803432263e518bbd16bccbbef8784ed.stl new file: src/lerobot/configs/robot/amazinghand/mjcf/assets/plain_washer_large_grade_a_m2_5__9a369f0dc77bf9c598cdf3fb468977e5.stl new file: src/lerobot/configs/robot/amazinghand/mjcf/assets/proximal.stl new file: src/lerobot/configs/robot/amazinghand/mjcf/assets/proximal_shell.stl new file: src/lerobot/configs/robot/amazinghand/mjcf/assets/rotule_ball.stl new file: src/lerobot/configs/robot/amazinghand/mjcf/assets/rotule_lever.stl new file: src/lerobot/configs/robot/amazinghand/mjcf/assets/scs0009.stl new file: src/lerobot/configs/robot/amazinghand/mjcf/assets/spacer.stl new file: src/lerobot/configs/robot/amazinghand/mjcf/assets/std00333_plast_tcb_torx_2_5x8__configuration_copy_of_default.stl new file: src/lerobot/configs/robot/amazinghand/mjcf/assets/std00447_thermoplastique_m2_5x6__configuration_default.stl new file: src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/bushing_0608_04.stl new file: src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/custom_servo_horn.stl new file: src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/distal.stl new file: src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/distal_shell.stl new file: src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/finger_frame_1.stl new file: src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/finger_frame_2.stl new file: src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/gimbal.stl new file: src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/l_hand_plate.stl new file: src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/l_wrist_interface.stl new file: src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/link.stl new file: src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/m2_rod_l18.stl new file: src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/parallel_pin_2_x_10__fee063fca0c8b40e46bbc4ffff61d999.stl new file: src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/parallel_pin_2_x_16__da4b7ddbe9d803fe3fbc70f2e822b99b.stl new file: src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/ph_pan_head_screw_m2x0_40_x_10__2803432263e518bbd16bccbbef8784ed.stl new file: src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/plain_washer_large_grade_a_m2_5__9a369f0dc77bf9c598cdf3fb468977e5.stl new file: src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/proximal.stl new file: src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/proximal_shell.stl new file: src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/rotule_ball.stl new file: src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/rotule_lever.stl new file: src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/scs0009.stl new file: src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/spacer.stl new file: src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/std00333_plast_tcb_torx_2_5x8__configuration_copy_of_default.stl new file: src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/std00447_thermoplastique_m2_5x6__configuration_default.stl --- .gitignore | 3 --- .../configs/robot/amazinghand/mjcf/assets/bushing_0608_04.stl | 3 +++ .../robot/amazinghand/mjcf/assets/custom_servo_horn.stl | 3 +++ src/lerobot/configs/robot/amazinghand/mjcf/assets/distal.stl | 3 +++ .../configs/robot/amazinghand/mjcf/assets/distal_shell.stl | 3 +++ .../configs/robot/amazinghand/mjcf/assets/finger_frame_1.stl | 3 +++ .../configs/robot/amazinghand/mjcf/assets/finger_frame_2.stl | 3 +++ src/lerobot/configs/robot/amazinghand/mjcf/assets/gimbal.stl | 3 +++ .../configs/robot/amazinghand/mjcf/assets/l_hand_plate.stl | 3 +++ .../robot/amazinghand/mjcf/assets/l_wrist_interface.stl | 3 +++ src/lerobot/configs/robot/amazinghand/mjcf/assets/link.stl | 3 +++ .../configs/robot/amazinghand/mjcf/assets/m2_rod_l18.stl | 3 +++ .../parallel_pin_2_x_10__fee063fca0c8b40e46bbc4ffff61d999.stl | 3 +++ .../parallel_pin_2_x_16__da4b7ddbe9d803fe3fbc70f2e822b99b.stl | 3 +++ ...ad_screw_m2x0_40_x_10__2803432263e518bbd16bccbbef8784ed.stl | 3 +++ ...er_large_grade_a_m2_5__9a369f0dc77bf9c598cdf3fb468977e5.stl | 3 +++ src/lerobot/configs/robot/amazinghand/mjcf/assets/proximal.stl | 3 +++ .../configs/robot/amazinghand/mjcf/assets/proximal_shell.stl | 3 +++ .../configs/robot/amazinghand/mjcf/assets/rotule_ball.stl | 3 +++ .../configs/robot/amazinghand/mjcf/assets/rotule_lever.stl | 3 +++ src/lerobot/configs/robot/amazinghand/mjcf/assets/scs0009.stl | 3 +++ src/lerobot/configs/robot/amazinghand/mjcf/assets/spacer.stl | 3 +++ ...333_plast_tcb_torx_2_5x8__configuration_copy_of_default.stl | 3 +++ .../std00447_thermoplastique_m2_5x6__configuration_default.stl | 3 +++ .../mjcf/assets/bushing_0608_04.stl | 3 +++ .../mjcf/assets/custom_servo_horn.stl | 3 +++ .../mjcf/assets/distal.stl | 3 +++ .../mjcf/assets/distal_shell.stl | 3 +++ .../mjcf/assets/finger_frame_1.stl | 3 +++ .../mjcf/assets/finger_frame_2.stl | 3 +++ .../mjcf/assets/gimbal.stl | 3 +++ .../mjcf/assets/l_hand_plate.stl | 3 +++ .../mjcf/assets/l_wrist_interface.stl | 3 +++ .../mjcf/assets/link.stl | 3 +++ .../mjcf/assets/m2_rod_l18.stl | 3 +++ .../parallel_pin_2_x_10__fee063fca0c8b40e46bbc4ffff61d999.stl | 3 +++ .../parallel_pin_2_x_16__da4b7ddbe9d803fe3fbc70f2e822b99b.stl | 3 +++ ...ad_screw_m2x0_40_x_10__2803432263e518bbd16bccbbef8784ed.stl | 3 +++ ...er_large_grade_a_m2_5__9a369f0dc77bf9c598cdf3fb468977e5.stl | 3 +++ .../mjcf/assets/proximal.stl | 3 +++ .../mjcf/assets/proximal_shell.stl | 3 +++ .../mjcf/assets/rotule_ball.stl | 3 +++ .../mjcf/assets/rotule_lever.stl | 3 +++ .../mjcf/assets/scs0009.stl | 3 +++ .../mjcf/assets/spacer.stl | 3 +++ ...333_plast_tcb_torx_2_5x8__configuration_copy_of_default.stl | 3 +++ .../std00447_thermoplastique_m2_5x6__configuration_default.stl | 3 +++ 47 files changed, 138 insertions(+), 3 deletions(-) create mode 100644 src/lerobot/configs/robot/amazinghand/mjcf/assets/bushing_0608_04.stl create mode 100644 src/lerobot/configs/robot/amazinghand/mjcf/assets/custom_servo_horn.stl create mode 100644 src/lerobot/configs/robot/amazinghand/mjcf/assets/distal.stl create mode 100644 src/lerobot/configs/robot/amazinghand/mjcf/assets/distal_shell.stl create mode 100644 src/lerobot/configs/robot/amazinghand/mjcf/assets/finger_frame_1.stl create mode 100644 src/lerobot/configs/robot/amazinghand/mjcf/assets/finger_frame_2.stl create mode 100644 src/lerobot/configs/robot/amazinghand/mjcf/assets/gimbal.stl create mode 100644 src/lerobot/configs/robot/amazinghand/mjcf/assets/l_hand_plate.stl create mode 100644 src/lerobot/configs/robot/amazinghand/mjcf/assets/l_wrist_interface.stl create mode 100644 src/lerobot/configs/robot/amazinghand/mjcf/assets/link.stl create mode 100644 src/lerobot/configs/robot/amazinghand/mjcf/assets/m2_rod_l18.stl create mode 100644 src/lerobot/configs/robot/amazinghand/mjcf/assets/parallel_pin_2_x_10__fee063fca0c8b40e46bbc4ffff61d999.stl create mode 100644 src/lerobot/configs/robot/amazinghand/mjcf/assets/parallel_pin_2_x_16__da4b7ddbe9d803fe3fbc70f2e822b99b.stl create mode 100644 src/lerobot/configs/robot/amazinghand/mjcf/assets/ph_pan_head_screw_m2x0_40_x_10__2803432263e518bbd16bccbbef8784ed.stl create mode 100644 src/lerobot/configs/robot/amazinghand/mjcf/assets/plain_washer_large_grade_a_m2_5__9a369f0dc77bf9c598cdf3fb468977e5.stl create mode 100644 src/lerobot/configs/robot/amazinghand/mjcf/assets/proximal.stl create mode 100644 src/lerobot/configs/robot/amazinghand/mjcf/assets/proximal_shell.stl create mode 100644 src/lerobot/configs/robot/amazinghand/mjcf/assets/rotule_ball.stl create mode 100644 src/lerobot/configs/robot/amazinghand/mjcf/assets/rotule_lever.stl create mode 100644 src/lerobot/configs/robot/amazinghand/mjcf/assets/scs0009.stl create mode 100644 src/lerobot/configs/robot/amazinghand/mjcf/assets/spacer.stl create mode 100644 src/lerobot/configs/robot/amazinghand/mjcf/assets/std00333_plast_tcb_torx_2_5x8__configuration_copy_of_default.stl create mode 100644 src/lerobot/configs/robot/amazinghand/mjcf/assets/std00447_thermoplastique_m2_5x6__configuration_default.stl create mode 100644 src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/bushing_0608_04.stl create mode 100644 src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/custom_servo_horn.stl create mode 100644 src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/distal.stl create mode 100644 src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/distal_shell.stl create mode 100644 src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/finger_frame_1.stl create mode 100644 src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/finger_frame_2.stl create mode 100644 src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/gimbal.stl create mode 100644 src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/l_hand_plate.stl create mode 100644 src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/l_wrist_interface.stl create mode 100644 src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/link.stl create mode 100644 src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/m2_rod_l18.stl create mode 100644 src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/parallel_pin_2_x_10__fee063fca0c8b40e46bbc4ffff61d999.stl create mode 100644 src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/parallel_pin_2_x_16__da4b7ddbe9d803fe3fbc70f2e822b99b.stl create mode 100644 src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/ph_pan_head_screw_m2x0_40_x_10__2803432263e518bbd16bccbbef8784ed.stl create mode 100644 src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/plain_washer_large_grade_a_m2_5__9a369f0dc77bf9c598cdf3fb468977e5.stl create mode 100644 src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/proximal.stl create mode 100644 src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/proximal_shell.stl create mode 100644 src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/rotule_ball.stl create mode 100644 src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/rotule_lever.stl create mode 100644 src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/scs0009.stl create mode 100644 src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/spacer.stl create mode 100644 src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/std00333_plast_tcb_torx_2_5x8__configuration_copy_of_default.stl create mode 100644 src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/std00447_thermoplastique_m2_5x6__configuration_default.stl diff --git a/.gitignore b/.gitignore index b47e22cbfad..a3cfa77fd40 100644 --- a/.gitignore +++ b/.gitignore @@ -173,7 +173,4 @@ outputs/ # Dev folders .cache/* -*.stl -*.urdf -*.xml *.part diff --git a/src/lerobot/configs/robot/amazinghand/mjcf/assets/bushing_0608_04.stl b/src/lerobot/configs/robot/amazinghand/mjcf/assets/bushing_0608_04.stl new file mode 100644 index 00000000000..505b3337ed2 --- /dev/null +++ b/src/lerobot/configs/robot/amazinghand/mjcf/assets/bushing_0608_04.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a4654c50399a5a2e2e929aaa9cc81b9243b4542a85bf3896838c65fb935709ca +size 43284 diff --git a/src/lerobot/configs/robot/amazinghand/mjcf/assets/custom_servo_horn.stl b/src/lerobot/configs/robot/amazinghand/mjcf/assets/custom_servo_horn.stl new file mode 100644 index 00000000000..da6a908ac11 --- /dev/null +++ b/src/lerobot/configs/robot/amazinghand/mjcf/assets/custom_servo_horn.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ac1bf3bf06f64f2b8b85e48c1605a92c0fbd5643f0a01345aad81db8bb850dfa +size 86084 diff --git a/src/lerobot/configs/robot/amazinghand/mjcf/assets/distal.stl b/src/lerobot/configs/robot/amazinghand/mjcf/assets/distal.stl new file mode 100644 index 00000000000..7748343a3d4 --- /dev/null +++ b/src/lerobot/configs/robot/amazinghand/mjcf/assets/distal.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e3aeab19ecad616e5b859d5326c2966d8dd5db196f9217c340ed5b762e85567b +size 177884 diff --git a/src/lerobot/configs/robot/amazinghand/mjcf/assets/distal_shell.stl b/src/lerobot/configs/robot/amazinghand/mjcf/assets/distal_shell.stl new file mode 100644 index 00000000000..02cd355a1e0 --- /dev/null +++ b/src/lerobot/configs/robot/amazinghand/mjcf/assets/distal_shell.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0a1e136158fb5464704b68d37d30eae450c1a3b507bf694f429d914e4419557b +size 1195884 diff --git a/src/lerobot/configs/robot/amazinghand/mjcf/assets/finger_frame_1.stl b/src/lerobot/configs/robot/amazinghand/mjcf/assets/finger_frame_1.stl new file mode 100644 index 00000000000..8d72fe07346 --- /dev/null +++ b/src/lerobot/configs/robot/amazinghand/mjcf/assets/finger_frame_1.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:50c439686722f30d165c1604a1ffdb3289c6b4af3d68ff79ca0daabd25251f49 +size 157284 diff --git a/src/lerobot/configs/robot/amazinghand/mjcf/assets/finger_frame_2.stl b/src/lerobot/configs/robot/amazinghand/mjcf/assets/finger_frame_2.stl new file mode 100644 index 00000000000..2897a272e23 --- /dev/null +++ b/src/lerobot/configs/robot/amazinghand/mjcf/assets/finger_frame_2.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0e618d270bfcb5055bfdf6bb41c52c2098ef78e25d43e7972df346e5085cf91f +size 94884 diff --git a/src/lerobot/configs/robot/amazinghand/mjcf/assets/gimbal.stl b/src/lerobot/configs/robot/amazinghand/mjcf/assets/gimbal.stl new file mode 100644 index 00000000000..9091c42ef63 --- /dev/null +++ b/src/lerobot/configs/robot/amazinghand/mjcf/assets/gimbal.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:54cdf9367ab1b30419c750b37c18c38d03425056beba1a56192287414496c50e +size 120784 diff --git a/src/lerobot/configs/robot/amazinghand/mjcf/assets/l_hand_plate.stl b/src/lerobot/configs/robot/amazinghand/mjcf/assets/l_hand_plate.stl new file mode 100644 index 00000000000..3fe4c6d76a5 --- /dev/null +++ b/src/lerobot/configs/robot/amazinghand/mjcf/assets/l_hand_plate.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:90ab794d14a277a7d2db17a4c1956a4b025967bf4ba0d934aa271f119179bc1c +size 718884 diff --git a/src/lerobot/configs/robot/amazinghand/mjcf/assets/l_wrist_interface.stl b/src/lerobot/configs/robot/amazinghand/mjcf/assets/l_wrist_interface.stl new file mode 100644 index 00000000000..9d10cc67917 --- /dev/null +++ b/src/lerobot/configs/robot/amazinghand/mjcf/assets/l_wrist_interface.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d130a3c6be00cba6186ccfd7d65bf09c558c9e632996729ff06989e117bef1e7 +size 825584 diff --git a/src/lerobot/configs/robot/amazinghand/mjcf/assets/link.stl b/src/lerobot/configs/robot/amazinghand/mjcf/assets/link.stl new file mode 100644 index 00000000000..a8f1ab9ba19 --- /dev/null +++ b/src/lerobot/configs/robot/amazinghand/mjcf/assets/link.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:77acb1b7675a799fed66ca06224d484dc371c76c3308e626fd339ecc6086917b +size 97884 diff --git a/src/lerobot/configs/robot/amazinghand/mjcf/assets/m2_rod_l18.stl b/src/lerobot/configs/robot/amazinghand/mjcf/assets/m2_rod_l18.stl new file mode 100644 index 00000000000..df7684dc646 --- /dev/null +++ b/src/lerobot/configs/robot/amazinghand/mjcf/assets/m2_rod_l18.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f78dcdda6669f36df4de6241f7869362e80fc4e7bd9fa0cf68eb22564d5d131e +size 14484 diff --git a/src/lerobot/configs/robot/amazinghand/mjcf/assets/parallel_pin_2_x_10__fee063fca0c8b40e46bbc4ffff61d999.stl b/src/lerobot/configs/robot/amazinghand/mjcf/assets/parallel_pin_2_x_10__fee063fca0c8b40e46bbc4ffff61d999.stl new file mode 100644 index 00000000000..d1ce9e06299 --- /dev/null +++ b/src/lerobot/configs/robot/amazinghand/mjcf/assets/parallel_pin_2_x_10__fee063fca0c8b40e46bbc4ffff61d999.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:01c8bb5014a46b355171bf4672a4b81602caab7d43751eefa7cef2362c12fb0f +size 28884 diff --git a/src/lerobot/configs/robot/amazinghand/mjcf/assets/parallel_pin_2_x_16__da4b7ddbe9d803fe3fbc70f2e822b99b.stl b/src/lerobot/configs/robot/amazinghand/mjcf/assets/parallel_pin_2_x_16__da4b7ddbe9d803fe3fbc70f2e822b99b.stl new file mode 100644 index 00000000000..4d505b6ecb8 --- /dev/null +++ b/src/lerobot/configs/robot/amazinghand/mjcf/assets/parallel_pin_2_x_16__da4b7ddbe9d803fe3fbc70f2e822b99b.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:34bf98bac12bf7d6ec7d0024f691e2aa866a7e309344a9fd4f3b8fcc49d73f7e +size 28884 diff --git a/src/lerobot/configs/robot/amazinghand/mjcf/assets/ph_pan_head_screw_m2x0_40_x_10__2803432263e518bbd16bccbbef8784ed.stl b/src/lerobot/configs/robot/amazinghand/mjcf/assets/ph_pan_head_screw_m2x0_40_x_10__2803432263e518bbd16bccbbef8784ed.stl new file mode 100644 index 00000000000..e9f0b407b42 --- /dev/null +++ b/src/lerobot/configs/robot/amazinghand/mjcf/assets/ph_pan_head_screw_m2x0_40_x_10__2803432263e518bbd16bccbbef8784ed.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3026c6ffc1b7012cce54aa32a0b33aab7719539ea4df675f84e34223340c05c1 +size 218484 diff --git a/src/lerobot/configs/robot/amazinghand/mjcf/assets/plain_washer_large_grade_a_m2_5__9a369f0dc77bf9c598cdf3fb468977e5.stl b/src/lerobot/configs/robot/amazinghand/mjcf/assets/plain_washer_large_grade_a_m2_5__9a369f0dc77bf9c598cdf3fb468977e5.stl new file mode 100644 index 00000000000..156aadb198c --- /dev/null +++ b/src/lerobot/configs/robot/amazinghand/mjcf/assets/plain_washer_large_grade_a_m2_5__9a369f0dc77bf9c598cdf3fb468977e5.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d48a6eb3cb410a29a68ad034b9e51f8fd45e8f4f0da6323a4840e6e19020e0c7 +size 28884 diff --git a/src/lerobot/configs/robot/amazinghand/mjcf/assets/proximal.stl b/src/lerobot/configs/robot/amazinghand/mjcf/assets/proximal.stl new file mode 100644 index 00000000000..323fb83c33e --- /dev/null +++ b/src/lerobot/configs/robot/amazinghand/mjcf/assets/proximal.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:167f17b5994a179f13b0ca5887740f5f9580604ea7f767d72ff9ad3516f16485 +size 270484 diff --git a/src/lerobot/configs/robot/amazinghand/mjcf/assets/proximal_shell.stl b/src/lerobot/configs/robot/amazinghand/mjcf/assets/proximal_shell.stl new file mode 100644 index 00000000000..4c79a74ab9e --- /dev/null +++ b/src/lerobot/configs/robot/amazinghand/mjcf/assets/proximal_shell.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:da46e0edefe0f245db0fc6b2230f97dd47a104da7c6c9cf10a923e8d27c742fa +size 357784 diff --git a/src/lerobot/configs/robot/amazinghand/mjcf/assets/rotule_ball.stl b/src/lerobot/configs/robot/amazinghand/mjcf/assets/rotule_ball.stl new file mode 100644 index 00000000000..bdc946d82f8 --- /dev/null +++ b/src/lerobot/configs/robot/amazinghand/mjcf/assets/rotule_ball.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c78afd69e2d4c0fada853805771e25fc0547dd85b1764e3e58d8bf5b1188c64e +size 201684 diff --git a/src/lerobot/configs/robot/amazinghand/mjcf/assets/rotule_lever.stl b/src/lerobot/configs/robot/amazinghand/mjcf/assets/rotule_lever.stl new file mode 100644 index 00000000000..194287402f7 --- /dev/null +++ b/src/lerobot/configs/robot/amazinghand/mjcf/assets/rotule_lever.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e50867e9e235a312d5b3aa279aae7cc8a29c705ef0dbfb6d89b0bb30364c5f84 +size 215084 diff --git a/src/lerobot/configs/robot/amazinghand/mjcf/assets/scs0009.stl b/src/lerobot/configs/robot/amazinghand/mjcf/assets/scs0009.stl new file mode 100644 index 00000000000..fbe8c10d9d3 --- /dev/null +++ b/src/lerobot/configs/robot/amazinghand/mjcf/assets/scs0009.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8a663fa3796995ac1b2c7186f8d67b1ae7af8e6c7a415d16b7ee832233449ac2 +size 394384 diff --git a/src/lerobot/configs/robot/amazinghand/mjcf/assets/spacer.stl b/src/lerobot/configs/robot/amazinghand/mjcf/assets/spacer.stl new file mode 100644 index 00000000000..36ffb4db14b --- /dev/null +++ b/src/lerobot/configs/robot/amazinghand/mjcf/assets/spacer.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:216a38b439575331c784abe34b81b6ecca092a816588dd06ed24c9bd45cd2cc8 +size 28884 diff --git a/src/lerobot/configs/robot/amazinghand/mjcf/assets/std00333_plast_tcb_torx_2_5x8__configuration_copy_of_default.stl b/src/lerobot/configs/robot/amazinghand/mjcf/assets/std00333_plast_tcb_torx_2_5x8__configuration_copy_of_default.stl new file mode 100644 index 00000000000..f0c0a909fe4 --- /dev/null +++ b/src/lerobot/configs/robot/amazinghand/mjcf/assets/std00333_plast_tcb_torx_2_5x8__configuration_copy_of_default.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5b16f1bdd7f88c443697e30353512139e38c2d98a333c9653f2b9cb56fb84d4a +size 177584 diff --git a/src/lerobot/configs/robot/amazinghand/mjcf/assets/std00447_thermoplastique_m2_5x6__configuration_default.stl b/src/lerobot/configs/robot/amazinghand/mjcf/assets/std00447_thermoplastique_m2_5x6__configuration_default.stl new file mode 100644 index 00000000000..e31af7c8085 --- /dev/null +++ b/src/lerobot/configs/robot/amazinghand/mjcf/assets/std00447_thermoplastique_m2_5x6__configuration_default.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:62340db6254431a4db1fcc862c6d91dd18119a28c9af4b10d6fd4eea112e05b3 +size 177584 diff --git a/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/bushing_0608_04.stl b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/bushing_0608_04.stl new file mode 100644 index 00000000000..505b3337ed2 --- /dev/null +++ b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/bushing_0608_04.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a4654c50399a5a2e2e929aaa9cc81b9243b4542a85bf3896838c65fb935709ca +size 43284 diff --git a/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/custom_servo_horn.stl b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/custom_servo_horn.stl new file mode 100644 index 00000000000..da6a908ac11 --- /dev/null +++ b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/custom_servo_horn.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ac1bf3bf06f64f2b8b85e48c1605a92c0fbd5643f0a01345aad81db8bb850dfa +size 86084 diff --git a/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/distal.stl b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/distal.stl new file mode 100644 index 00000000000..7748343a3d4 --- /dev/null +++ b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/distal.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e3aeab19ecad616e5b859d5326c2966d8dd5db196f9217c340ed5b762e85567b +size 177884 diff --git a/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/distal_shell.stl b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/distal_shell.stl new file mode 100644 index 00000000000..02cd355a1e0 --- /dev/null +++ b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/distal_shell.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0a1e136158fb5464704b68d37d30eae450c1a3b507bf694f429d914e4419557b +size 1195884 diff --git a/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/finger_frame_1.stl b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/finger_frame_1.stl new file mode 100644 index 00000000000..8d72fe07346 --- /dev/null +++ b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/finger_frame_1.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:50c439686722f30d165c1604a1ffdb3289c6b4af3d68ff79ca0daabd25251f49 +size 157284 diff --git a/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/finger_frame_2.stl b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/finger_frame_2.stl new file mode 100644 index 00000000000..2897a272e23 --- /dev/null +++ b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/finger_frame_2.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0e618d270bfcb5055bfdf6bb41c52c2098ef78e25d43e7972df346e5085cf91f +size 94884 diff --git a/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/gimbal.stl b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/gimbal.stl new file mode 100644 index 00000000000..9091c42ef63 --- /dev/null +++ b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/gimbal.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:54cdf9367ab1b30419c750b37c18c38d03425056beba1a56192287414496c50e +size 120784 diff --git a/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/l_hand_plate.stl b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/l_hand_plate.stl new file mode 100644 index 00000000000..3fe4c6d76a5 --- /dev/null +++ b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/l_hand_plate.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:90ab794d14a277a7d2db17a4c1956a4b025967bf4ba0d934aa271f119179bc1c +size 718884 diff --git a/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/l_wrist_interface.stl b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/l_wrist_interface.stl new file mode 100644 index 00000000000..9d10cc67917 --- /dev/null +++ b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/l_wrist_interface.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d130a3c6be00cba6186ccfd7d65bf09c558c9e632996729ff06989e117bef1e7 +size 825584 diff --git a/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/link.stl b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/link.stl new file mode 100644 index 00000000000..a8f1ab9ba19 --- /dev/null +++ b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/link.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:77acb1b7675a799fed66ca06224d484dc371c76c3308e626fd339ecc6086917b +size 97884 diff --git a/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/m2_rod_l18.stl b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/m2_rod_l18.stl new file mode 100644 index 00000000000..df7684dc646 --- /dev/null +++ b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/m2_rod_l18.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f78dcdda6669f36df4de6241f7869362e80fc4e7bd9fa0cf68eb22564d5d131e +size 14484 diff --git a/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/parallel_pin_2_x_10__fee063fca0c8b40e46bbc4ffff61d999.stl b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/parallel_pin_2_x_10__fee063fca0c8b40e46bbc4ffff61d999.stl new file mode 100644 index 00000000000..d1ce9e06299 --- /dev/null +++ b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/parallel_pin_2_x_10__fee063fca0c8b40e46bbc4ffff61d999.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:01c8bb5014a46b355171bf4672a4b81602caab7d43751eefa7cef2362c12fb0f +size 28884 diff --git a/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/parallel_pin_2_x_16__da4b7ddbe9d803fe3fbc70f2e822b99b.stl b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/parallel_pin_2_x_16__da4b7ddbe9d803fe3fbc70f2e822b99b.stl new file mode 100644 index 00000000000..4d505b6ecb8 --- /dev/null +++ b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/parallel_pin_2_x_16__da4b7ddbe9d803fe3fbc70f2e822b99b.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:34bf98bac12bf7d6ec7d0024f691e2aa866a7e309344a9fd4f3b8fcc49d73f7e +size 28884 diff --git a/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/ph_pan_head_screw_m2x0_40_x_10__2803432263e518bbd16bccbbef8784ed.stl b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/ph_pan_head_screw_m2x0_40_x_10__2803432263e518bbd16bccbbef8784ed.stl new file mode 100644 index 00000000000..e9f0b407b42 --- /dev/null +++ b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/ph_pan_head_screw_m2x0_40_x_10__2803432263e518bbd16bccbbef8784ed.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3026c6ffc1b7012cce54aa32a0b33aab7719539ea4df675f84e34223340c05c1 +size 218484 diff --git a/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/plain_washer_large_grade_a_m2_5__9a369f0dc77bf9c598cdf3fb468977e5.stl b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/plain_washer_large_grade_a_m2_5__9a369f0dc77bf9c598cdf3fb468977e5.stl new file mode 100644 index 00000000000..156aadb198c --- /dev/null +++ b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/plain_washer_large_grade_a_m2_5__9a369f0dc77bf9c598cdf3fb468977e5.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d48a6eb3cb410a29a68ad034b9e51f8fd45e8f4f0da6323a4840e6e19020e0c7 +size 28884 diff --git a/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/proximal.stl b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/proximal.stl new file mode 100644 index 00000000000..323fb83c33e --- /dev/null +++ b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/proximal.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:167f17b5994a179f13b0ca5887740f5f9580604ea7f767d72ff9ad3516f16485 +size 270484 diff --git a/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/proximal_shell.stl b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/proximal_shell.stl new file mode 100644 index 00000000000..4c79a74ab9e --- /dev/null +++ b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/proximal_shell.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:da46e0edefe0f245db0fc6b2230f97dd47a104da7c6c9cf10a923e8d27c742fa +size 357784 diff --git a/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/rotule_ball.stl b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/rotule_ball.stl new file mode 100644 index 00000000000..bdc946d82f8 --- /dev/null +++ b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/rotule_ball.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c78afd69e2d4c0fada853805771e25fc0547dd85b1764e3e58d8bf5b1188c64e +size 201684 diff --git a/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/rotule_lever.stl b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/rotule_lever.stl new file mode 100644 index 00000000000..194287402f7 --- /dev/null +++ b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/rotule_lever.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e50867e9e235a312d5b3aa279aae7cc8a29c705ef0dbfb6d89b0bb30364c5f84 +size 215084 diff --git a/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/scs0009.stl b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/scs0009.stl new file mode 100644 index 00000000000..fbe8c10d9d3 --- /dev/null +++ b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/scs0009.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8a663fa3796995ac1b2c7186f8d67b1ae7af8e6c7a415d16b7ee832233449ac2 +size 394384 diff --git a/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/spacer.stl b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/spacer.stl new file mode 100644 index 00000000000..36ffb4db14b --- /dev/null +++ b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/spacer.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:216a38b439575331c784abe34b81b6ecca092a816588dd06ed24c9bd45cd2cc8 +size 28884 diff --git a/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/std00333_plast_tcb_torx_2_5x8__configuration_copy_of_default.stl b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/std00333_plast_tcb_torx_2_5x8__configuration_copy_of_default.stl new file mode 100644 index 00000000000..f0c0a909fe4 --- /dev/null +++ b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/std00333_plast_tcb_torx_2_5x8__configuration_copy_of_default.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5b16f1bdd7f88c443697e30353512139e38c2d98a333c9653f2b9cb56fb84d4a +size 177584 diff --git a/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/std00447_thermoplastique_m2_5x6__configuration_default.stl b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/std00447_thermoplastique_m2_5x6__configuration_default.stl new file mode 100644 index 00000000000..e31af7c8085 --- /dev/null +++ b/src/lerobot/teleoperators/lerobot_teleoperator_amazinghandtracker/lerobot_teleoperator_amazinghandtracker/mjcf/assets/std00447_thermoplastique_m2_5x6__configuration_default.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:62340db6254431a4db1fcc862c6d91dd18119a28c9af4b10d6fd4eea112e05b3 +size 177584 From 6d2b9bee1b0af0429cc11d5e56a247c0fcfa9383 Mon Sep 17 00:00:00 2001 From: raiddanial Date: Thu, 15 Jan 2026 13:46:54 +0800 Subject: [PATCH 10/12] modified: webrtc_teleoperation/pc1_leader/camera_client_webrtc.py modified: webrtc_teleoperation/pc1_leader/leader_sender_udp.py modified: webrtc_teleoperation/pc2_follower/robot_receiver_udp.py --- .../pc1_leader/camera_client_webrtc.py | 93 ++++++++++++++++++ .../pc1_leader/leader_sender_udp.py | 98 ++++++++++++++++++- .../pc2_follower/robot_receiver_udp.py | 11 +++ 3 files changed, 199 insertions(+), 3 deletions(-) diff --git a/webrtc_teleoperation/pc1_leader/camera_client_webrtc.py b/webrtc_teleoperation/pc1_leader/camera_client_webrtc.py index f7e5695bfe9..a9ab5815d51 100644 --- a/webrtc_teleoperation/pc1_leader/camera_client_webrtc.py +++ b/webrtc_teleoperation/pc1_leader/camera_client_webrtc.py @@ -1,6 +1,12 @@ """ PC1 (Leader) - WebRTC Camera Client Receives camera feed via WebRTC and displays it. + +Features: +- Video latency measurement +- Frame rate tracking +- Bandwidth monitoring +- CSV logging with timestamp """ import asyncio import json @@ -8,6 +14,11 @@ import logging import websockets import numpy as np +import csv +import time +from datetime import datetime +from collections import deque +import statistics from aiortc import RTCPeerConnection, RTCSessionDescription from av import VideoFrame @@ -19,6 +30,10 @@ PEER_ID = "leader" TARGET_PEER = "follower" +# Measurement settings +LOG_INTERVAL = 1.0 # Log metrics every 1 second +LATENCY_WINDOW = 100 # Keep last 100 latency measurements + # ========================= @@ -32,6 +47,24 @@ def __init__(self, signaling_url, peer_id, target_peer): self.pc = None self.ws = None self.video_frames = asyncio.Queue(maxsize=1) + + # Measurement tracking + self.frame_count = 0 + self.frames_received = 0 + self.frames_dropped = 0 + self.bytes_received = 0 + self.latency_measurements = deque(maxlen=LATENCY_WINDOW) + self.last_log_time = time.time() + + # Create CSV log file + log_filename = f"teleoperation_video_log_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv" + self.csv_file = open(log_filename, 'w', newline='') + self.csv_writer = csv.writer(self.csv_file) + self.csv_writer.writerow([ + 'Timestamp', 'Video_Latency_ms', 'Jitter_ms', 'FPS', + 'Frames_Dropped_%', 'Bandwidth_kbps' + ]) + logger.info(f"Logging to: {log_filename}") async def connect_signaling(self): """Connect to signaling server.""" @@ -86,15 +119,26 @@ async def process_video_track(self, track): logger.info("Started receiving video frames") try: while True: + recv_time = time.time() frame = await track.recv() + # Calculate latency (frame.time is in seconds since epoch) + if hasattr(frame, 'time') and frame.time: + latency = (recv_time - frame.time) * 1000 # Convert to ms + self.latency_measurements.append(latency) + # Convert av.VideoFrame to numpy array img = frame.to_ndarray(format="bgr24") + # Track bytes (approximate) + self.bytes_received += img.nbytes + self.frames_received += 1 + # Put frame in queue (drop old frame if queue full) if self.video_frames.full(): try: self.video_frames.get_nowait() + self.frames_dropped += 1 except asyncio.QueueEmpty: pass @@ -129,10 +173,56 @@ async def handle_signaling_messages(self): async def display_video(self): """Display received video frames.""" logger.info("Starting video display...") + frames_displayed = 0 + display_start = time.time() + while True: try: # Get frame from queue with timeout frame = await asyncio.wait_for(self.video_frames.get(), timeout=1.0) + frames_displayed += 1 + self.frame_count += 1 + + # Calculate and log metrics periodically + current_time = time.time() + if current_time - self.last_log_time >= LOG_INTERVAL: + elapsed = current_time - self.last_log_time + + # Calculate metrics + avg_latency = statistics.mean(self.latency_measurements) if self.latency_measurements else 0 + jitter = statistics.stdev(self.latency_measurements) if len(self.latency_measurements) > 1 else 0 + fps = self.frame_count / elapsed + drop_rate = (self.frames_dropped / self.frames_received * 100) if self.frames_received > 0 else 0 + bandwidth = (self.bytes_received * 8) / elapsed / 1000 # kbps + + # Write to CSV + self.csv_writer.writerow([ + datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3], + f"{avg_latency:.2f}", + f"{jitter:.2f}", + f"{fps:.1f}", + f"{drop_rate:.2f}", + f"{bandwidth:.2f}" + ]) + self.csv_file.flush() + + # Print to console + logger.info(f"Video - Latency: {avg_latency:.1f}ms | Jitter: {jitter:.1f}ms | FPS: {fps:.1f} | Drop: {drop_rate:.1f}% | BW: {bandwidth:.1f}kbps") + + # Reset counters + self.last_log_time = current_time + self.frame_count = 0 + self.bytes_received = 0 + + # Add metrics overlay on frame + if self.latency_measurements: + avg_latency = statistics.mean(self.latency_measurements) + fps = frames_displayed / (current_time - display_start) if (current_time - display_start) > 0 else 0 + + cv2.putText(frame, f"Latency: {avg_latency:.1f}ms", (10, 30), + cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) + cv2.putText(frame, f"FPS: {fps:.1f}", (10, 60), + cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) # Display frame cv2.imshow("Follower Camera (WebRTC)", frame) @@ -172,6 +262,9 @@ async def run(self): await self.pc.close() if self.ws: await self.ws.close() + if hasattr(self, 'csv_file'): + self.csv_file.close() + logger.info(f"Video log saved") cv2.destroyAllWindows() logger.info("Camera client stopped") diff --git a/webrtc_teleoperation/pc1_leader/leader_sender_udp.py b/webrtc_teleoperation/pc1_leader/leader_sender_udp.py index e1c1b709b97..8218141e66f 100644 --- a/webrtc_teleoperation/pc1_leader/leader_sender_udp.py +++ b/webrtc_teleoperation/pc1_leader/leader_sender_udp.py @@ -2,10 +2,21 @@ PC1 (Leader) - Robot Control Sender (UDP) Sends bi-SO100 leader commands via UDP to the follower. This is kept separate from WebRTC camera streaming. + +Features: +- RTT (Round-Trip Time) measurement +- Jitter calculation +- Packet loss detection +- Bandwidth monitoring +- CSV logging with timestamp """ import socket import json import time +import csv +from datetime import datetime +from collections import deque +import statistics from lerobot.teleoperators.bi_so100_leader.bi_so100_leader import BiSO100Leader from lerobot.teleoperators.bi_so100_leader.config_bi_so100_leader import BiSO100LeaderConfig @@ -18,10 +29,25 @@ LEADER_RIGHT_PORT = "COM8" # Change to your right arm leader COM port LEADER_ID = "leader" +# Measurement settings +LOG_INTERVAL = 1.0 # Log metrics every 1 second +RTT_WINDOW = 100 # Keep last 100 RTT measurements for jitter calculation +TIMEOUT = 0.1 # Socket timeout for RTT measurement (100ms) + # ========================== +# Create CSV log file with timestamp +log_filename = f"teleoperation_control_log_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv" +csv_file = open(log_filename, 'w', newline='') +csv_writer = csv.writer(csv_file) +csv_writer.writerow([ + 'Timestamp', 'RTT_ms', 'Jitter_ms', 'Packet_Loss_%', + 'Bandwidth_kbps', 'Packets_Sent', 'Packets_Lost' +]) + # Initialize UDP socket sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) +sock.settimeout(TIMEOUT) dt = 1.0 / SEND_HZ # Initialize bi-SO100 leader (dual arms) @@ -33,24 +59,89 @@ leader = BiSO100Leader(leader_cfg) leader.connect() +# Measurement variables +sequence_num = 0 +packets_sent = 0 +packets_lost = 0 +bytes_sent = 0 +rtt_measurements = deque(maxlen=RTT_WINDOW) +last_log_time = time.time() + print(f"Leader bi-SO100 initialized on {LEADER_LEFT_PORT} (left) and {LEADER_RIGHT_PORT} (right)") print(f"Sending commands to {FOLLOWER_IP}:{UDP_PORT} at {SEND_HZ} Hz") +print(f"Logging to: {log_filename}") +print(f"Metrics: RTT, Jitter, Packet Loss, Bandwidth") try: while True: + loop_start = time.time() + # Get action from leader raw_action = leader.get_action() action = {key: float(value) for key, value in raw_action.items()} - # Send via UDP + # Send via UDP with sequence number for RTT measurement msg = { "ts": time.time(), + "seq": sequence_num, "action": action, } data = json.dumps(msg).encode("utf-8") + + send_time = time.time() sock.sendto(data, (FOLLOWER_IP, UDP_PORT)) + bytes_sent += len(data) + packets_sent += 1 + + # Try to receive echo for RTT measurement + try: + echo_data, _ = sock.recvfrom(256) + recv_time = time.time() + echo_msg = json.loads(echo_data.decode("utf-8")) + + if echo_msg.get("seq") == sequence_num: + rtt = (recv_time - send_time) * 1000 # Convert to ms + rtt_measurements.append(rtt) + except socket.timeout: + # No echo received - count as lost packet + packets_lost += 1 + except Exception: + packets_lost += 1 + + sequence_num += 1 + + # Log metrics periodically + current_time = time.time() + if current_time - last_log_time >= LOG_INTERVAL: + # Calculate metrics + avg_rtt = statistics.mean(rtt_measurements) if rtt_measurements else 0 + jitter = statistics.stdev(rtt_measurements) if len(rtt_measurements) > 1 else 0 + packet_loss = (packets_lost / packets_sent * 100) if packets_sent > 0 else 0 + bandwidth = (bytes_sent * 8) / (current_time - last_log_time) / 1000 # kbps + + # Write to CSV + csv_writer.writerow([ + datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3], + f"{avg_rtt:.2f}", + f"{jitter:.2f}", + f"{packet_loss:.2f}", + f"{bandwidth:.2f}", + packets_sent, + packets_lost + ]) + csv_file.flush() + + # Print to console + print(f"RTT: {avg_rtt:.1f}ms | Jitter: {jitter:.1f}ms | Loss: {packet_loss:.1f}% | BW: {bandwidth:.1f}kbps") + + # Reset counters + last_log_time = current_time + bytes_sent = 0 - time.sleep(dt) + # Sleep to maintain send rate + elapsed = time.time() - loop_start + sleep_time = max(0, dt - elapsed) + time.sleep(sleep_time) except KeyboardInterrupt: print("\nStopping sender...") @@ -60,4 +151,5 @@ except: pass sock.close() - print("Leader sender stopped") + csv_file.close() + print(f"Leader sender stopped. Log saved to: {log_filename}") diff --git a/webrtc_teleoperation/pc2_follower/robot_receiver_udp.py b/webrtc_teleoperation/pc2_follower/robot_receiver_udp.py index 1a365c4f73a..cf60b807625 100644 --- a/webrtc_teleoperation/pc2_follower/robot_receiver_udp.py +++ b/webrtc_teleoperation/pc2_follower/robot_receiver_udp.py @@ -2,6 +2,10 @@ PC2 (Follower) - Robot Control Receiver (UDP) Receives robot commands via UDP and controls the bi-SO100 follower arm. This is kept separate from WebRTC camera streaming. + +Features: +- Echo packets back to leader for RTT measurement +- Receive timestamped commands """ import socket import json @@ -31,12 +35,19 @@ print(f"Follower robot initialized on {FOLLOWER_LEFT_PORT} (left) and {FOLLOWER_RIGHT_PORT} (right)") print(f"Listening for commands on UDP port {UDP_PORT}") +print("Echoing packets for RTT measurement") try: while True: data, addr = sock.recvfrom(4096) msg = json.loads(data.decode("utf-8")) + # Echo back sequence number for RTT measurement + if "seq" in msg: + echo = {"seq": msg["seq"]} + echo_data = json.dumps(echo).encode("utf-8") + sock.sendto(echo_data, addr) + action = msg.get("action") if action: # Send action to follower robot From ee2f3b82dd2afa32463168f8ee8384d3aa798d1e Mon Sep 17 00:00:00 2001 From: Raid Date: Thu, 15 Jan 2026 14:58:10 +0800 Subject: [PATCH 11/12] modified: webrtc_teleoperation/pc1_leader/camera_client_webrtc.py modified: webrtc_teleoperation/pc1_leader/leader_sender_udp.py modified: webrtc_teleoperation/pc2_follower/camera_server_webrtc.py --- .../pc1_leader/camera_client_webrtc.py | 118 +++++++++++++----- .../pc1_leader/leader_sender_udp.py | 2 +- .../pc2_follower/camera_server_webrtc.py | 2 +- 3 files changed, 86 insertions(+), 36 deletions(-) diff --git a/webrtc_teleoperation/pc1_leader/camera_client_webrtc.py b/webrtc_teleoperation/pc1_leader/camera_client_webrtc.py index a9ab5815d51..30405700b50 100644 --- a/webrtc_teleoperation/pc1_leader/camera_client_webrtc.py +++ b/webrtc_teleoperation/pc1_leader/camera_client_webrtc.py @@ -26,10 +26,16 @@ logger = logging.getLogger(__name__) # ========= CONFIG ========= -SIGNALING_SERVER = "ws://localhost:8080/ws" # Change to your deployed server URL +SIGNALING_SERVER = "wss://msf-lerobot-1.onrender.com/ws" # Change to your deployed server URL PEER_ID = "leader" TARGET_PEER = "follower" +# ICE servers for NAT traversal +ICE_SERVERS = [ + {"urls": "stun:stun.l.google.com:19302"}, + {"urls": "stun:stun1.l.google.com:19302"}, +] + # Measurement settings LOG_INTERVAL = 1.0 # Log metrics every 1 second LATENCY_WINDOW = 100 # Keep last 100 latency measurements @@ -46,7 +52,8 @@ def __init__(self, signaling_url, peer_id, target_peer): self.target_peer = target_peer self.pc = None self.ws = None - self.video_frames = asyncio.Queue(maxsize=1) + self.video_tracks = [] # List to store multiple tracks + self.video_queues = [] # Separate queue for each camera # Measurement tracking self.frame_count = 0 @@ -84,14 +91,32 @@ async def connect_signaling(self): async def handle_offer(self, offer_data): """Handle incoming offer and send answer.""" - self.pc = RTCPeerConnection() + from aiortc import RTCConfiguration, RTCIceServer + + ice_servers = [] + for server in ICE_SERVERS: + if "username" in server and "credential" in server: + ice_servers.append(RTCIceServer( + urls=server["urls"], + username=server["username"], + credential=server["credential"] + )) + else: + ice_servers.append(RTCIceServer(urls=server["urls"])) + + config = RTCConfiguration(iceServers=ice_servers) + self.pc = RTCPeerConnection(configuration=config) # Handle incoming video track @self.pc.on("track") async def on_track(track): logger.info(f"Receiving {track.kind} track") if track.kind == "video": - asyncio.create_task(self.process_video_track(track)) + track_index = len(self.video_tracks) + self.video_tracks.append(track) + queue = asyncio.Queue(maxsize=1) + self.video_queues.append(queue) + asyncio.create_task(self.process_video_track(track, queue, track_index)) # Set remote description (offer) offer = RTCSessionDescription( @@ -114,9 +139,9 @@ async def on_track(track): })) logger.info(f"Sent answer to {self.target_peer}") - async def process_video_track(self, track): + async def process_video_track(self, track, queue, track_index): """Process incoming video frames.""" - logger.info("Started receiving video frames") + logger.info(f"Started receiving video frames for Camera {track_index + 1}") try: while True: recv_time = time.time() @@ -135,17 +160,17 @@ async def process_video_track(self, track): self.frames_received += 1 # Put frame in queue (drop old frame if queue full) - if self.video_frames.full(): + if queue.full(): try: - self.video_frames.get_nowait() + queue.get_nowait() self.frames_dropped += 1 except asyncio.QueueEmpty: pass - await self.video_frames.put(img) + await queue.put(img) except Exception as e: - logger.error(f"Error processing video: {e}") + logger.error(f"Error processing video from Camera {track_index + 1}: {e}") async def handle_signaling_messages(self): """Handle incoming signaling messages.""" @@ -171,20 +196,59 @@ async def handle_signaling_messages(self): logger.warning("Signaling connection closed") async def display_video(self): - """Display received video frames.""" + """Display received video frames in separate windows.""" logger.info("Starting video display...") frames_displayed = 0 display_start = time.time() + # Position windows in a 2x2 grid + window_positions = [ + (0, 0), # Top-left + (650, 0), # Top-right + (0, 500), # Bottom-left + (650, 500), # Bottom-right + ] + + windows_created = set() + while True: try: - # Get frame from queue with timeout - frame = await asyncio.wait_for(self.video_frames.get(), timeout=1.0) - frames_displayed += 1 - self.frame_count += 1 + current_time = time.time() + + # Display each camera in its own window + for i, queue in enumerate(self.video_queues): + try: + # Try to get latest frame (non-blocking) + frame = queue.get_nowait() + frames_displayed += 1 + self.frame_count += 1 + + # Add metrics overlay on frame + if self.latency_measurements: + avg_latency = statistics.mean(self.latency_measurements) + fps = frames_displayed / (current_time - display_start) if (current_time - display_start) > 0 else 0 + + cv2.putText(frame, f"Camera {i + 1}", (10, 30), + cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) + cv2.putText(frame, f"Latency: {avg_latency:.1f}ms", (10, 60), + cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) + cv2.putText(frame, f"FPS: {fps:.1f}", (10, 90), + cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) + + window_name = f"Camera {i + 1}" + cv2.imshow(window_name, frame) + + # Position window only once when first created + if window_name not in windows_created and i < len(window_positions): + x, y = window_positions[i] + cv2.moveWindow(window_name, x, y) + windows_created.add(window_name) + + except asyncio.QueueEmpty: + # No new frame available for this camera - that's ok + pass # Calculate and log metrics periodically - current_time = time.time() if current_time - self.last_log_time >= LOG_INTERVAL: elapsed = current_time - self.last_log_time @@ -214,29 +278,15 @@ async def display_video(self): self.frame_count = 0 self.bytes_received = 0 - # Add metrics overlay on frame - if self.latency_measurements: - avg_latency = statistics.mean(self.latency_measurements) - fps = frames_displayed / (current_time - display_start) if (current_time - display_start) > 0 else 0 - - cv2.putText(frame, f"Latency: {avg_latency:.1f}ms", (10, 30), - cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) - cv2.putText(frame, f"FPS: {fps:.1f}", (10, 60), - cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) - - # Display frame - cv2.imshow("Follower Camera (WebRTC)", frame) + # Small async sleep to prevent busy waiting + await asyncio.sleep(0.01) # Process OpenCV events - if cv2.waitKey(1) & 0xFF == ord('q'): + key = cv2.waitKey(1) & 0xFF + if key == ord('q'): logger.info("Quit signal received") break - except asyncio.TimeoutError: - # No frame received in 1 second - continue waiting - cv2.waitKey(1) # Keep window responsive - continue - except Exception as e: logger.error(f"Display error: {e}") break diff --git a/webrtc_teleoperation/pc1_leader/leader_sender_udp.py b/webrtc_teleoperation/pc1_leader/leader_sender_udp.py index 8218141e66f..c5945267043 100644 --- a/webrtc_teleoperation/pc1_leader/leader_sender_udp.py +++ b/webrtc_teleoperation/pc1_leader/leader_sender_udp.py @@ -21,7 +21,7 @@ from lerobot.teleoperators.bi_so100_leader.config_bi_so100_leader import BiSO100LeaderConfig # ========= CONFIG ========= -FOLLOWER_IP = "192.168.0.90" # PC2 IP address +FOLLOWER_IP = "100.78.30.123" # PC2 IP address UDP_PORT = 5005 SEND_HZ = 50 diff --git a/webrtc_teleoperation/pc2_follower/camera_server_webrtc.py b/webrtc_teleoperation/pc2_follower/camera_server_webrtc.py index 40894765448..426fd36a676 100644 --- a/webrtc_teleoperation/pc2_follower/camera_server_webrtc.py +++ b/webrtc_teleoperation/pc2_follower/camera_server_webrtc.py @@ -15,7 +15,7 @@ logger = logging.getLogger(__name__) # ========= CONFIG ========= -SIGNALING_SERVER = "ws://localhost:8080/ws" # Change to your deployed server URL +SIGNALING_SERVER = "wss://msf-lerobot-1.onrender.com/ws" # Change to your deployed server URL CAMERA_INDEX = 0 # Change to your camera index PEER_ID = "follower" TARGET_PEER = "leader" From 0d10dc466489a4efd80efe2e81030672f6058a58 Mon Sep 17 00:00:00 2001 From: Raid Date: Thu, 15 Jan 2026 17:31:40 +0800 Subject: [PATCH 12/12] modified: webrtc_teleoperation/pc1_leader/leader_sender_udp.py modified: webrtc_teleoperation/pc2_follower/robot_receiver_udp.py --- .../pc1_leader/leader_sender_udp.py | 24 +++++++++++------ .../pc2_follower/robot_receiver_udp.py | 27 ++++++++++++++----- 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/webrtc_teleoperation/pc1_leader/leader_sender_udp.py b/webrtc_teleoperation/pc1_leader/leader_sender_udp.py index c5945267043..de29d27f164 100644 --- a/webrtc_teleoperation/pc1_leader/leader_sender_udp.py +++ b/webrtc_teleoperation/pc1_leader/leader_sender_udp.py @@ -4,7 +4,7 @@ This is kept separate from WebRTC camera streaming. Features: -- RTT (Round-Trip Time) measurement +- End-to-End Latency measurement (Leader → Follower Execution) - Jitter calculation - Packet loss detection - Bandwidth monitoring @@ -14,12 +14,16 @@ import json import time import csv +import logging from datetime import datetime from collections import deque import statistics from lerobot.teleoperators.bi_so100_leader.bi_so100_leader import BiSO100Leader from lerobot.teleoperators.bi_so100_leader.config_bi_so100_leader import BiSO100LeaderConfig +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + # ========= CONFIG ========= FOLLOWER_IP = "100.78.30.123" # PC2 IP address UDP_PORT = 5005 @@ -41,7 +45,7 @@ csv_file = open(log_filename, 'w', newline='') csv_writer = csv.writer(csv_file) csv_writer.writerow([ - 'Timestamp', 'RTT_ms', 'Jitter_ms', 'Packet_Loss_%', + 'Timestamp', 'E2E_Latency_ms', 'Jitter_ms', 'Packet_Loss_%', 'Bandwidth_kbps', 'Packets_Sent', 'Packets_Lost' ]) @@ -70,7 +74,7 @@ print(f"Leader bi-SO100 initialized on {LEADER_LEFT_PORT} (left) and {LEADER_RIGHT_PORT} (right)") print(f"Sending commands to {FOLLOWER_IP}:{UDP_PORT} at {SEND_HZ} Hz") print(f"Logging to: {log_filename}") -print(f"Metrics: RTT, Jitter, Packet Loss, Bandwidth") +print(f"Metrics: End-to-End Latency (Leader → Follower Execution), Jitter, Packet Loss, Bandwidth") try: while True: @@ -93,15 +97,19 @@ bytes_sent += len(data) packets_sent += 1 - # Try to receive echo for RTT measurement + # Try to receive echo with end-to-end latency measurement try: - echo_data, _ = sock.recvfrom(256) + echo_data, _ = sock.recvfrom(512) recv_time = time.time() echo_msg = json.loads(echo_data.decode("utf-8")) if echo_msg.get("seq") == sequence_num: - rtt = (recv_time - send_time) * 1000 # Convert to ms - rtt_measurements.append(rtt) + # Use end-to-end latency if available, otherwise fall back to RTT + if "e2e_latency" in echo_msg: + latency = echo_msg["e2e_latency"] + else: + latency = (recv_time - send_time) * 1000 # Convert to ms + rtt_measurements.append(latency) except socket.timeout: # No echo received - count as lost packet packets_lost += 1 @@ -132,7 +140,7 @@ csv_file.flush() # Print to console - print(f"RTT: {avg_rtt:.1f}ms | Jitter: {jitter:.1f}ms | Loss: {packet_loss:.1f}% | BW: {bandwidth:.1f}kbps") + logger.info(f"E2E Latency: {avg_rtt:.1f}ms | Jitter: {jitter:.1f}ms | Loss: {packet_loss:.1f}% | BW: {bandwidth:.1f}kbps") # Reset counters last_log_time = current_time diff --git a/webrtc_teleoperation/pc2_follower/robot_receiver_udp.py b/webrtc_teleoperation/pc2_follower/robot_receiver_udp.py index cf60b807625..1d499ccac9b 100644 --- a/webrtc_teleoperation/pc2_follower/robot_receiver_udp.py +++ b/webrtc_teleoperation/pc2_follower/robot_receiver_udp.py @@ -4,11 +4,13 @@ This is kept separate from WebRTC camera streaming. Features: -- Echo packets back to leader for RTT measurement +- Echo packets back to leader for end-to-end latency measurement - Receive timestamped commands +- Measure execution latency """ import socket import json +import time from lerobot.robots.bi_so100.bi_so100 import BiSO100Robot from lerobot.robots.bi_so100.config_bi_so100 import BiSO100RobotConfig @@ -39,19 +41,30 @@ try: while True: + recv_time = time.time() data, addr = sock.recvfrom(4096) msg = json.loads(data.decode("utf-8")) - # Echo back sequence number for RTT measurement - if "seq" in msg: - echo = {"seq": msg["seq"]} - echo_data = json.dumps(echo).encode("utf-8") - sock.sendto(echo_data, addr) - action = msg.get("action") if action: # Send action to follower robot follower.send_action(action) + execution_time = time.time() + + # Calculate end-to-end latency (from leader timestamp to execution completion) + leader_timestamp = msg.get("ts", execution_time) + end_to_end_latency = (execution_time - leader_timestamp) * 1000 # ms + + # Echo back with latency information + if "seq" in msg: + echo = { + "seq": msg["seq"], + "e2e_latency": end_to_end_latency, + "recv_time": recv_time, + "exec_time": execution_time + } + echo_data = json.dumps(echo).encode("utf-8") + sock.sendto(echo_data, addr) except KeyboardInterrupt: print("\nStopping receiver...")