commit
ffe9f7faea
951 changed files with 75739 additions and 0 deletions
-
23.gitattributes
-
175.gitignore
-
38LICENSE
-
16Makefile
-
140README.md
-
160docker/.bashrc
-
84docker/.tmux.conf
-
5docker/70-manus-hid.rules
-
127docker/Dockerfile.deploy
-
155docker/Dockerfile.deploy.base
-
55docker/build_deploy_base.sh
-
5docker/entrypoint/bash.sh
-
20docker/entrypoint/deploy.sh
-
23docker/entrypoint/install_deps.sh
-
1docker/image_name.txt
-
79docker/kill_all_containers.sh
-
192docker/kill_gr00t_wbc_processors.sh
-
3docker/publish.sh
-
454docker/run_docker.sh
-
39external_dependencies/unitree_sdk2_python/.gitignore
-
29external_dependencies/unitree_sdk2_python/LICENSE
-
121external_dependencies/unitree_sdk2_python/README zh.md
-
118external_dependencies/unitree_sdk2_python/README.md
-
51external_dependencies/unitree_sdk2_python/example/b2/camera/camera_opencv.py
-
51external_dependencies/unitree_sdk2_python/example/b2/camera/capture_image.py
-
105external_dependencies/unitree_sdk2_python/example/b2/high_level/b2_sport_client.py
-
175external_dependencies/unitree_sdk2_python/example/b2/low_level/b2_stand_example.py
-
20external_dependencies/unitree_sdk2_python/example/b2/low_level/unitree_legged_const.py
-
51external_dependencies/unitree_sdk2_python/example/b2w/camera/camera_opencv.py
-
51external_dependencies/unitree_sdk2_python/example/b2w/camera/capture_image.py
-
101external_dependencies/unitree_sdk2_python/example/b2w/high_level/b2w_sport_client.py
-
196external_dependencies/unitree_sdk2_python/example/b2w/low_level/b2w_stand_example.py
-
24external_dependencies/unitree_sdk2_python/example/b2w/low_level/unitree_legged_const.py
-
44external_dependencies/unitree_sdk2_python/example/g1/audio/g1_audio_client_example.py
-
192external_dependencies/unitree_sdk2_python/example/g1/high_level/g1_arm5_sdk_dds_example.py
-
194external_dependencies/unitree_sdk2_python/example/g1/high_level/g1_arm7_sdk_dds_example.py
-
117external_dependencies/unitree_sdk2_python/example/g1/high_level/g1_loco_client_example.py
-
205external_dependencies/unitree_sdk2_python/example/g1/low_level/g1_low_level_example.py
-
225external_dependencies/unitree_sdk2_python/example/g1/low_level/g1_move_hands_example.py
-
5external_dependencies/unitree_sdk2_python/example/g1/readme.md
-
41external_dependencies/unitree_sdk2_python/example/go2/front_camera/camera_opencv.py
-
30external_dependencies/unitree_sdk2_python/example/go2/front_camera/capture_image.py
-
170external_dependencies/unitree_sdk2_python/example/go2/high_level/go2_sport_client.py
-
39external_dependencies/unitree_sdk2_python/example/go2/high_level/go2_utlidar_switch.py
-
176external_dependencies/unitree_sdk2_python/example/go2/low_level/go2_stand_example.py
-
20external_dependencies/unitree_sdk2_python/example/go2/low_level/unitree_legged_const.py
-
99external_dependencies/unitree_sdk2_python/example/go2w/high_level/go2w_sport_client.py
-
196external_dependencies/unitree_sdk2_python/example/go2w/low_level/go2w_stand_example.py
-
24external_dependencies/unitree_sdk2_python/example/go2w/low_level/unitree_legged_const.py
-
96external_dependencies/unitree_sdk2_python/example/h1/high_level/h1_loco_client_example.py
-
167external_dependencies/unitree_sdk2_python/example/h1/low_level/h1_low_level_example.py
-
5external_dependencies/unitree_sdk2_python/example/h1/low_level/unitree_legged_const.py
-
201external_dependencies/unitree_sdk2_python/example/h1_2/low_level/h1_2_low_level_example.py
-
28external_dependencies/unitree_sdk2_python/example/helloworld/publisher.py
-
20external_dependencies/unitree_sdk2_python/example/helloworld/subscriber.py
-
9external_dependencies/unitree_sdk2_python/example/helloworld/user_data.py
-
36external_dependencies/unitree_sdk2_python/example/motionSwitcher/motion_switcher_example.py
-
35external_dependencies/unitree_sdk2_python/example/obstacles_avoid/obstacles_avoid_move.py
-
94external_dependencies/unitree_sdk2_python/example/obstacles_avoid/obstacles_avoid_switch.py
-
77external_dependencies/unitree_sdk2_python/example/vui_client/vui_client_example.py
-
131external_dependencies/unitree_sdk2_python/example/wireless_controller/wireless_controller.py
-
3external_dependencies/unitree_sdk2_python/pyproject.toml
-
21external_dependencies/unitree_sdk2_python/setup.py
-
10external_dependencies/unitree_sdk2_python/unitree_sdk2py/__init__.py
-
16external_dependencies/unitree_sdk2_python/unitree_sdk2py/b2/back_video/back_video_api.py
-
23external_dependencies/unitree_sdk2_python/unitree_sdk2py/b2/back_video/back_video_client.py
-
16external_dependencies/unitree_sdk2_python/unitree_sdk2py/b2/front_video/front_video_api.py
-
23external_dependencies/unitree_sdk2_python/unitree_sdk2py/b2/front_video/front_video_client.py
-
25external_dependencies/unitree_sdk2_python/unitree_sdk2py/b2/robot_state/robot_state_api.py
-
84external_dependencies/unitree_sdk2_python/unitree_sdk2py/b2/robot_state/robot_state_client.py
-
58external_dependencies/unitree_sdk2_python/unitree_sdk2py/b2/sport/sport_api.py
-
241external_dependencies/unitree_sdk2_python/unitree_sdk2py/b2/sport/sport_client.py
-
21external_dependencies/unitree_sdk2_python/unitree_sdk2py/b2/vui/vui_api.py
-
86external_dependencies/unitree_sdk2_python/unitree_sdk2py/b2/vui/vui_client.py
-
0external_dependencies/unitree_sdk2_python/unitree_sdk2py/comm/motion_switcher/__init__.py
-
29external_dependencies/unitree_sdk2_python/unitree_sdk2py/comm/motion_switcher/motion_switcher_api.py
-
51external_dependencies/unitree_sdk2_python/unitree_sdk2py/comm/motion_switcher/motion_switcher_client.py
-
0external_dependencies/unitree_sdk2_python/unitree_sdk2py/core/__init__.py
-
290external_dependencies/unitree_sdk2_python/unitree_sdk2py/core/channel.py
-
25external_dependencies/unitree_sdk2_python/unitree_sdk2py/core/channel_config.py
-
34external_dependencies/unitree_sdk2_python/unitree_sdk2py/core/channel_name.py
-
24external_dependencies/unitree_sdk2_python/unitree_sdk2py/g1/audio/g1_audio_api.py
-
62external_dependencies/unitree_sdk2_python/unitree_sdk2py/g1/audio/g1_audio_client.py
-
32external_dependencies/unitree_sdk2_python/unitree_sdk2py/g1/loco/g1_loco_api.py
-
127external_dependencies/unitree_sdk2_python/unitree_sdk2py/g1/loco/g1_loco_client.py
-
0external_dependencies/unitree_sdk2_python/unitree_sdk2py/go2/__init__.py
-
0external_dependencies/unitree_sdk2_python/unitree_sdk2py/go2/obstacles_avoid/__init__.py
-
19external_dependencies/unitree_sdk2_python/unitree_sdk2py/go2/obstacles_avoid/obstacles_avoid_api.py
-
60external_dependencies/unitree_sdk2_python/unitree_sdk2py/go2/obstacles_avoid/obstacles_avoid_client.py
-
0external_dependencies/unitree_sdk2_python/unitree_sdk2py/go2/robot_state/__init__.py
-
25external_dependencies/unitree_sdk2_python/unitree_sdk2py/go2/robot_state/robot_state_api.py
-
84external_dependencies/unitree_sdk2_python/unitree_sdk2py/go2/robot_state/robot_state_client.py
-
0external_dependencies/unitree_sdk2_python/unitree_sdk2py/go2/sport/__init__.py
-
74external_dependencies/unitree_sdk2_python/unitree_sdk2py/go2/sport/sport_api.py
-
446external_dependencies/unitree_sdk2_python/unitree_sdk2py/go2/sport/sport_client.py
-
0external_dependencies/unitree_sdk2_python/unitree_sdk2py/go2/video/__init__.py
-
16external_dependencies/unitree_sdk2_python/unitree_sdk2py/go2/video/video_api.py
-
23external_dependencies/unitree_sdk2_python/unitree_sdk2py/go2/video/video_client.py
-
0external_dependencies/unitree_sdk2_python/unitree_sdk2py/go2/vui/__init__.py
-
21external_dependencies/unitree_sdk2_python/unitree_sdk2py/go2/vui/vui_api.py
@ -0,0 +1,23 @@ |
|||
# Machine learning models and data files |
|||
*.pt filter=lfs diff=lfs merge=lfs -text |
|||
*.onnx filter=lfs diff=lfs merge=lfs -text |
|||
*.pkl filter=lfs diff=lfs merge=lfs -text |
|||
# 3D assets and models |
|||
*.usd filter=lfs diff=lfs merge=lfs -text |
|||
*.usda filter=lfs diff=lfs merge=lfs -text |
|||
*.STL filter=lfs diff=lfs merge=lfs -text |
|||
*.stl filter=lfs diff=lfs merge=lfs -text |
|||
# Shared libraries |
|||
*.a filter=lfs diff=lfs merge=lfs -text |
|||
*.so* filter=lfs diff=lfs merge=lfs -text |
|||
*.png filter=lfs diff=lfs merge=lfs -text |
|||
*.gif filter=lfs diff=lfs merge=lfs -text |
|||
*.safetensors filter=lfs diff=lfs merge=lfs -text |
|||
*.deb filter=lfs diff=lfs merge=lfs -text |
|||
# Collect demo in sim |
|||
*.hdf5 filter=lfs diff=lfs merge=lfs -text |
|||
*.parquet filter=lfs diff=lfs merge=lfs -text |
|||
*.obj filter=lfs diff=lfs merge=lfs -text |
|||
*.dae filter=lfs diff=lfs merge=lfs -text |
|||
*.so filter=lfs diff=lfs merge=lfs -text |
|||
*.so.* filter=lfs diff=lfs merge=lfs -text |
|||
@ -0,0 +1,175 @@ |
|||
# Byte-compiled / optimized / DLL files |
|||
__pycache__/ |
|||
*.py[cod] |
|||
*$py.class |
|||
|
|||
# C extensions |
|||
|
|||
|
|||
# Distribution / packaging |
|||
.Python |
|||
build/ |
|||
develop-eggs/ |
|||
dist/ |
|||
downloads/ |
|||
eggs/ |
|||
.eggs/ |
|||
parts/ |
|||
sdist/ |
|||
var/ |
|||
wheels/ |
|||
share/python-wheels/ |
|||
*.egg-info/ |
|||
.installed.cfg |
|||
*.egg |
|||
MANIFEST |
|||
|
|||
# PyInstaller |
|||
# Usually these files are written by a python script from a template |
|||
# before PyInstaller builds the exe, so as to inject date/other infos into it. |
|||
*.manifest |
|||
*.spec |
|||
|
|||
# Installer logs |
|||
pip-log.txt |
|||
pip-delete-this-directory.txt |
|||
|
|||
# Unit test / coverage reports |
|||
htmlcov/ |
|||
.tox/ |
|||
.nox/ |
|||
.coverage |
|||
.coverage.* |
|||
.cache |
|||
nosetests.xml |
|||
coverage.xml |
|||
*.cover |
|||
*.py,cover |
|||
.hypothesis/ |
|||
.pytest_cache/ |
|||
cover/ |
|||
|
|||
# Translations |
|||
*.mo |
|||
*.pot |
|||
|
|||
# Django stuff: |
|||
*.log |
|||
local_settings.py |
|||
db.sqlite3 |
|||
db.sqlite3-journal |
|||
|
|||
# Flask stuff: |
|||
instance/ |
|||
.webassets-cache |
|||
|
|||
# Scrapy stuff: |
|||
.scrapy |
|||
|
|||
# Sphinx documentation |
|||
docs/_build/ |
|||
|
|||
# PyBuilder |
|||
.pybuilder/ |
|||
target/ |
|||
|
|||
# Jupyter Notebook |
|||
.ipynb_checkpoints |
|||
|
|||
# IPython |
|||
profile_default/ |
|||
ipython_config.py |
|||
|
|||
# pyenv |
|||
# For a library or package, you might want to ignore these files since the code is |
|||
# intended to run in multiple environments; otherwise, check them in: |
|||
# .python-version |
|||
|
|||
# pipenv |
|||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. |
|||
# However, in case of collaboration, if having platform-specific dependencies or dependencies |
|||
# having no cross-platform support, pipenv may install dependencies that don't work, or not |
|||
# install all needed dependencies. |
|||
#Pipfile.lock |
|||
|
|||
# UV |
|||
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. |
|||
# This is especially recommended for binary packages to ensure reproducibility, and is more |
|||
# commonly ignored for libraries. |
|||
#uv.lock |
|||
|
|||
# poetry |
|||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. |
|||
# This is especially recommended for binary packages to ensure reproducibility, and is more |
|||
# commonly ignored for libraries. |
|||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control |
|||
#poetry.lock |
|||
|
|||
# pdm |
|||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. |
|||
#pdm.lock |
|||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it |
|||
# in version control. |
|||
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control |
|||
.pdm.toml |
|||
.pdm-python |
|||
.pdm-build/ |
|||
|
|||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm |
|||
__pypackages__/ |
|||
|
|||
# Celery stuff |
|||
celerybeat-schedule |
|||
celerybeat.pid |
|||
|
|||
# SageMath parsed files |
|||
*.sage.py |
|||
|
|||
# Spyder project settings |
|||
.spyderproject |
|||
.spyproject |
|||
|
|||
# Rope project settings |
|||
.ropeproject |
|||
|
|||
# mkdocs documentation |
|||
/site |
|||
|
|||
# mypy |
|||
.mypy_cache/ |
|||
.dmypy.json |
|||
dmypy.json |
|||
|
|||
# Pyre type checker |
|||
.pyre/ |
|||
|
|||
# pytype static type analyzer |
|||
.pytype/ |
|||
|
|||
# Cython debug symbols |
|||
cython_debug/ |
|||
|
|||
# IDE |
|||
.idea/ |
|||
.vscode/ |
|||
|
|||
# log |
|||
outputs/ |
|||
|
|||
# Ruff stuff: |
|||
.ruff_cache/ |
|||
|
|||
# PyPI configuration file |
|||
.pypirc |
|||
|
|||
outputs/ |
|||
|
|||
.DS_Store |
|||
|
|||
|
|||
# Mujoco |
|||
MUJOCO_LOG.TXT |
|||
|
|||
# IsaacDeploy |
|||
external_dependencies/isaac_teleop_app/isaac-deploy |
|||
|
|||
@ -0,0 +1,38 @@ |
|||
NVIDIA License |
|||
|
|||
1. Definitions |
|||
|
|||
“Licensor” means any person or entity that distributes its Work. |
|||
“Work” means (a) the original work of authorship made available under this license, which may include software, documentation, or other files, and (b) any additions to or derivative works thereof that are made available under this license. |
|||
The terms “reproduce,” “reproduction,” “derivative works,” and “distribution” have the meaning as provided under U.S. copyright law; provided, however, that for the purposes of this license, derivative works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work. |
|||
Works are “made available” under this license by including in or with the Work either (a) a copyright notice referencing the applicability of this license to the Work, or (b) a copy of this license. |
|||
|
|||
2. License Grant |
|||
|
|||
2.1 Copyright Grant. Subject to the terms and conditions of this license, each Licensor grants to you a perpetual, worldwide, non-exclusive, royalty-free, copyright license to use, reproduce, prepare derivative works of, publicly display, publicly perform, sublicense and distribute its Work and any resulting derivative works in any form. |
|||
|
|||
3. Limitations |
|||
|
|||
3.1 Redistribution. You may reproduce or distribute the Work only if (a) you do so under this license, (b) you include a complete copy of this license with your distribution, and (c) you retain without modification any copyright, patent, trademark, or attribution notices that are present in the Work. |
|||
|
|||
3.2 Derivative Works. You may specify that additional or different terms apply to the use, reproduction, and distribution of your derivative works of the Work (“Your Terms”) only if (a) Your Terms provide that the use limitation in Section 3.3 applies to your derivative works; (b) you comply with Other Licenses, and (c) you identify the specific derivative works that are subject to Your Terms and Other Licenses, as applicable. Notwithstanding Your Terms, this license (including the redistribution requirements in Section 3.1) will continue to apply to the Work itself. |
|||
|
|||
3.3 Use Limitation. The Work and any derivative works thereof only may be used or intended for use non-commercially. As used herein, “non-commercially” means for non-commercial research purposes only, and excludes any military, surveillance, service of nuclear technology or biometric processing purposes. |
|||
|
|||
3.4 Patent Claims. If you bring or threaten to bring a patent claim against any Licensor (including any claim, cross-claim or counterclaim in a lawsuit) to enforce any patents that you allege are infringed by any Work, then your rights under this license from such Licensor (including the grant in Section 2.1) will terminate immediately. |
|||
|
|||
3.5 Trademarks. This license does not grant any rights to use any Licensor’s or its affiliates’ names, logos, or trademarks, except as necessary to reproduce the notices described in this license. |
|||
|
|||
3.6 Termination. If you violate any term of this license, then your rights under this license (including the grant in Section 2.1) will terminate immediately. |
|||
|
|||
3.7 Components Under Other Licenses. The Work may include or be distributed with components provided with separate legal notices or terms that accompany the components, such as open source software licenses and other license terms, including but not limited to the Meta OPT-IML 175B License Agreement (“Other Licenses”). The components are subject to the applicable Other Licenses, including any proprietary notices, disclaimers, requirements and extended use rights; except that this Agreement will prevail regarding the use of third-party software, unless a third-party software license requires it license terms to prevail. |
|||
|
|||
4. Disclaimer of Warranty. |
|||
|
|||
THE WORK IS PROVIDED “AS IS” WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WARRANTIES OR CONDITIONS OF |
|||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE OR NON-INFRINGEMENT. YOU BEAR THE RISK OF UNDERTAKING ANY ACTIVITIES UNDER THIS LICENSE. |
|||
|
|||
5. Limitation of Liability. |
|||
|
|||
EXCEPT AS PROHIBITED BY APPLICABLE LAW, IN NO EVENT AND UNDER NO LEGAL THEORY, WHETHER IN TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE SHALL ANY LICENSOR BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR RELATED TO THIS LICENSE, THE USE OR INABILITY TO USE THE WORK (INCLUDING BUT NOT LIMITED TO LOSS OF GOODWILL, BUSINESS INTERRUPTION, LOST PROFITS OR DATA, COMPUTER FAILURE OR MALFUNCTION, OR ANY OTHER DAMAGES OR LOSSES), EVEN IF THE LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. |
|||
|
|||
@ -0,0 +1,16 @@ |
|||
.PHONY : run-checks |
|||
run-checks : |
|||
isort --check . |
|||
black --check . |
|||
ruff check . |
|||
# mypy . |
|||
|
|||
.PHONY : format |
|||
format : |
|||
isort . |
|||
black . |
|||
|
|||
.PHONY : build |
|||
build : |
|||
rm -rf *.egg-info/ |
|||
python -m build |
|||
@ -0,0 +1,140 @@ |
|||
# gr00t_wbc |
|||
|
|||
Software stack for loco-manipulation experiments across multiple humanoid platforms, with primary support for the Unitree G1. This repository provides whole-body control policies, a teleoperation stack, and a data exporter. |
|||
|
|||
--- |
|||
|
|||
## System Installation |
|||
|
|||
### Prerequisites |
|||
- Ubuntu 22.04 |
|||
- NVIDIA GPU with a recent driver |
|||
- Docker and NVIDIA Container Toolkit (required for GPU access inside the container) |
|||
|
|||
### Repository Setup |
|||
Install Git and Git LFS: |
|||
```bash |
|||
sudo apt update |
|||
sudo apt install git git-lfs |
|||
git lfs install |
|||
``` |
|||
|
|||
Clone the repository: |
|||
```bash |
|||
mkdir -p ~/Projects |
|||
cd ~/Projects |
|||
git clone https://github.com/NVlabs/gr00t_wbc.git |
|||
cd gr00t_wbc |
|||
``` |
|||
|
|||
### Docker Environment |
|||
We provide a Docker image with all dependencies pre-installed. |
|||
|
|||
Install a fresh image and start a container: |
|||
```bash |
|||
./docker/run_docker.sh --install --root |
|||
``` |
|||
This pulls the latest `gr00t_wbc` image from `docker.io/nvgear`. |
|||
|
|||
Start or re-enter a container: |
|||
```bash |
|||
./docker/run_docker.sh --root |
|||
``` |
|||
|
|||
Use `--root` to run as the `root` user. To run as a normal user, build the image locally: |
|||
```bash |
|||
./docker/run_docker.sh --build |
|||
``` |
|||
--- |
|||
|
|||
## Running the Control Stack |
|||
|
|||
Once inside the container, the control policies can be launched directly. |
|||
|
|||
- Simulation: |
|||
```bash |
|||
python gr00t_wbc/control/main/teleop/run_g1_control_loop.py |
|||
``` |
|||
- Real robot: Ensure the host machine network is configured per the [G1 SDK Development Guide](https://support.unitree.com/home/en/G1_developer) and set a static IP at `192.168.123.222`, subnet mask `255.255.255.0`: |
|||
```bash |
|||
python gr00t_wbc/control/main/teleop/run_g1_control_loop.py --interface real |
|||
``` |
|||
|
|||
Keyboard shortcuts (terminal window): |
|||
- `]`: Activate policy |
|||
- `o`: Deactivate policy |
|||
- `9`: Release / Hold the robot |
|||
- `w` / `s`: Move forward / backward |
|||
- `a` / `d`: Strafe left / right |
|||
- `q` / `e`: Rotate left / right |
|||
- `z`: Zero navigation commands |
|||
- `1` / `2`: Raise / lower the base height |
|||
- `backspace` (viewer): Reset the robot in the visualizer |
|||
|
|||
--- |
|||
|
|||
## Running the Teleoperation Stack |
|||
|
|||
The teleoperation policy primarily uses Pico controllers for coordinated hand and body control. It also supports other teleoperation devices, including LeapMotion and HTC Vive with Nintendo Switch Joy-Con controllers. |
|||
|
|||
Keep `run_g1_control_loop.py` running, and in another terminal run: |
|||
|
|||
```bash |
|||
python gr00t_wbc/control/main/teleop/run_teleop_policy_loop.py --hand_control_device=pico --body_control_device=pico |
|||
``` |
|||
|
|||
### Pico Setup and Controls |
|||
Configure the teleop app on your Pico headset by following the [XR Robotics guidelines](https://github.com/XR-Robotics). |
|||
|
|||
The necessary PC software is pre-installed in the Docker container. Only the [XRoboToolkit-PC-Service](https://github.com/XR-Robotics/XRoboToolkit-PC-Service) component is needed. |
|||
|
|||
Prerequisites: Connect the Pico to the same network as the host computer. |
|||
|
|||
Controller bindings: |
|||
- `menu + left trigger`: Toggle lower-body policy |
|||
- `menu + right trigger`: Toggle upper-body policy |
|||
- `Left stick`: X/Y translation |
|||
- `Right stick`: Yaw rotation |
|||
- `L/R triggers`: Control hand grippers |
|||
|
|||
Pico unit test: |
|||
```bash |
|||
python gr00t_wbc/control/teleop/streamers/pico_streamer.py |
|||
``` |
|||
|
|||
--- |
|||
|
|||
## Running the Data Collection Stack |
|||
|
|||
Run the full stack (control loop, teleop policy, and camera forwarder) via the deployment helper: |
|||
```bash |
|||
python scripts/deploy_g1.py \ |
|||
--interface sim \ |
|||
--camera_host localhost \ |
|||
--sim_in_single_process \ |
|||
--simulator robocasa \ |
|||
--image-publish \ |
|||
--enable-offscreen \ |
|||
--env_name PnPBottle \ |
|||
--hand_control_device=pico \ |
|||
--body_control_device=pico |
|||
``` |
|||
|
|||
The `tmux` session `g1_deployment` is created with panes for: |
|||
- `control_data_teleop`: Main control loop, data collection, and teleoperation policy |
|||
- `camera`: Camera forwarder |
|||
- `camera_viewer`: Optional live camera feed |
|||
|
|||
Operations in the `controller` window (`control_data_teleop` pane, left): |
|||
- `]`: Activate policy |
|||
- `o`: Deactivate policy |
|||
- `k`: Reset the simulation and policies |
|||
- `` ` ``: Terminate the tmux session |
|||
- `ctrl + d`: Exit the shell in the pane |
|||
|
|||
Operations in the `data exporter` window (`control_data_teleop` pane, right top): |
|||
- Enter the task prompt |
|||
|
|||
Operations on Pico controllers: |
|||
- `A`: Start/Stop recording |
|||
- `B`: Discard trajectory |
|||
@ -0,0 +1,160 @@ |
|||
# ~/.bashrc: executed by bash(1) for non-login shells. |
|||
# see /usr/share/doc/bash/examples/startup-files (in the package bash-doc) |
|||
# for examples |
|||
|
|||
# If not running interactively, don't do anything |
|||
case $- in |
|||
*i*) ;; |
|||
*) return;; |
|||
esac |
|||
|
|||
# don't put duplicate lines or lines starting with space in the history. |
|||
# See bash(1) for more options |
|||
HISTCONTROL=ignoreboth |
|||
|
|||
# append to the history file, don't overwrite it |
|||
shopt -s histappend |
|||
|
|||
# for setting history length see HISTSIZE and HISTFILESIZE in bash(1) |
|||
HISTSIZE=1000 |
|||
HISTFILESIZE=2000 |
|||
|
|||
# check the window size after each command and, if necessary, |
|||
# update the values of LINES and COLUMNS. |
|||
shopt -s checkwinsize |
|||
|
|||
# If set, the pattern "**" used in a pathname expansion context will |
|||
# match all files and zero or more directories and subdirectories. |
|||
#shopt -s globstar |
|||
|
|||
# make less more friendly for non-text input files, see lesspipe(1) |
|||
[ -x /usr/bin/lesspipe ] && eval "$(SHELL=/bin/sh lesspipe)" |
|||
|
|||
# set variable identifying the chroot you work in (used in the prompt below) |
|||
if [ -z "${debian_chroot:-}" ] && [ -r /etc/debian_chroot ]; then |
|||
debian_chroot=$(cat /etc/debian_chroot) |
|||
fi |
|||
|
|||
# set a fancy prompt (non-color, unless we know we "want" color) |
|||
case "$TERM" in |
|||
xterm-color|*-256color) color_prompt=yes;; |
|||
esac |
|||
|
|||
# uncomment for a colored prompt, if the terminal has the capability; turned |
|||
# off by default to not distract the user: the focus in a terminal window |
|||
# should be on the output of commands, not on the prompt |
|||
force_color_prompt=yes |
|||
|
|||
if [ -n "$force_color_prompt" ]; then |
|||
if [ -x /usr/bin/tput ] && tput setaf 1 >&/dev/null; then |
|||
# We have color support; assume it's compliant with Ecma-48 |
|||
# (ISO/IEC-6429). (Lack of such support is extremely rare, and such |
|||
# a case would tend to support setf rather than setaf.) |
|||
color_prompt=yes |
|||
else |
|||
color_prompt= |
|||
fi |
|||
fi |
|||
|
|||
if [ "$color_prompt" = yes ]; then |
|||
PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ ' |
|||
else |
|||
PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ ' |
|||
fi |
|||
unset color_prompt force_color_prompt |
|||
|
|||
# If this is an xterm set the title to user@host:dir |
|||
case "$TERM" in |
|||
xterm*|rxvt*) |
|||
PS1="\[\e]0;${debian_chroot:+($debian_chroot)}\u@\h: \w\a\]$PS1" |
|||
;; |
|||
*) |
|||
;; |
|||
esac |
|||
|
|||
# enable color support of ls and also add handy aliases |
|||
if [ -x /usr/bin/dircolors ]; then |
|||
test -r ~/.dircolors && eval "$(dircolors -b ~/.dircolors)" || eval "$(dircolors -b)" |
|||
alias ls='ls --color=auto' |
|||
#alias dir='dir --color=auto' |
|||
#alias vdir='vdir --color=auto' |
|||
|
|||
alias grep='grep --color=auto' |
|||
alias fgrep='fgrep --color=auto' |
|||
alias egrep='egrep --color=auto' |
|||
fi |
|||
|
|||
# colored GCC warnings and errors |
|||
export GCC_COLORS='error=01;31:warning=01;35:note=01;36:caret=01;32:locus=01:quote=01' |
|||
|
|||
# Set terminal type for color support |
|||
export TERM=xterm-256color |
|||
|
|||
# some more ls aliases |
|||
alias ll='ls -alF' |
|||
alias la='ls -A' |
|||
alias l='ls -CF' |
|||
|
|||
# Add an "alert" alias for long running commands. Use like so: |
|||
# sleep 10; alert |
|||
alias alert='notify-send --urgency=low -i "$([ $? = 0 ] && echo terminal || echo error)" "$(history|tail -n1|sed -e '\''s/^\s*[0-9]\+\s*//;s/[;&|]\s*alert$//'\'')"' |
|||
|
|||
# Alias definitions. |
|||
# You may want to put all your additions into a separate file like |
|||
# ~/.bash_aliases, instead of adding them here directly. |
|||
# See /usr/share/doc/bash-doc/examples in the bash-doc package. |
|||
|
|||
if [ -f ~/.bash_aliases ]; then |
|||
. ~/.bash_aliases |
|||
fi |
|||
|
|||
# enable programmable completion features (you don't need to enable |
|||
# this, if it's already enabled in /etc/bash.bashrc and /etc/profile |
|||
# sources /etc/bash.bashrc). |
|||
if ! shopt -oq posix; then |
|||
if [ -f /usr/share/bash-completion/bash_completion ]; then |
|||
. /usr/share/bash-completion/bash_completion |
|||
elif [ -f /etc/bash_completion ]; then |
|||
. /etc/bash_completion |
|||
fi |
|||
fi |
|||
|
|||
# useful commands |
|||
bind '"\e[A": history-search-backward' |
|||
bind '"\e[B": history-search-forward' |
|||
|
|||
# Store the last 10 directories in a history file |
|||
CD_HISTFILE=~/.cd_history |
|||
CD_HISTSIZE=10 |
|||
|
|||
cd() { |
|||
local histfile="${CD_HISTFILE:-$HOME/.cd_history}" |
|||
local max="${CD_HISTSIZE:-10}" |
|||
|
|||
case "$1" in |
|||
--) [ -f "$histfile" ] && tac "$histfile" | nl -w2 -s' ' || echo "No directory history yet."; return ;; |
|||
-[0-9]*) |
|||
local idx=${1#-} |
|||
local dir=$(tac "$histfile" 2>/dev/null | sed -n "${idx}p") |
|||
[ -n "$dir" ] && builtin cd "$dir" || echo "Invalid selection: $1" |
|||
return ;; |
|||
esac |
|||
|
|||
builtin cd "$@" || return |
|||
|
|||
[[ $(tail -n1 "$histfile" 2>/dev/null) != "$PWD" ]] && echo "$PWD" >> "$histfile" |
|||
tail -n "$max" "$histfile" > "${histfile}.tmp" && mv "${histfile}.tmp" "$histfile" |
|||
} |
|||
|
|||
# Manus to LD_LIBRARY_PATH |
|||
export LD_LIBRARY_PATH=$GR00T_WBC_DIR/gr00t_wbc/control/teleop/device/SDKClient_Linux/ManusSDK/lib:$LD_LIBRARY_PATH |
|||
|
|||
# CUDA support |
|||
export LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu:/usr/lib:/lib/x86_64-linux-gnu:/lib64:/lib:$LD_LIBRARY_PATH |
|||
|
|||
# gr00t_wbc aliases |
|||
alias dg="python $GR00T_WBC_DIR/scripts/deploy_g1.py" |
|||
alias rsl="python $GR00T_WBC_DIR/gr00t_wbc/control/main/teleop/run_sim_loop.py" |
|||
alias rgcl="python $GR00T_WBC_DIR/gr00t_wbc/control/main/teleop/run_g1_control_loop.py" |
|||
alias rtpl="python $GR00T_WBC_DIR/gr00t_wbc/control/main/teleop/run_teleop_policy_loop.py" |
|||
alias tgcl="pytest $GR00T_WBC_DIR/tests/control/main/teleop/test_g1_control_loop.py -s" |
|||
@ -0,0 +1,84 @@ |
|||
# Enable mouse mode |
|||
set -g mouse on |
|||
|
|||
# Start window numbering at 0 (default) |
|||
set -g base-index 0 |
|||
setw -g pane-base-index 0 |
|||
|
|||
# Increase scrollback buffer size |
|||
set -g history-limit 50000 |
|||
|
|||
# Use Alt-arrow keys without prefix key to switch panes |
|||
bind -n M-Left select-pane -L |
|||
bind -n M-Right select-pane -R |
|||
bind -n M-Up select-pane -U |
|||
bind -n M-Down select-pane -D |
|||
|
|||
# Use Alt-1,2,3... to switch windows |
|||
bind -n M-1 select-window -t 1 |
|||
bind -n M-2 select-window -t 2 |
|||
bind -n M-3 select-window -t 3 |
|||
bind -n M-4 select-window -t 4 |
|||
bind -n M-5 select-window -t 5 |
|||
bind -n M-6 select-window -t 6 |
|||
bind -n M-7 select-window -t 7 |
|||
bind -n M-8 select-window -t 8 |
|||
bind -n M-9 select-window -t 9 |
|||
|
|||
# Split panes using Alt-| and Alt-- |
|||
bind -n M-| split-window -h |
|||
bind -n M-- split-window -v |
|||
|
|||
# Easy config reload |
|||
bind -n M-r source-file ~/.tmux.conf \; display-message "Config reloaded!" |
|||
|
|||
# Status bar customization |
|||
set -g status-style bg=colour240,fg=colour255 |
|||
set -g status-left "#[fg=colour255,bg=colour240] #S #[fg=colour240,bg=colour238]" |
|||
set -g status-right "#[fg=colour255,bg=colour240] %H:%M #[fg=colour240,bg=colour238]" |
|||
|
|||
# Window status format |
|||
setw -g window-status-format "#[fg=colour255,bg=colour238] #I:#W " |
|||
setw -g window-status-current-format "#[fg=colour238,bg=colour255]#[fg=colour238,bg=colour255] #I:#W #[fg=colour255,bg=colour238]" |
|||
|
|||
# Pane border colors |
|||
set -g pane-border-style fg=colour240 |
|||
set -g pane-active-border-style fg=colour255 |
|||
|
|||
# Message text |
|||
set -g message-style bg=colour238,fg=colour255 |
|||
|
|||
# Clock mode |
|||
setw -g clock-mode-colour colour255 |
|||
|
|||
# Enable focus events |
|||
set -g focus-events on |
|||
|
|||
# Increase escape time |
|||
set -sg escape-time 0 |
|||
|
|||
# Enable true color support |
|||
set -ga terminal-overrides ",*256col*:Tc" |
|||
|
|||
# Set default terminal mode to 256 colors |
|||
set -g default-terminal "screen-256color" |
|||
|
|||
# Display a message when a window is created |
|||
set -g display-time 4000 |
|||
|
|||
# Automatically set window title |
|||
setw -g automatic-rename on |
|||
set -g set-titles on |
|||
set -g set-titles-string "#T" |
|||
|
|||
# Enable clipboard integration |
|||
set -g @plugin 'tmux-plugins/tmux-yank' |
|||
|
|||
# List of plugins |
|||
set -g @plugin 'tmux-plugins/tpm' |
|||
set -g @plugin 'tmux-plugins/tmux-sensible' |
|||
set -g @plugin 'tmux-plugins/tmux-resurrect' |
|||
set -g @plugin 'tmux-plugins/tmux-continuum' |
|||
|
|||
# Initialize TMUX plugin manager (keep this line at the very bottom of tmux.conf) |
|||
run '~/.tmux/plugins/tpm/tpm' |
|||
@ -0,0 +1,5 @@ |
|||
# HIDAPI/libusb |
|||
SUBSYSTEMS=="usb", ATTRS{idVendor}=="3325", MODE:="0666" |
|||
|
|||
# HIDAPI/hidraw |
|||
KERNEL=="hidraw*", ATTRS{idVendor}=="3325", MODE:="0666" |
|||
@ -0,0 +1,127 @@ |
|||
FROM nvgear/ros-2:latest |
|||
|
|||
# Accept build argument for username |
|||
ARG USERNAME |
|||
ARG USERID |
|||
ARG HOME_DIR |
|||
ARG WORKTREE_NAME |
|||
|
|||
# Create user with the same name as host |
|||
RUN if [ "$USERID" != "0" ]; then \ |
|||
useradd -m -u ${USERID} -s /bin/bash ${USERNAME} && \ |
|||
echo "${USERNAME} ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers && \ |
|||
# Add user to video and render groups for GPU access |
|||
usermod -a -G video,render ${USERNAME} || true; \ |
|||
fi |
|||
|
|||
# Copy .bashrc with color settings before switching user |
|||
COPY --chown=${USERNAME}:${USERNAME} docker/.bashrc ${HOME_DIR}/.bashrc |
|||
|
|||
# Install Manus udev rules |
|||
COPY --chown=${USERNAME}:${USERNAME} docker/70-manus-hid.rules /etc/udev/rules.d/70-manus-hid.rules |
|||
|
|||
# Copy tmux configuration |
|||
COPY --chown=${USERNAME}:${USERNAME} docker/.tmux.conf ${HOME_DIR}/.tmux.conf |
|||
|
|||
# Switch to user |
|||
USER ${USERNAME} |
|||
|
|||
# Install tmux plugin manager and uv in parallel |
|||
RUN git clone https://github.com/tmux-plugins/tpm ${HOME_DIR}/.tmux/plugins/tpm & \ |
|||
curl -LsSf https://astral.sh/uv/install.sh | env UV_INSTALL_DIR=${HOME_DIR}/.cargo/bin sh & \ |
|||
wait |
|||
|
|||
# Install tmux plugins automatically |
|||
RUN ${HOME_DIR}/.tmux/plugins/tpm/bin/install_plugins || true |
|||
|
|||
# Add uv to PATH |
|||
ENV PATH="${HOME_DIR}/.cargo/bin:$PATH" |
|||
ENV UV_PYTHON=${HOME_DIR}/venv/bin/python |
|||
|
|||
# Create venv |
|||
RUN uv venv --python 3.10 ${HOME_DIR}/venv |
|||
|
|||
# Install hardware-specific packages (x86 only - not available on ARM64/Orin) |
|||
USER root |
|||
COPY --chown=${USERNAME}:${USERNAME} gr00t_wbc/control/teleop/device/pico/XRoboToolkit_PC_Service_1.0.0_ubuntu_22.04_amd64.deb ${HOME_DIR}/XRoboToolkit_PC_Service_1.0.0_ubuntu_22.04_amd64.deb |
|||
COPY --chown=${USERNAME}:${USERNAME} gr00t_wbc/control/teleop/device/pico/roboticsservice_1.0.0.0_arm64.deb ${HOME_DIR}/roboticsservice_1.0.0.0_arm64.deb |
|||
|
|||
RUN if [ "$(dpkg --print-architecture)" = "amd64" ]; then \ |
|||
# Ultra Leap setup |
|||
wget -qO - https://repo.ultraleap.com/keys/apt/gpg | gpg --dearmor | tee /etc/apt/trusted.gpg.d/ultraleap.gpg && \ |
|||
echo 'deb [arch=amd64] https://repo.ultraleap.com/apt stable main' | tee /etc/apt/sources.list.d/ultraleap.list && \ |
|||
apt-get update && \ |
|||
echo "yes" | DEBIAN_FRONTEND=noninteractive apt-get install -y ultraleap-hand-tracking libhidapi-dev && \ |
|||
# Space Mouse udev rules |
|||
echo 'KERNEL=="hidraw*", SUBSYSTEM=="hidraw", MODE="0664", GROUP="plugdev"' > /etc/udev/rules.d/99-hidraw-permissions.rules && \ |
|||
usermod -aG plugdev ${USERNAME}; \ |
|||
# Pico setup |
|||
apt-get install -y xdg-utils && \ |
|||
dpkg -i ${HOME_DIR}/XRoboToolkit_PC_Service_1.0.0_ubuntu_22.04_amd64.deb; \ |
|||
else \ |
|||
echo "Skipping x86-only hardware packages on $(dpkg --print-architecture)"; \ |
|||
fi |
|||
|
|||
USER ${USERNAME} |
|||
# Install hardware Python packages (x86 only) with caching |
|||
RUN --mount=type=cache,target=${HOME_DIR}/.cache/uv,uid=${USERID},gid=${USERID} \ |
|||
if [ "$(dpkg --print-architecture)" = "amd64" ]; then \ |
|||
# Ultra Leap Python bindings |
|||
git clone https://github.com/ultraleap/leapc-python-bindings ${HOME_DIR}/leapc-python-bindings && \ |
|||
cd ${HOME_DIR}/leapc-python-bindings && \ |
|||
UV_CONCURRENT_DOWNLOADS=8 uv pip install -r requirements.txt && \ |
|||
MAKEFLAGS="-j$(nproc)" ${HOME_DIR}/venv/bin/python -m build leapc-cffi && \ |
|||
uv pip install leapc-cffi/dist/leapc_cffi-0.0.1.tar.gz && \ |
|||
uv pip install -e leapc-python-api && \ |
|||
# Space Mouse Python package |
|||
uv pip install pyspacemouse && \ |
|||
# Pico Python bindings |
|||
git clone https://github.com/XR-Robotics/XRoboToolkit-PC-Service-Pybind.git ${HOME_DIR}/XRoboToolkit-PC-Service-Pybind && \ |
|||
cd ${HOME_DIR}/XRoboToolkit-PC-Service-Pybind && \ |
|||
uv pip install setuptools pybind11 && \ |
|||
sed -i "s|pip install|uv pip install|g" setup_ubuntu.sh && \ |
|||
sed -i "s|pip uninstall|uv pip uninstall|g" setup_ubuntu.sh && \ |
|||
sed -i "s|python setup.py install|${HOME_DIR}/venv/bin/python setup.py install|g" setup_ubuntu.sh && \ |
|||
bash setup_ubuntu.sh; \ |
|||
fi |
|||
|
|||
# Install Python dependencies using uv with caching |
|||
RUN --mount=type=cache,target=${HOME_DIR}/.cache/uv,uid=${USERID},gid=${USERID} \ |
|||
UV_CONCURRENT_DOWNLOADS=8 uv pip install --upgrade pip ipython jupyter notebook debugpy |
|||
|
|||
|
|||
# Copy entire project to the workspace directory where it will be mounted at runtime |
|||
# NOTE: The build context must be the project root for this to work |
|||
# Use dynamic worktree name to match runtime mount path |
|||
COPY --chown=${USERNAME}:${USERNAME} . ${HOME_DIR}/Projects/${WORKTREE_NAME} |
|||
|
|||
# Install Python dependencies inside the venv with caching - split into separate commands |
|||
RUN --mount=type=cache,target=${HOME_DIR}/.cache/uv,uid=${USERID},gid=${USERID} \ |
|||
UV_CONCURRENT_DOWNLOADS=8 uv pip install \ |
|||
-e ${HOME_DIR}/Projects/${WORKTREE_NAME}/external_dependencies/unitree_sdk2_python |
|||
|
|||
# Unlike pip, uv downloads LFS files by default. There's a bug in uv that causes LFS files |
|||
# to fail to download (https://github.com/astral-sh/uv/issues/3312). So we need to set |
|||
# UV_GIT_LFS=1 to prevent uv from downloading LFS files. |
|||
# Install project dependencies (VLA extras only on x86) with caching |
|||
RUN --mount=type=cache,target=${HOME_DIR}/.cache/uv,uid=${USERID},gid=${USERID} \ |
|||
GIT_LFS_SKIP_SMUDGE=1 UV_CONCURRENT_DOWNLOADS=8 uv pip install -e "${HOME_DIR}/Projects/${WORKTREE_NAME}[dev]" |
|||
|
|||
# Clone and install robosuite with specific branch |
|||
RUN --mount=type=cache,target=${HOME_DIR}/.cache/uv,uid=${USERID},gid=${USERID} \ |
|||
git clone https://github.com/xieleo5/robosuite.git ${HOME_DIR}/robosuite && \ |
|||
cd ${HOME_DIR}/robosuite && \ |
|||
git checkout leo/support_g1_locomanip && \ |
|||
UV_CONCURRENT_DOWNLOADS=8 uv pip install -e . |
|||
|
|||
# Install gr00trobocasa |
|||
RUN --mount=type=cache,target=${HOME_DIR}/.cache/uv,uid=${USERID},gid=${USERID} \ |
|||
UV_CONCURRENT_DOWNLOADS=8 uv pip install -e ${HOME_DIR}/Projects/${WORKTREE_NAME}/gr00t_wbc/dexmg/gr00trobocasa |
|||
|
|||
# Configure bash environment with virtual environment and ROS2 setup |
|||
RUN echo "source ${HOME_DIR}/venv/bin/activate" >> ${HOME_DIR}/.bashrc && \ |
|||
echo "source /opt/ros/humble/setup.bash" >> ${HOME_DIR}/.bashrc && \ |
|||
echo "export ROS_LOCALHOST_ONLY=1" >> ${HOME_DIR}/.bashrc |
|||
|
|||
# Default command (can be overridden at runtime) |
|||
CMD ["/bin/bash"] |
|||
@ -0,0 +1,155 @@ |
|||
# Multi-architecture Dockerfile for NVIDIA CUDA |
|||
# Supports linux/amd64 and linux/arm64 |
|||
FROM nvidia/cuda:12.4.0-runtime-ubuntu22.04 |
|||
|
|||
# Build info - ARGs need to be redeclared after FROM to use in RUN commands |
|||
ARG TARGETPLATFORM |
|||
ARG BUILDPLATFORM |
|||
RUN echo "Building for $TARGETPLATFORM on $BUILDPLATFORM" |
|||
|
|||
# Avoid prompts from apt |
|||
ENV DEBIAN_FRONTEND=noninteractive |
|||
|
|||
# Install minimal system dependencies |
|||
RUN apt-get update && \ |
|||
apt-get install -y \ |
|||
# Basic tools |
|||
build-essential \ |
|||
curl \ |
|||
gdb \ |
|||
git \ |
|||
git-lfs \ |
|||
net-tools \ |
|||
sudo \ |
|||
wget \ |
|||
iputils-ping \ |
|||
vim \ |
|||
unzip \ |
|||
# System services |
|||
udev \ |
|||
# Graphics and X11 |
|||
libgl1-mesa-dri \ |
|||
libgl1-mesa-glx \ |
|||
libglu1-mesa \ |
|||
mesa-utils \ |
|||
libxcb-cursor0 \ |
|||
x11-apps \ |
|||
xauth \ |
|||
# EGL and GPU access |
|||
libegl1 \ |
|||
libegl1-mesa \ |
|||
libegl1-mesa-dev \ |
|||
libgl1-mesa-dev \ |
|||
libgles2-mesa-dev \ |
|||
libglvnd-dev \ |
|||
mesa-common-dev \ |
|||
# XCB and Qt platform dependencies |
|||
libxcb-icccm4 \ |
|||
libxcb-image0 \ |
|||
libxcb-keysyms1 \ |
|||
libxcb-randr0 \ |
|||
libxcb-render-util0 \ |
|||
libxcb-shape0 \ |
|||
libxcb-xfixes0 \ |
|||
libxcb-xinerama0 \ |
|||
libxcb-xinput0 \ |
|||
libxcb-xkb1 \ |
|||
libxkbcommon-x11-0 \ |
|||
# D-Bus and system dependencies |
|||
libdbus-1-3 \ |
|||
# Other dependencies |
|||
libncurses5-dev \ |
|||
libudev-dev \ |
|||
libusb-1.0-0-dev \ |
|||
# Python 3.10 and pip |
|||
python3.10 \ |
|||
python3.10-venv \ |
|||
python3.10-distutils \ |
|||
python3-pip \ |
|||
expect \ |
|||
# ffmpeg and related libraries |
|||
ffmpeg \ |
|||
libavcodec-dev \ |
|||
libavformat-dev \ |
|||
libavdevice-dev \ |
|||
libavfilter-dev \ |
|||
libavutil-dev \ |
|||
libswresample-dev \ |
|||
libswscale-dev \ |
|||
# opencv |
|||
libgtk2.0-dev \ |
|||
# Clean up |
|||
&& rm -rf /var/lib/apt/lists/* |
|||
|
|||
# --- Install ROS 2 Humble (following official instructions) --- |
|||
# Enable required repositories |
|||
RUN apt-get update && apt-get install -y software-properties-common \ |
|||
&& add-apt-repository universe \ |
|||
# Add ROS 2 GPG key and repository |
|||
&& apt-get install -y curl gnupg lsb-release \ |
|||
&& curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg \ |
|||
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(. /etc/os-release && echo $UBUNTU_CODENAME) main" > /etc/apt/sources.list.d/ros2.list \ |
|||
# Upgrade system to avoid removal of critical packages (see ROS 2 docs) |
|||
&& apt-get update \ |
|||
&& apt-get upgrade -y \ |
|||
# Install ROS 2 Humble desktop |
|||
&& apt-get install -y ros-humble-desktop \ |
|||
# (Optional) Install development tools |
|||
&& apt-get install -y ros-dev-tools \ |
|||
# Install Eclipse Cyclone DDS RMW implementation |
|||
&& apt-get install -y ros-humble-rmw-cyclonedds-cpp \ |
|||
# Clean up |
|||
&& rm -rf /var/lib/apt/lists/* |
|||
|
|||
# Source ROS 2 setup in bashrc for all users |
|||
RUN echo 'source /opt/ros/humble/setup.bash' >> /etc/bash.bashrc |
|||
|
|||
# Clone, build, and install CycloneDDS 0.10.x |
|||
RUN git clone --branch releases/0.10.x https://github.com/eclipse-cyclonedds/cyclonedds /opt/cyclonedds && \ |
|||
mkdir -p /opt/cyclonedds/build /opt/cyclonedds/install && \ |
|||
cd /opt/cyclonedds/build && \ |
|||
cmake .. -DCMAKE_INSTALL_PREFIX=../install && \ |
|||
cmake --build . --target install && \ |
|||
# Clean up build files to reduce image size |
|||
rm -rf /opt/cyclonedds/build |
|||
|
|||
# Set CYCLONEDDS_HOME for all users |
|||
ENV CYCLONEDDS_HOME=/opt/cyclonedds/install |
|||
|
|||
# Install uv |
|||
RUN curl -LsSf https://astral.sh/uv/install.sh | env UV_INSTALL_DIR=/opt/uv sh |
|||
|
|||
# Add uv to PATH |
|||
ENV PATH="/opt/uv:$PATH" |
|||
ENV UV_PYTHON=/opt/venv/bin/python |
|||
|
|||
# Fix dpkg state and install tmux |
|||
RUN dpkg --configure -a && apt-get update && apt-get install -y tmux && rm -rf /var/lib/apt/lists/* |
|||
|
|||
# Create NVIDIA ICD config for EGL if it doesn't exist |
|||
# Note: This might need platform-specific handling for ARM64 |
|||
RUN if [ ! -f /usr/share/glvnd/egl_vendor.d/10_nvidia.json ]; then \ |
|||
mkdir -p /usr/share/glvnd/egl_vendor.d && \ |
|||
printf '{\n "file_format_version" : "1.0.0",\n "ICD" : {\n "library_path" : "libEGL_nvidia.so.0"\n }\n}' | tee /usr/share/glvnd/egl_vendor.d/10_nvidia.json > /dev/null; \ |
|||
fi |
|||
|
|||
# Platform-specific configurations |
|||
RUN case "$TARGETPLATFORM" in \ |
|||
"linux/arm64") \ |
|||
echo "Configuring for ARM64 platform" && \ |
|||
# Add any ARM64-specific configurations here |
|||
echo "export GPU_FORCE_64BIT_PTR=1" >> /etc/environment \ |
|||
;; \ |
|||
"linux/amd64") \ |
|||
echo "Configuring for AMD64 platform" && \ |
|||
# Add any AMD64-specific configurations here |
|||
echo "AMD64 platform configured" \ |
|||
;; \ |
|||
*) \ |
|||
echo "Unknown platform: $TARGETPLATFORM" \ |
|||
;; \ |
|||
esac |
|||
|
|||
# Add labels for better image management |
|||
LABEL org.opencontainers.image.title="Multi-Arch CUDA Runtime with ROS 2 Humble" |
|||
LABEL org.opencontainers.image.description="Multi-architecture Docker image with CUDA runtime and ROS 2 Humble" |
|||
@ -0,0 +1,55 @@ |
|||
#!/bin/bash |
|||
|
|||
# Multi-architecture Docker build script |
|||
# Supports linux/amd64 |
|||
|
|||
set -e |
|||
|
|||
# Configuration |
|||
IMAGE_NAME="nvgear/ros-2" |
|||
TAG="${1:-latest}" |
|||
DOCKERFILE="docker/Dockerfile.deploy.base" |
|||
|
|||
# Colors for output |
|||
RED='\033[0;31m' |
|||
GREEN='\033[0;32m' |
|||
YELLOW='\033[1;33m' |
|||
NC='\033[0m' # No Color |
|||
|
|||
echo -e "${GREEN}Building multi-architecture Docker image: ${IMAGE_NAME}:${TAG}${NC}" |
|||
|
|||
# Ensure we're using the multiarch builder |
|||
echo -e "${YELLOW}Setting up multiarch builder...${NC}" |
|||
sudo docker buildx use multiarch-builder 2>/dev/null || { |
|||
echo -e "${YELLOW}Creating multiarch builder...${NC}" |
|||
sudo docker buildx create --name multiarch-builder --use --bootstrap |
|||
} |
|||
|
|||
# Show supported platforms |
|||
echo -e "${YELLOW}Supported platforms:${NC}" |
|||
sudo docker buildx inspect --bootstrap | grep Platforms |
|||
|
|||
# Build for multiple architectures |
|||
echo -e "${GREEN}Starting multi-arch build...${NC}" |
|||
sudo docker buildx build \ |
|||
--platform linux/amd64 \ |
|||
--file "${DOCKERFILE}" \ |
|||
--tag "${IMAGE_NAME}:${TAG}" \ |
|||
--push \ |
|||
. |
|||
|
|||
# Alternative: Build and load locally (only works for single platform) |
|||
# docker buildx build \ |
|||
# --platform linux/amd64 \ |
|||
# --file "${DOCKERFILE}" \ |
|||
# --tag "${IMAGE_NAME}:${TAG}" \ |
|||
# --load \ |
|||
# . |
|||
|
|||
echo -e "${GREEN}Multi-arch build completed successfully!${NC}" |
|||
echo -e "${GREEN}Image: ${IMAGE_NAME}:${TAG}${NC}" |
|||
echo -e "${GREEN}Platforms: linux/amd64${NC}" |
|||
|
|||
# Verify the manifest |
|||
echo -e "${YELLOW}Verifying multi-arch manifest...${NC}" |
|||
sudo docker buildx imagetools inspect "${IMAGE_NAME}:${TAG}" |
|||
@ -0,0 +1,5 @@ |
|||
#!/bin/bash |
|||
set -e # Exit on error |
|||
|
|||
echo "Dependencies installed successfully. Starting interactive bash shell..." |
|||
exec /bin/bash |
|||
@ -0,0 +1,20 @@ |
|||
#!/bin/bash |
|||
set -e # Exit on error |
|||
|
|||
# Run the deployment script |
|||
# Check for script existence before running |
|||
DEPLOY_SCRIPT="scripts/deploy_g1.py" |
|||
if [ -f "$DEPLOY_SCRIPT" ]; then |
|||
echo "Running deployment script at $DEPLOY_SCRIPT" |
|||
echo "Using python from $(which python)" |
|||
echo "Deploy args: $@" |
|||
exec python "$DEPLOY_SCRIPT" "$@" |
|||
else |
|||
echo "ERROR: Deployment script not found at $DEPLOY_SCRIPT" |
|||
echo "Current directory structure:" |
|||
find . -type f -name "*.py" | grep -i deploy |
|||
echo "Available script options:" |
|||
find . -type f -name "*.py" | sort |
|||
echo "Starting a bash shell for troubleshooting..." |
|||
exec /bin/bash |
|||
fi |
|||
@ -0,0 +1,23 @@ |
|||
#!/bin/bash |
|||
set -e |
|||
|
|||
# Source virtual environment and ROS2 |
|||
source ${HOME}/venv/bin/activate |
|||
source /opt/ros/humble/setup.bash |
|||
export ROS_LOCALHOST_ONLY=1 |
|||
|
|||
# Install external dependencies |
|||
echo "Current directory: $(pwd)" |
|||
echo "Installing dependencies..." |
|||
|
|||
# Install Unitree SDK and LeRobot |
|||
if [ -d "external_dependencies/unitree_sdk2_python" ]; then |
|||
cd external_dependencies/unitree_sdk2_python/ |
|||
uv pip install -e . --no-deps |
|||
cd ../.. |
|||
fi |
|||
|
|||
# Install project |
|||
if [ -f "pyproject.toml" ]; then |
|||
UV_GIT_LFS=1 uv pip install -e ".[dev]" |
|||
fi |
|||
@ -0,0 +1 @@ |
|||
nvcr.io/nvidian/gr00t_wbc:base |
|||
@ -0,0 +1,79 @@ |
|||
#!/bin/bash |
|||
|
|||
# Script to kill all running Docker containers |
|||
# Usage: ./kill_all_containers.sh [--force] |
|||
|
|||
set -e |
|||
|
|||
# Colors for output |
|||
RED='\033[0;31m' |
|||
GREEN='\033[0;32m' |
|||
YELLOW='\033[1;33m' |
|||
NC='\033[0m' # No Color |
|||
|
|||
# Function to print colored output |
|||
print_info() { |
|||
echo -e "${GREEN}[INFO]${NC} $1" |
|||
} |
|||
|
|||
print_warning() { |
|||
echo -e "${YELLOW}[WARNING]${NC} $1" |
|||
} |
|||
|
|||
print_error() { |
|||
echo -e "${RED}[ERROR]${NC} $1" |
|||
} |
|||
|
|||
# Check if Docker is running |
|||
if ! sudo docker info >/dev/null 2>&1; then |
|||
print_error "Docker is not running or not accessible. Please start Docker first." |
|||
exit 1 |
|||
fi |
|||
|
|||
# Get list of running containers |
|||
RUNNING_CONTAINERS=$(sudo docker ps -q) |
|||
|
|||
if [ -z "$RUNNING_CONTAINERS" ]; then |
|||
print_info "No running containers found." |
|||
exit 0 |
|||
fi |
|||
|
|||
# Count running containers |
|||
CONTAINER_COUNT=$(echo "$RUNNING_CONTAINERS" | wc -l | tr -d ' ') |
|||
|
|||
print_info "Found $CONTAINER_COUNT running container(s):" |
|||
sudo docker ps --format "table {{.ID}}\t{{.Image}}\t{{.Names}}\t{{.Status}}" |
|||
|
|||
# Check for --force flag |
|||
FORCE_KILL=false |
|||
if [ "$1" = "--force" ]; then |
|||
FORCE_KILL=true |
|||
print_warning "Force mode enabled. Containers will be killed without confirmation." |
|||
fi |
|||
|
|||
# Ask for confirmation unless --force is used |
|||
if [ "$FORCE_KILL" = false ]; then |
|||
echo |
|||
read -p "Are you sure you want to kill all running containers? (y/N): " -n 1 -r |
|||
echo |
|||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then |
|||
print_info "Operation cancelled." |
|||
exit 0 |
|||
fi |
|||
fi |
|||
|
|||
# Kill all running containers |
|||
print_info "Killing all running containers..." |
|||
if sudo docker kill $RUNNING_CONTAINERS; then |
|||
print_info "Successfully killed all running containers." |
|||
else |
|||
print_error "Failed to kill some containers. You may need to run with sudo or check Docker permissions." |
|||
exit 1 |
|||
fi |
|||
|
|||
# Optional: Remove stopped containers (commented out by default) |
|||
# Uncomment the following lines if you also want to remove the stopped containers |
|||
# print_info "Removing stopped containers..." |
|||
# sudo docker container prune -f |
|||
|
|||
print_info "Done!" |
|||
@ -0,0 +1,192 @@ |
|||
#!/bin/bash |
|||
|
|||
# kill_gr00t_wbc_processors.sh |
|||
# Kill gr00t_wbc processes in current container to prevent message passing conflicts |
|||
|
|||
# Note: Don't use 'set -e' as tmux/pgrep commands may return non-zero exit codes |
|||
|
|||
# Configuration |
|||
DRY_RUN=false |
|||
FORCE=false |
|||
QUIET=false |
|||
declare -A FOUND_PROCESSES |
|||
|
|||
# Default to verbose mode if no arguments |
|||
[[ $# -eq 0 ]] && { QUIET=false; DRY_RUN=false; } |
|||
|
|||
# Parse arguments |
|||
while [[ $# -gt 0 ]]; do |
|||
case $1 in |
|||
--dry-run) DRY_RUN=true ;; |
|||
--force) FORCE=true ;; |
|||
--verbose|-v) VERBOSE=true ;; |
|||
--help|-h) |
|||
echo "Usage: $0 [--dry-run] [--force] [--verbose] [--help]" |
|||
echo "Kill gr00t_wbc processes to prevent message passing conflicts" |
|||
exit 0 ;; |
|||
*) echo "Unknown option: $1"; exit 1 ;; |
|||
esac |
|||
shift |
|||
done |
|||
|
|||
# Colors (only if not quiet) |
|||
if [[ "$QUIET" != true ]]; then |
|||
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m' |
|||
else |
|||
RED=''; GREEN=''; YELLOW=''; BLUE=''; NC='' |
|||
fi |
|||
|
|||
# Show processes by pattern (for preview) |
|||
show_processes_by_pattern() { |
|||
local pattern="$1" desc="$2" |
|||
local pids=$(pgrep -f "$pattern" 2>/dev/null || true) |
|||
|
|||
[[ -z "$pids" ]] && return 0 |
|||
|
|||
echo -e "${YELLOW}$desc processes:${NC}" |
|||
|
|||
for pid in $pids; do |
|||
local cmd=$(ps -p $pid -o cmd= 2>/dev/null || echo "Process not found") |
|||
echo " PID $pid: $cmd" |
|||
done |
|||
} |
|||
|
|||
# Kill processes by pattern (silent killing) |
|||
kill_by_pattern() { |
|||
local pattern="$1" desc="$2" signal="${3:-TERM}" |
|||
local pids=$(pgrep -f "$pattern" 2>/dev/null || true) |
|||
|
|||
[[ -z "$pids" ]] && return 0 |
|||
|
|||
for pid in $pids; do |
|||
# Kill if not dry run |
|||
[[ "$DRY_RUN" != true ]] && kill -$signal $pid 2>/dev/null |
|||
done |
|||
} |
|||
|
|||
# Show tmux sessions (for preview) |
|||
show_tmux() { |
|||
local pattern="$1" |
|||
local sessions=$(tmux list-sessions 2>/dev/null | grep "$pattern" | cut -d: -f1 || true) |
|||
|
|||
[[ -z "$sessions" ]] && return 0 |
|||
|
|||
echo -e "${YELLOW}Tmux sessions:${NC}" |
|||
|
|||
for session in $sessions; do |
|||
echo " Session: $session" |
|||
done |
|||
} |
|||
|
|||
# Kill tmux sessions (silent killing) |
|||
kill_tmux() { |
|||
local pattern="$1" |
|||
local sessions=$(tmux list-sessions 2>/dev/null | grep "$pattern" | cut -d: -f1 || true) |
|||
|
|||
[[ -z "$sessions" ]] && return 0 |
|||
|
|||
for session in $sessions; do |
|||
[[ "$DRY_RUN" != true ]] && tmux kill-session -t "$session" 2>/dev/null |
|||
done |
|||
} |
|||
|
|||
# Show processes by port (for preview) |
|||
show_processes_by_port() { |
|||
local port="$1" desc="$2" |
|||
local pids=$(lsof -ti:$port 2>/dev/null || true) |
|||
|
|||
[[ -z "$pids" ]] && return 0 |
|||
|
|||
echo -e "${YELLOW}$desc (port $port):${NC}" |
|||
|
|||
for pid in $pids; do |
|||
local cmd=$(ps -p $pid -o cmd= 2>/dev/null || echo "Process not found") |
|||
echo " PID $pid: $cmd" |
|||
done |
|||
} |
|||
|
|||
# Kill processes by port (silent killing) |
|||
kill_by_port() { |
|||
local port="$1" desc="$2" |
|||
local pids=$(lsof -ti:$port 2>/dev/null || true) |
|||
|
|||
[[ -z "$pids" ]] && return 0 |
|||
|
|||
for pid in $pids; do |
|||
[[ "$DRY_RUN" != true ]] && kill -TERM $pid 2>/dev/null |
|||
done |
|||
} |
|||
|
|||
# Check if any processes exist |
|||
has_processes() { |
|||
# Check for processes |
|||
local has_tmux=$(tmux list-sessions 2>/dev/null | grep "g1_deployment" || true) |
|||
local has_control=$(pgrep -f "run_g1_control_loop.py" 2>/dev/null || true) |
|||
local has_teleop=$(pgrep -f "run_teleop_policy_loop.py" 2>/dev/null || true) |
|||
local has_camera=$(pgrep -f "camera_forwarder.py" 2>/dev/null || true) |
|||
local has_rqt=$(pgrep -f "rqt.*image_view" 2>/dev/null || true) |
|||
local has_port=$(lsof -ti:5555 2>/dev/null || true) |
|||
|
|||
[[ -n "$has_tmux" || -n "$has_control" || -n "$has_teleop" || -n "$has_camera" || -n "$has_rqt" || -n "$has_port" ]] |
|||
} |
|||
|
|||
# Main execution |
|||
main() { |
|||
# Check if any processes exist first |
|||
if ! has_processes; then |
|||
# No processes to kill, exit silently |
|||
exit 0 |
|||
fi |
|||
|
|||
# Show header and processes to be killed |
|||
if [[ "$QUIET" != true ]]; then |
|||
echo -e "${BLUE}=== gr00t_wbc Process Killer ===${NC}" |
|||
[[ "$DRY_RUN" == true ]] && echo -e "${BLUE}=== DRY RUN MODE ===${NC}" |
|||
|
|||
# Show what will be killed |
|||
show_tmux "g1_deployment" |
|||
show_processes_by_pattern "run_g1_control_loop.py" "G1 control loop" |
|||
show_processes_by_pattern "run_teleop_policy_loop.py" "Teleop policy" |
|||
show_processes_by_pattern "camera_forwarder.py" "Camera forwarder" |
|||
show_processes_by_pattern "rqt.*image_view" "RQT viewer" |
|||
show_processes_by_port "5555" "Inference server" |
|||
|
|||
# Ask for confirmation |
|||
if [[ "$FORCE" != true && "$DRY_RUN" != true ]]; then |
|||
echo |
|||
echo -e "${RED}WARNING: This will terminate the above gr00t_wbc processes!${NC}" |
|||
read -p "Continue? [Y/n]: " -n 1 -r |
|||
echo |
|||
# Default to Y - only abort if user explicitly types 'n' or 'N' |
|||
[[ $REPLY =~ ^[Nn]$ ]] && { echo "Aborted."; exit 0; } |
|||
fi |
|||
echo |
|||
fi |
|||
|
|||
# Kill processes (silently) |
|||
kill_tmux "g1_deployment" |
|||
kill_by_pattern "run_g1_control_loop.py" "G1 control loop" |
|||
kill_by_pattern "run_teleop_policy_loop.py" "Teleop policy" |
|||
kill_by_pattern "camera_forwarder.py" "Camera forwarder" |
|||
kill_by_pattern "rqt.*image_view" "RQT viewer" |
|||
kill_by_port "5555" "Inference server" |
|||
|
|||
# Force kill remaining (SIGKILL) |
|||
[[ "$DRY_RUN" != true ]] && { |
|||
sleep 1 |
|||
kill_by_pattern "run_g1_control_loop.py" "G1 control loop" "KILL" |
|||
kill_by_pattern "run_teleop_policy_loop.py" "Teleop policy" "KILL" |
|||
kill_by_pattern "camera_forwarder.py" "Camera forwarder" "KILL" |
|||
} |
|||
|
|||
# Summary (unless quiet) |
|||
[[ "$QUIET" != true ]] && { |
|||
if [[ "$DRY_RUN" == true ]]; then |
|||
echo -e "${BLUE}=== DRY RUN COMPLETE ===${NC}" |
|||
else |
|||
echo -e "${GREEN}All gr00t_wbc processes terminated${NC}" |
|||
fi |
|||
} |
|||
} |
|||
|
|||
main "$@" |
|||
@ -0,0 +1,3 @@ |
|||
#!/bin/bash |
|||
image_name=$(cat image_name.txt) |
|||
docker push "$@" $image_name |
|||
@ -0,0 +1,454 @@ |
|||
#!/bin/bash |
|||
|
|||
# Docker run script for gr00t_wbc with branch-based container isolation |
|||
# |
|||
# Usage: |
|||
# ./docker/run_docker.sh [OPTIONS] |
|||
# |
|||
# Options: |
|||
# --build Build Docker image |
|||
# --clean Clean containers |
|||
# --deploy Run in deploy mode |
|||
# --install Pull prebuilt Docker image |
|||
# --push Push built image to Docker Hub |
|||
# --branch Use branch-specific container names |
|||
# |
|||
# Branch-based Container Isolation (when --branch flag is used): |
|||
# - Each git branch gets its own isolated containers |
|||
# - Container names include branch identifier (e.g., gr00t_wbc-deploy-user-main) |
|||
# - Works with git worktrees, separate clones, or nested repositories |
|||
# - Clean and build operations only affect the current branch |
|||
|
|||
# Exit on error |
|||
set -e |
|||
|
|||
# Default values |
|||
BUILD=false |
|||
CLEAN=false |
|||
DEPLOY=false |
|||
INSTALL=false |
|||
# Flag to push the built Docker image to Docker Hub |
|||
# This should be used when someone updates the Docker image dependencies |
|||
# because this image is used for CI/CD pipelines |
|||
# When true, the image will be tagged and pushed to docker.io/nvgear/gr00t_wbc:latest (lowercased in practice) |
|||
DOCKER_HUB_PUSH=false |
|||
# Flag to build the docker with root user |
|||
# This could cause some of your local files to be owned by root |
|||
# If you get error like "PermissionError: [Errno 13] Permission denied:" |
|||
# You can run `sudo chown -R $USER:$USER .` in local machine to fix it |
|||
ROOT=false |
|||
BRANCH_MODE=false |
|||
EXTRA_ARGS=() |
|||
PROJECT_NAME="gr00t_wbc" |
|||
PROJECT_SLUG=$(echo "$PROJECT_NAME" | tr '[:upper:]' '[:lower:]') |
|||
REMOTE_IMAGE="nvgear/${PROJECT_SLUG}:latest" |
|||
|
|||
# Parse command line arguments |
|||
while [[ $# -gt 0 ]]; do |
|||
case $1 in |
|||
--build) |
|||
BUILD=true |
|||
shift |
|||
;; |
|||
--clean) |
|||
CLEAN=true |
|||
shift |
|||
;; |
|||
--deploy) |
|||
DEPLOY=true |
|||
shift |
|||
;; |
|||
--install) |
|||
INSTALL=true |
|||
shift |
|||
;; |
|||
--push) |
|||
DOCKER_HUB_PUSH=true |
|||
shift |
|||
;; |
|||
--root) |
|||
ROOT=true |
|||
shift |
|||
;; |
|||
--branch) |
|||
BRANCH_MODE=true |
|||
shift |
|||
;; |
|||
*) |
|||
# Collect all unknown arguments as extra args for the deployment script |
|||
EXTRA_ARGS+=("$1") |
|||
shift |
|||
;; |
|||
esac |
|||
done |
|||
|
|||
if [ "$INSTALL" = true ] && [ "$BUILD" = true ]; then |
|||
echo "Cannot use --install and --build together. Choose one." |
|||
exit 1 |
|||
fi |
|||
|
|||
|
|||
# Function to get branch name for container naming |
|||
function get_branch_id { |
|||
# Check if we're in a git repository |
|||
if git rev-parse --is-inside-work-tree > /dev/null 2>&1; then |
|||
# Get current branch name (returns "HEAD" in detached state) |
|||
local branch_name=$(git rev-parse --abbrev-ref HEAD) |
|||
# Replace forward slashes with dashes for valid container names |
|||
echo "${branch_name//\//-}" |
|||
else |
|||
# Default: no branch identifier (not in git repo) |
|||
echo "" |
|||
fi |
|||
} |
|||
|
|||
# Architecture detection helpers |
|||
is_arm64() { [ "$(dpkg --print-architecture)" = "arm64" ]; } |
|||
is_amd64() { [ "$(dpkg --print-architecture)" = "amd64" ]; } |
|||
|
|||
# Get current user's username and UID |
|||
if [ "$ROOT" = true ]; then |
|||
USERNAME=root |
|||
USERID=0 |
|||
DOCKER_HOME_DIR=/root |
|||
CACHE_FROM=${PROJECT_SLUG}-deploy-cache-root |
|||
else |
|||
USERNAME=$(whoami) |
|||
USERID=$(id -u) |
|||
DOCKER_HOME_DIR=/home/${USERNAME} |
|||
CACHE_FROM=${PROJECT_SLUG}-deploy-cache |
|||
fi |
|||
# Get input group ID for device access |
|||
INPUT_GID=$(getent group input | cut -d: -f3) |
|||
|
|||
# Get script directory for path calculations |
|||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" |
|||
|
|||
# Function to get the actual project directory (worktree-aware) |
|||
function get_project_dir { |
|||
# For worktrees, use the actual worktree root path |
|||
if git rev-parse --is-inside-work-tree > /dev/null 2>&1; then |
|||
git rev-parse --show-toplevel |
|||
else |
|||
# Fallback to script-based detection |
|||
dirname "$SCRIPT_DIR" |
|||
fi |
|||
} |
|||
|
|||
# Get branch identifier |
|||
BRANCH_ID=$(get_branch_id) |
|||
|
|||
# Set project directory (needs to be after branch detection) |
|||
PROJECT_DIR="$(get_project_dir)" |
|||
|
|||
# Function to generate container name with optional branch support |
|||
function get_container_name { |
|||
local container_type="$1" |
|||
if [[ -n "$BRANCH_ID" ]] && [[ "$BRANCH_MODE" = true ]]; then |
|||
echo "${PROJECT_SLUG}-${container_type}-${USERNAME}-${BRANCH_ID}" |
|||
else |
|||
echo "${PROJECT_SLUG}-${container_type}-${USERNAME}" |
|||
fi |
|||
} |
|||
|
|||
# Set common variables used throughout the script |
|||
DEPLOY_CONTAINER=$(get_container_name "deploy") |
|||
BASH_CONTAINER=$(get_container_name "bash") |
|||
WORKTREE_NAME=$(basename "$PROJECT_DIR") |
|||
|
|||
# Debug output for branch detection |
|||
if [[ -n "$BRANCH_ID" ]] && [[ "$BRANCH_MODE" = true ]]; then |
|||
echo "Branch mode enabled - using branch: $BRANCH_ID" |
|||
echo "Project directory: $PROJECT_DIR" |
|||
elif [[ -n "$BRANCH_ID" ]]; then |
|||
echo "Branch mode disabled - using default containers" |
|||
echo "Project directory: $PROJECT_DIR" |
|||
else |
|||
echo "Running outside git repository" |
|||
echo "Project directory: $PROJECT_DIR" |
|||
fi |
|||
|
|||
# Get host's hostname and append -docker |
|||
HOSTNAME=$(hostname)-docker |
|||
|
|||
function clean_container { |
|||
echo "Cleaning up Docker containers..." |
|||
|
|||
# Stop containers |
|||
sudo docker stop $DEPLOY_CONTAINER 2>/dev/null || true |
|||
sudo docker stop $BASH_CONTAINER 2>/dev/null || true |
|||
# Remove containers |
|||
echo "Removing containers..." |
|||
sudo docker rm $DEPLOY_CONTAINER 2>/dev/null || true |
|||
sudo docker rm $BASH_CONTAINER 2>/dev/null || true |
|||
echo "Containers cleaned!" |
|||
} |
|||
|
|||
|
|||
# Function to install Docker Buildx if needed |
|||
function install_docker_buildx { |
|||
# Check if Docker Buildx is already installed |
|||
if sudo docker buildx version &> /dev/null; then |
|||
echo "Docker Buildx is already installed." |
|||
return 0 |
|||
fi |
|||
|
|||
echo "Installing Docker Buildx..." |
|||
|
|||
# Create directories and detect architecture |
|||
mkdir -p ~/.docker/cli-plugins/ && sudo mkdir -p /root/.docker/cli-plugins/ |
|||
ARCH=$(dpkg --print-architecture) |
|||
[[ "$ARCH" == "arm64" ]] && BUILDX_ARCH="linux-arm64" || BUILDX_ARCH="linux-amd64" |
|||
|
|||
# Get version (with fallback) |
|||
BUILDX_VERSION=$(curl -s https://api.github.com/repos/docker/buildx/releases/latest | grep tag_name | cut -d '"' -f 4) |
|||
BUILDX_VERSION=${BUILDX_VERSION:-v0.13.1} |
|||
|
|||
# Download and install for both user and root |
|||
curl -L "https://github.com/docker/buildx/releases/download/${BUILDX_VERSION}/buildx-${BUILDX_VERSION}.${BUILDX_ARCH}" -o ~/.docker/cli-plugins/docker-buildx |
|||
sudo cp ~/.docker/cli-plugins/docker-buildx /root/.docker/cli-plugins/docker-buildx |
|||
chmod +x ~/.docker/cli-plugins/docker-buildx && sudo chmod +x /root/.docker/cli-plugins/docker-buildx |
|||
|
|||
# Create builder |
|||
sudo docker buildx create --use --name mybuilder || true |
|||
sudo docker buildx inspect --bootstrap |
|||
|
|||
echo "Docker Buildx installation complete!" |
|||
} |
|||
|
|||
# Function to install NVIDIA Container Toolkit if needed |
|||
function install_nvidia_toolkit { |
|||
# Check if NVIDIA Container Toolkit is already installed |
|||
if command -v nvidia-container-toolkit &> /dev/null; then |
|||
echo "NVIDIA Container Toolkit is already installed." |
|||
return 0 |
|||
fi |
|||
|
|||
echo "Installing NVIDIA Container Toolkit..." |
|||
|
|||
# Add the package repositories |
|||
distribution=$(. /etc/os-release;echo $ID$VERSION_ID) |
|||
|
|||
# Check if GPG key exists and remove it if it does |
|||
if [ -f "/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg" ]; then |
|||
echo "Removing existing NVIDIA GPG key..." |
|||
sudo rm /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg |
|||
fi |
|||
|
|||
# Add new GPG key |
|||
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg |
|||
|
|||
# Add repository |
|||
curl -s -L https://nvidia.github.io/nvidia-container-runtime/$distribution/nvidia-container-runtime.list | \ |
|||
sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \ |
|||
sudo tee /etc/apt/sources.list.d/nvidia-container-runtime.list |
|||
|
|||
# Install nvidia-container-toolkit and docker if needed |
|||
sudo apt-get update |
|||
sudo apt-get install -y nvidia-container-toolkit |
|||
|
|||
# Install docker if not already installed |
|||
if ! command -v docker &> /dev/null; then |
|||
sudo apt-get install -y docker.io |
|||
fi |
|||
|
|||
# Configure Docker to use the NVIDIA runtime |
|||
sudo nvidia-ctk runtime configure --runtime=docker |
|||
|
|||
# Restart the Docker daemon |
|||
sudo systemctl restart docker |
|||
|
|||
echo "NVIDIA Container Toolkit installation complete!" |
|||
} |
|||
|
|||
|
|||
# Function to build Docker image for current branch |
|||
function build_docker_image { |
|||
echo "Building Docker image: $DEPLOY_CONTAINER" |
|||
|
|||
sudo docker buildx build \ |
|||
--build-arg USERNAME=$USERNAME \ |
|||
--build-arg USERID=$USERID \ |
|||
--build-arg HOME_DIR=$DOCKER_HOME_DIR \ |
|||
--build-arg WORKTREE_NAME=$WORKTREE_NAME \ |
|||
--cache-from $CACHE_FROM \ |
|||
-t $DEPLOY_CONTAINER \ |
|||
-f docker/Dockerfile.deploy \ |
|||
--load \ |
|||
. |
|||
|
|||
# Tag for persistent cache |
|||
# sudo docker tag $DEPLOY_CONTAINER $CACHE_FROM |
|||
echo "Docker image build complete!" |
|||
} |
|||
|
|||
# Build function |
|||
function build_with_cleanup { |
|||
echo "Building Docker image..." |
|||
echo "Removing existing containers and images..." |
|||
clean_container |
|||
# Tag for persistent cache before deleting the image |
|||
sudo docker tag $DEPLOY_CONTAINER $CACHE_FROM 2>/dev/null || true |
|||
sudo docker rmi $DEPLOY_CONTAINER 2>/dev/null || true |
|||
echo "Images cleaned!" |
|||
|
|||
install_docker_buildx |
|||
install_nvidia_toolkit |
|||
build_docker_image |
|||
} |
|||
|
|||
function install_remote_image { |
|||
echo "Installing Docker image from remote registry: $REMOTE_IMAGE" |
|||
echo "Removing existing containers to ensure a clean install..." |
|||
clean_container |
|||
sudo docker pull "$REMOTE_IMAGE" |
|||
sudo docker tag "$REMOTE_IMAGE" "$DEPLOY_CONTAINER" |
|||
sudo docker tag "$REMOTE_IMAGE" "$CACHE_FROM" 2>/dev/null || true |
|||
echo "Docker image install complete!" |
|||
} |
|||
|
|||
# Clean up if requested |
|||
if [ "$CLEAN" = true ]; then |
|||
clean_container |
|||
exit 0 |
|||
fi |
|||
|
|||
# Build if requested |
|||
if [ "$BUILD" = true ]; then |
|||
build_with_cleanup |
|||
fi |
|||
|
|||
if [ "$INSTALL" = true ]; then |
|||
install_remote_image |
|||
fi |
|||
|
|||
if [ "$DOCKER_HUB_PUSH" = true ]; then |
|||
echo "Pushing Docker image to Docker Hub: docker.io/nvgear/${PROJECT_SLUG}:latest" |
|||
sudo docker tag $DEPLOY_CONTAINER docker.io/nvgear/${PROJECT_SLUG}:latest |
|||
sudo docker push docker.io/nvgear/${PROJECT_SLUG}:latest |
|||
echo "Docker image pushed to Docker Hub!" |
|||
exit 0 |
|||
fi |
|||
|
|||
# Setup X11 display forwarding |
|||
setup_x11() { |
|||
# Set display if missing and X server available |
|||
if [ -z "$DISPLAY" ] && command -v xset >/dev/null 2>&1 && xset q >/dev/null 2>&1; then |
|||
export DISPLAY=:1 |
|||
echo "No DISPLAY set, using :1" |
|||
fi |
|||
|
|||
# Enable X11 forwarding if possible |
|||
if [ -n "$DISPLAY" ] && command -v xhost >/dev/null 2>&1 && xhost +local:docker 2>/dev/null; then |
|||
echo "X11 forwarding enabled" |
|||
return 0 |
|||
else |
|||
echo "Headless environment - X11 disabled" |
|||
export DISPLAY="" |
|||
return 1 |
|||
fi |
|||
} |
|||
|
|||
X11_ENABLED=false |
|||
setup_x11 && X11_ENABLED=true |
|||
|
|||
# Mount entire /dev directory for dynamic device access (including hidraw for joycon) |
|||
# This allows JoyCon controllers to be detected even when connected after container launch |
|||
sudo chmod g+r+w /dev/input/* |
|||
|
|||
# Detect GPU setup and set appropriate environment variables |
|||
echo "Detecting GPU setup..." |
|||
GPU_ENV_VARS="" |
|||
|
|||
# Check if we have both integrated and discrete GPUs (hybrid/Optimus setup) |
|||
HAS_AMD_GPU=$(lspci | grep -i "vga\|3d\|display" | grep -i amd | wc -l) |
|||
HAS_INTEL_GPU=$(lspci | grep -i "vga\|3d\|display" | grep -i intel | wc -l) |
|||
HAS_NVIDIA_GPU=$(lspci | grep -i "vga\|3d\|display" | grep -i nvidia | wc -l) |
|||
|
|||
if [[ "$HAS_INTEL_GPU" -gt 0 ]] || [[ "$HAS_AMD_GPU" -gt 0 ]] && [[ "$HAS_NVIDIA_GPU" -gt 0 ]]; then |
|||
echo "Detected hybrid GPU setup (Intel/AMD integrated + NVIDIA discrete)" |
|||
echo "Setting NVIDIA Optimus environment variables for proper rendering offload..." |
|||
GPU_ENV_VARS="-e __NV_PRIME_RENDER_OFFLOAD=1 \ |
|||
-e __VK_LAYER_NV_optimus=NVIDIA_only" |
|||
else |
|||
GPU_ENV_VARS="" |
|||
fi |
|||
|
|||
# Set GPU runtime based on architecture |
|||
if is_arm64; then |
|||
echo "Detected ARM64 architecture (Jetson Orin), using device access instead of nvidia runtime..." |
|||
GPU_RUNTIME_ARGS="--device /dev/nvidia0 --device /dev/nvidiactl --device /dev/nvidia-modeset --device /dev/nvidia-uvm --device /dev/nvidia-uvm-tools" |
|||
else |
|||
GPU_RUNTIME_ARGS="--gpus all --runtime=nvidia" |
|||
fi |
|||
|
|||
# Common Docker run parameters |
|||
DOCKER_RUN_ARGS="--hostname $HOSTNAME \ |
|||
--user $USERNAME \ |
|||
--group-add $INPUT_GID \ |
|||
$GPU_RUNTIME_ARGS \ |
|||
--ipc=host \ |
|||
--network=host \ |
|||
--privileged \ |
|||
--device=/dev \ |
|||
$GPU_ENV_VARS \ |
|||
-p 5678:5678 \ |
|||
-e DISPLAY=$DISPLAY \ |
|||
-e NVIDIA_VISIBLE_DEVICES=all \ |
|||
-e NVIDIA_DRIVER_CAPABILITIES=graphics,compute,utility \ |
|||
-e __GLX_VENDOR_LIBRARY_NAME=nvidia \ |
|||
-e USERNAME=$USERNAME \ |
|||
-e GR00T_WBC_DIR="$DOCKER_HOME_DIR/Projects/$WORKTREE_NAME" \ |
|||
-v /dev/bus/usb:/dev/bus/usb \ |
|||
-v /tmp/.X11-unix:/tmp/.X11-unix \ |
|||
-v $HOME/.ssh:$DOCKER_HOME_DIR/.ssh \ |
|||
-v $HOME/.gear:$DOCKER_HOME_DIR/.gear \ |
|||
-v $HOME/.Xauthority:$DOCKER_HOME_DIR/.Xauthority \ |
|||
-v $PROJECT_DIR:$DOCKER_HOME_DIR/Projects/$(basename "$PROJECT_DIR") |
|||
--device /dev/snd \ |
|||
--group-add audio \ |
|||
-e PULSE_SERVER=unix:/run/user/$(id -u)/pulse/native \ |
|||
-v /run/user/$(id -u)/pulse/native:/run/user/$(id -u)/pulse/native \ |
|||
-v $HOME/.config/pulse/cookie:/home/$USERNAME/.config/pulse/cookie" |
|||
|
|||
# Check if RL mode first, then handle container logic |
|||
if [ "$DEPLOY" = true ]; then |
|||
# Deploy mode - use gr00t_wbc-deploy-${USERNAME} container |
|||
|
|||
# Always clean up old processes and create a new container |
|||
# Kill all gr00t_wbc processes across containers to prevent message passing conflicts |
|||
"$SCRIPT_DIR/kill_gr00t_wbc_processors.sh" |
|||
echo "Creating new deploy container..." |
|||
|
|||
# Clean up old processes and create a fresh deploy container |
|||
# Remove existing deploy container if it exists |
|||
if sudo docker ps -a --format '{{.Names}}' | grep -q "^$DEPLOY_CONTAINER$"; then |
|||
echo "Removing existing deploy container..." |
|||
sudo docker rm -f $DEPLOY_CONTAINER |
|||
fi |
|||
sudo docker run -it --rm $DOCKER_RUN_ARGS \ |
|||
-w $DOCKER_HOME_DIR/Projects/$WORKTREE_NAME \ |
|||
--name $DEPLOY_CONTAINER \ |
|||
$DEPLOY_CONTAINER \ |
|||
/bin/bash -ic 'exec "$0" "$@"' \ |
|||
"${DOCKER_HOME_DIR}/Projects/${WORKTREE_NAME}/docker/entrypoint/deploy.sh" \ |
|||
"${EXTRA_ARGS[@]}" |
|||
else |
|||
# Bash mode - use gr00t_wbc-bash-${USERNAME} container |
|||
if sudo docker ps -a --format '{{.Names}}' | grep -q "^$BASH_CONTAINER$"; then |
|||
echo "Bash container exists, starting it..." |
|||
sudo docker start $BASH_CONTAINER > /dev/null |
|||
sudo docker exec -it $BASH_CONTAINER /bin/bash |
|||
else |
|||
echo "Creating new bash container with auto-install gr00t_wbc..." |
|||
sudo docker run -it $DOCKER_RUN_ARGS \ |
|||
-w $DOCKER_HOME_DIR/Projects/$WORKTREE_NAME \ |
|||
--name $BASH_CONTAINER \ |
|||
$DEPLOY_CONTAINER \ |
|||
/bin/bash -ic 'exec "$0"' \ |
|||
"${DOCKER_HOME_DIR}/Projects/${WORKTREE_NAME}/docker/entrypoint/bash.sh" |
|||
fi |
|||
fi |
|||
|
|||
# Cleanup X11 permissions |
|||
$X11_ENABLED && xhost -local:docker 2>/dev/null |
|||
@ -0,0 +1,39 @@ |
|||
# Generated by MacOS |
|||
.DS_Store |
|||
|
|||
# Generated by Windows |
|||
Thumbs.db |
|||
|
|||
# Applications |
|||
*.app |
|||
*.exe |
|||
*.war |
|||
|
|||
# Large media files |
|||
*.mp4 |
|||
*.tiff |
|||
*.avi |
|||
*.flv |
|||
*.mov |
|||
*.wmv |
|||
*.jpg |
|||
*.png |
|||
|
|||
# VS Code |
|||
.vscode |
|||
|
|||
# other |
|||
*.egg-info |
|||
__pycache__ |
|||
|
|||
# IDEs |
|||
.idea |
|||
|
|||
# cache |
|||
.pytest_cache |
|||
|
|||
# JetBrains IDE |
|||
.idea/ |
|||
|
|||
# python |
|||
dist/ |
|||
@ -0,0 +1,29 @@ |
|||
BSD 3-Clause License |
|||
|
|||
Copyright (c) 2016-2024 HangZhou YuShu TECHNOLOGY CO.,LTD. ("Unitree Robotics") |
|||
All rights reserved. |
|||
|
|||
Redistribution and use in source and binary forms, with or without |
|||
modification, are permitted provided that the following conditions are met: |
|||
|
|||
* Redistributions of source code must retain the above copyright notice, this |
|||
list of conditions and the following disclaimer. |
|||
|
|||
* Redistributions in binary form must reproduce the above copyright notice, |
|||
this list of conditions and the following disclaimer in the documentation |
|||
and/or other materials provided with the distribution. |
|||
|
|||
* Neither the name of the copyright holder nor the names of its |
|||
contributors may be used to endorse or promote products derived from |
|||
this software without specific prior written permission. |
|||
|
|||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
|||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
|||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
|||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE |
|||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
|||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
|||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
|||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
|||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|||
@ -0,0 +1,121 @@ |
|||
# unitree_sdk2_python |
|||
unitree_sdk2 python 接口 |
|||
|
|||
# 安装 |
|||
## 依赖 |
|||
- python>=3.8 |
|||
- cyclonedds==0.10.2 |
|||
- numpy |
|||
- opencv-python |
|||
|
|||
## 安装 unitree_sdk2_python |
|||
在终端中执行: |
|||
```bash |
|||
cd ~ |
|||
sudo apt install python3-pip |
|||
git clone https://github.com/unitreerobotics/unitree_sdk2_python.git |
|||
cd unitree_sdk2_python |
|||
pip3 install -e . |
|||
``` |
|||
## FAQ |
|||
##### 1. `pip3 install -e .` 遇到报错 |
|||
```bash |
|||
Could not locate cyclonedds. Try to set CYCLONEDDS_HOME or CMAKE_PREFIX_PATH |
|||
``` |
|||
该错误提示找不到 cyclonedds 路径。首先编译安装cyclonedds: |
|||
```bash |
|||
cd ~ |
|||
git clone https://github.com/eclipse-cyclonedds/cyclonedds -b releases/0.10.x |
|||
cd cyclonedds && mkdir build install && cd build |
|||
cmake .. -DCMAKE_INSTALL_PREFIX=../install |
|||
cmake --build . --target install |
|||
``` |
|||
进入 unitree_sdk2_python 目录,设置 `CYCLONEDDS_HOME` 为刚刚编译好的 cyclonedds 所在路径,再安装 unitree_sdk2_python |
|||
```bash |
|||
cd ~/unitree_sdk2_python |
|||
export CYCLONEDDS_HOME="~/cyclonedds/install" |
|||
pip3 install -e . |
|||
``` |
|||
|
|||
详细见: |
|||
https://pypi.org/project/cyclonedds/#installing-with-pre-built-binaries |
|||
|
|||
# 使用 |
|||
python sdk2 接口与 unitree_skd2的接口保持一致,通过请求响应或订阅发布topic实现机器人的状态获取和控制。相应的例程位于`/example`目录下。在运行例程前,需要根据文档 https://support.unitree.com/home/zh/developer/Quick_start 配置好机器人的网络连接。 |
|||
## DDS通讯 |
|||
在终端中执行: |
|||
```bash |
|||
python3 ./example/helloworld/publisher.py |
|||
``` |
|||
打开新的终端,执行: |
|||
```bash |
|||
python3 ./example/helloworld/subscriber.py |
|||
``` |
|||
可以看到终端输出的数据信息。`publisher.py` 和 `subscriber.py` 传输的数据定义在 `user_data.py` 中,用户可以根据需要自行定义需要传输的数据结构。 |
|||
|
|||
## 高层状态和控制 |
|||
高层接口的数据结构和控制方式与unitree_sdk2一致。具体可见:https://support.unitree.com/home/zh/developer/sports_services |
|||
### 高层状态 |
|||
终端中执行: |
|||
```bash |
|||
python3 ./example/high_level/read_highstate.py enp2s0 |
|||
``` |
|||
其中 `enp2s0` 为机器人所连接的网卡名称,请根据实际情况修改。 |
|||
### 高层控制 |
|||
终端中执行: |
|||
```bash |
|||
python3 ./example/high_level/sportmode_test.py enp2s0 |
|||
``` |
|||
其中 `enp2s0` 为机器人所连接的网卡名称,请根据实际情况修改。 |
|||
该例程提供了几种测试方法,可根据测试需要选择: |
|||
```python |
|||
test.StandUpDown() # 站立趴下 |
|||
# test.VelocityMove() # 速度控制 |
|||
# test.BalanceAttitude() # 姿态控制 |
|||
# test.TrajectoryFollow() # 轨迹跟踪 |
|||
# test.SpecialMotions() # 特殊动作 |
|||
|
|||
``` |
|||
## 底层状态和控制 |
|||
底层接口的数据结构和控制方式与unitree_sdk2一致。具体可见:https://support.unitree.com/home/zh/developer/Basic_services |
|||
### 底层状态 |
|||
终端中执行: |
|||
```bash |
|||
python3 ./example/low_level/lowlevel_control.py enp2s0 |
|||
``` |
|||
其中 `enp2s0` 为机器人所连接的网卡名称,请根据实际情况修改。程序会输出右前腿hip关节的状态、IMU和电池电压信息。 |
|||
|
|||
### 底层电机控制 |
|||
首先使用 app 关闭高层运动服务(sport_mode),否则会导致指令冲突。 |
|||
终端中执行: |
|||
```bash |
|||
python3 ./example/low_level/lowlevel_control.py enp2s0 |
|||
``` |
|||
其中 `enp2s0` 为机器人所连接的网卡名称,请根据实际情况修改。左后腿 hip 关节会保持在0角度 (安全起见,这里设置 kp=10, kd=1),左后腿 calf 关节将持续输出 1Nm 的转矩。 |
|||
|
|||
## 遥控器状态获取 |
|||
终端中执行: |
|||
```bash |
|||
python3 ./example/wireless_controller/wireless_controller.py enp2s0 |
|||
``` |
|||
其中 `enp2s0` 为机器人所连接的网卡名称,请根据实际情况修改。 |
|||
终端将输出每一个按键的状态。对于遥控器按键的定义和数据结构可见: https://support.unitree.com/home/zh/developer/Get_remote_control_status |
|||
|
|||
## 前置摄像头 |
|||
使用opencv获取前置摄像头(确保在有图形界面的系统下运行, 按 ESC 退出程序): |
|||
```bash |
|||
python3 ./example/front_camera/camera_opencv.py enp2s0 |
|||
``` |
|||
其中 `enp2s0` 为机器人所连接的网卡名称,请根据实际情况修改。 |
|||
|
|||
## 避障开关 |
|||
```bash |
|||
python3 ./example/obstacles_avoid_switch/obstacles_avoid_switch.py enp2s0 |
|||
``` |
|||
其中 `enp2s0` 为机器人所连接的网卡名称,请根据实际情况修改。机器人将循环开启和关闭避障功能。关于避障服务,详细见 https://support.unitree.com/home/zh/developer/ObstaclesAvoidClient |
|||
|
|||
## 灯光音量控制 |
|||
```bash |
|||
python3 ./example/vui_client/vui_client_example.py enp2s0 |
|||
``` |
|||
其中 `enp2s0` 为机器人所连接的网卡名称,请根据实际情况修改。机器人将循环调节音量和灯光亮度。该接口详细见 https://support.unitree.com/home/zh/developer/VuiClient |
|||
@ -0,0 +1,118 @@ |
|||
# unitree_sdk2_python |
|||
Python interface for unitree sdk2 |
|||
|
|||
# Installation |
|||
## Dependencies |
|||
- Python >= 3.8 |
|||
- cyclonedds == 0.10.2 |
|||
- numpy |
|||
- opencv-python |
|||
## Install unitree_sdk2_python |
|||
|
|||
```bash |
|||
pip install unitree_sdk2py |
|||
``` |
|||
|
|||
### Installing from source |
|||
Execute the following commands in the terminal: |
|||
```bash |
|||
cd ~ |
|||
sudo apt install python3-pip |
|||
git clone https://github.com/unitreerobotics/unitree_sdk2_python.git |
|||
cd unitree_sdk2_python |
|||
pip3 install -e . |
|||
``` |
|||
## FAQ |
|||
##### 1. Error when `pip3 install -e .`: |
|||
```bash |
|||
Could not locate cyclonedds. Try to set CYCLONEDDS_HOME or CMAKE_PREFIX_PATH |
|||
``` |
|||
This error mentions that the cyclonedds path could not be found. First compile and install cyclonedds: |
|||
|
|||
```bash |
|||
cd ~ |
|||
git clone https://github.com/eclipse-cyclonedds/cyclonedds -b releases/0.10.x |
|||
cd cyclonedds && mkdir build install && cd build |
|||
cmake .. -DCMAKE_INSTALL_PREFIX=../install |
|||
cmake --build . --target install |
|||
``` |
|||
Enter the unitree_sdk2_python directory, set `CYCLONEDDS_HOME` to the path of the cyclonedds you just compiled, and then install unitree_sdk2_python. |
|||
```bash |
|||
cd ~/unitree_sdk2_python |
|||
export CYCLONEDDS_HOME="~/cyclonedds/install" |
|||
pip3 install -e . |
|||
``` |
|||
For details, see: https://pypi.org/project/cyclonedds/#installing-with-pre-built-binaries |
|||
|
|||
# Usage |
|||
The Python sdk2 interface maintains consistency with the unitree_sdk2 interface, achieving robot status acquisition and control through request-response or topic subscription/publishing. Example programs are located in the `/example` directory. Before running the examples, configure the robot's network connection as per the instructions in the document at https://support.unitree.com/home/en/developer/Quick_start. |
|||
## DDS Communication |
|||
In the terminal, execute: |
|||
```bash |
|||
python3 ./example/helloworld/publisher.py |
|||
``` |
|||
Open a new terminal and execute: |
|||
```bash |
|||
python3 ./example/helloworld/subscriber.py |
|||
``` |
|||
You will see the data output in the terminal. The data structure transmitted between `publisher.py` and `subscriber.py` is defined in `user_data.py`, and users can define the required data structure as needed. |
|||
## High-Level Status and Control |
|||
The high-level interface maintains consistency with unitree_sdk2 in terms of data structure and control methods. For detailed information, refer to https://support.unitree.com/home/en/developer/sports_services. |
|||
### High-Level Status |
|||
Execute the following command in the terminal: |
|||
```bash |
|||
python3 ./example/high_level/read_highstate.py enp2s0 |
|||
``` |
|||
Replace `enp2s0` with the name of the network interface to which the robot is connected,. |
|||
### High-Level Control |
|||
Execute the following command in the terminal: |
|||
```bash |
|||
python3 ./example/high_level/sportmode_test.py enp2s0 |
|||
``` |
|||
Replace `enp2s0` with the name of the network interface to which the robot is connected. This example program provides several test methods, and you can choose the required tests as follows: |
|||
```python |
|||
test.StandUpDown() # Stand up and lie down |
|||
# test.VelocityMove() # Velocity control |
|||
# test.BalanceAttitude() # Attitude control |
|||
# test.TrajectoryFollow() # Trajectory tracking |
|||
# test.SpecialMotions() # Special motions |
|||
``` |
|||
## Low-Level Status and Control |
|||
The low-level interface maintains consistency with unitree_sdk2 in terms of data structure and control methods. For detailed information, refer to https://support.unitree.com/home/en/developer/Basic_services. |
|||
### Low-Level Status |
|||
Execute the following command in the terminal: |
|||
```bash |
|||
python3 ./example/low_level/lowlevel_control.py enp2s0 |
|||
``` |
|||
Replace `enp2s0` with the name of the network interface to which the robot is connected. The program will output the state of the right front leg hip joint, IMU, and battery voltage. |
|||
### Low-Level Motor Control |
|||
First, use the app to turn off the high-level motion service (sport_mode) to prevent conflicting instructions. |
|||
Execute the following command in the terminal: |
|||
```bash |
|||
python3 ./example/low_level/lowlevel_control.py enp2s0 |
|||
``` |
|||
Replace `enp2s0` with the name of the network interface to which the robot is connected. The left hind leg hip joint will maintain a 0-degree position (for safety, set kp=10, kd=1), and the left hind leg calf joint will continuously output 1Nm of torque. |
|||
## Wireless Controller Status |
|||
Execute the following command in the terminal: |
|||
```bash |
|||
python3 ./example/wireless_controller/wireless_controller.py enp2s0 |
|||
``` |
|||
Replace `enp2s0` with the name of the network interface to which the robot is connected. The terminal will output the status of each key. For the definition and data structure of the remote control keys, refer to https://support.unitree.com/home/en/developer/Get_remote_control_status. |
|||
## Front Camera |
|||
Use OpenCV to obtain the front camera (ensure to run on a system with a graphical interface, and press ESC to exit the program): |
|||
```bash |
|||
python3 ./example/front_camera/camera_opencv.py enp2s0 |
|||
``` |
|||
Replace `enp2s0` with the name of the network interface to which the robot is connected. |
|||
|
|||
## Obstacle Avoidance Switch |
|||
```bash |
|||
python3 ./example/obstacles_avoid_switch/obstacles_avoid_switch.py enp2s0 |
|||
``` |
|||
Replace `enp2s0` with the name of the network interface to which the robot is connected. The robot will cycle obstacle avoidance on and off. For details on the obstacle avoidance service, see https://support.unitree.com/home/en/developer/ObstaclesAvoidClient |
|||
|
|||
## Light and volume control |
|||
```bash |
|||
python3 ./example/vui_client/vui_client_example.py enp2s0 |
|||
``` |
|||
Replace `enp2s0` with the name of the network interface to which the robot is connected.T he robot will cycle the volume and light brightness. The interface is detailed at https://support.unitree.com/home/en/developer/VuiClient |
|||
@ -0,0 +1,51 @@ |
|||
from unitree_sdk2py.core.channel import ChannelFactoryInitialize |
|||
from unitree_sdk2py.b2.front_video.front_video_client import FrontVideoClient |
|||
from unitree_sdk2py.b2.back_video.back_video_client import BackVideoClient |
|||
import cv2 |
|||
import numpy as np |
|||
import sys |
|||
|
|||
def display_image(window_name, data): |
|||
# If data is a list, we need to convert it to a bytes object |
|||
if isinstance(data, list): |
|||
data = bytes(data) |
|||
|
|||
# Now convert to numpy image |
|||
image_data = np.frombuffer(data, dtype=np.uint8) |
|||
image = cv2.imdecode(image_data, cv2.IMREAD_COLOR) |
|||
if image is not None: |
|||
cv2.imshow(window_name, image) |
|||
|
|||
if __name__ == "__main__": |
|||
if len(sys.argv) > 1: |
|||
ChannelFactoryInitialize(0, sys.argv[1]) |
|||
else: |
|||
ChannelFactoryInitialize(0) |
|||
|
|||
frontCameraClient = FrontVideoClient() # Create a front camera video client |
|||
frontCameraClient.SetTimeout(3.0) |
|||
frontCameraClient.Init() |
|||
|
|||
backCameraClient = BackVideoClient() # Create a back camera video client |
|||
backCameraClient.SetTimeout(3.0) |
|||
backCameraClient.Init() |
|||
|
|||
# Loop to continuously fetch images |
|||
while True: |
|||
# Get front camera image |
|||
front_code, front_data = frontCameraClient.GetImageSample() |
|||
if front_code == 0: |
|||
display_image("Front Camera", front_data) |
|||
|
|||
# Get back camera image |
|||
back_code, back_data = backCameraClient.GetImageSample() |
|||
if back_code == 0: |
|||
display_image("Back Camera", back_data) |
|||
|
|||
# Press ESC to stop |
|||
if cv2.waitKey(20) == 27: |
|||
break |
|||
|
|||
# Clean up windows |
|||
cv2.destroyAllWindows() |
|||
|
|||
@ -0,0 +1,51 @@ |
|||
import time |
|||
import os |
|||
import sys |
|||
|
|||
from unitree_sdk2py.core.channel import ChannelFactoryInitialize |
|||
from unitree_sdk2py.b2.front_video.front_video_client import FrontVideoClient |
|||
from unitree_sdk2py.b2.back_video.back_video_client import BackVideoClient |
|||
|
|||
if __name__ == "__main__": |
|||
if len(sys.argv) > 1: |
|||
ChannelFactoryInitialize(0, sys.argv[1]) |
|||
else: |
|||
ChannelFactoryInitialize(0) |
|||
|
|||
# 创建前置相机客户端 |
|||
front_client = FrontVideoClient() |
|||
front_client.SetTimeout(3.0) |
|||
front_client.Init() |
|||
|
|||
# 创建后置相机客户端 |
|||
back_client = BackVideoClient() |
|||
back_client.SetTimeout(3.0) |
|||
back_client.Init() |
|||
|
|||
print("##################Get Front Camera Image###################") |
|||
# 获取前置相机图像 |
|||
front_code, front_data = front_client.GetImageSample() |
|||
|
|||
if front_code != 0: |
|||
print("Get front camera image error. Code:", front_code) |
|||
else: |
|||
front_image_name = "./front_img.jpg" |
|||
print("Front Image Saved as:", front_image_name) |
|||
|
|||
with open(front_image_name, "+wb") as f: |
|||
f.write(bytes(front_data)) |
|||
|
|||
print("##################Get Back Camera Image###################") |
|||
# 获取后置相机图像 |
|||
back_code, back_data = back_client.GetImageSample() |
|||
|
|||
if back_code != 0: |
|||
print("Get back camera image error. Code:", back_code) |
|||
else: |
|||
back_image_name = "./back_img.jpg" |
|||
print("Back Image Saved as:", back_image_name) |
|||
|
|||
with open(back_image_name, "+wb") as f: |
|||
f.write(bytes(back_data)) |
|||
|
|||
time.sleep(1) |
|||
@ -0,0 +1,105 @@ |
|||
import time |
|||
import sys |
|||
from unitree_sdk2py.core.channel import ChannelSubscriber, ChannelFactoryInitialize |
|||
from unitree_sdk2py.b2.sport.sport_client import SportClient |
|||
import math |
|||
from dataclasses import dataclass |
|||
|
|||
@dataclass |
|||
class TestOption: |
|||
name: str |
|||
id: int |
|||
|
|||
option_list = [ |
|||
TestOption(name="damp", id=0), |
|||
TestOption(name="stand_up", id=1), |
|||
TestOption(name="stand_down", id=2), |
|||
TestOption(name="move forward", id=3), |
|||
TestOption(name="move lateral", id=4), |
|||
TestOption(name="move rotate", id=5), |
|||
TestOption(name="stop_move", id=6), |
|||
TestOption(name="switch_gait", id=7), |
|||
TestOption(name="switch_gait", id=8), |
|||
TestOption(name="recovery", id=9), |
|||
TestOption(name="balanced stand", id=10) |
|||
] |
|||
|
|||
class UserInterface: |
|||
def __init__(self): |
|||
self.test_option_ = None |
|||
|
|||
def convert_to_int(self, input_str): |
|||
try: |
|||
return int(input_str) |
|||
except ValueError: |
|||
return None |
|||
|
|||
def terminal_handle(self): |
|||
input_str = input("Enter id or name: \n") |
|||
|
|||
if input_str == "list": |
|||
self.test_option_.name = None |
|||
self.test_option_.id = None |
|||
for option in option_list: |
|||
print(f"{option.name}, id: {option.id}") |
|||
return |
|||
|
|||
for option in option_list: |
|||
if input_str == option.name or self.convert_to_int(input_str) == option.id: |
|||
self.test_option_.name = option.name |
|||
self.test_option_.id = option.id |
|||
print(f"Test: {self.test_option_.name}, test_id: {self.test_option_.id}") |
|||
return |
|||
|
|||
print("No matching test option found.") |
|||
|
|||
if __name__ == "__main__": |
|||
|
|||
if len(sys.argv) < 2: |
|||
print(f"Usage: python3 {sys.argv[0]} networkInterface") |
|||
sys.exit(-1) |
|||
|
|||
print("WARNING: Please ensure there are no obstacles around the robot while running this example.") |
|||
input("Press Enter to continue...") |
|||
|
|||
ChannelFactoryInitialize(0, sys.argv[1]) |
|||
|
|||
test_option = TestOption(name=None, id=None) |
|||
user_interface = UserInterface() |
|||
user_interface.test_option_ = test_option |
|||
|
|||
sport_client = SportClient() |
|||
sport_client.SetTimeout(10.0) |
|||
sport_client.Init() |
|||
|
|||
print("Input \"list\" to list all test option ...") |
|||
|
|||
while True: |
|||
user_interface.terminal_handle() |
|||
|
|||
print(f"Updated Test Option: Name = {test_option.name}, ID = {test_option.id}") |
|||
|
|||
if test_option.id == 0: |
|||
sport_client.Damp() |
|||
elif test_option.id == 1: |
|||
sport_client.StandUp() |
|||
elif test_option.id == 2: |
|||
sport_client.StandDown() |
|||
elif test_option.id == 3: |
|||
sport_client.Move(0.3,0,0) |
|||
elif test_option.id == 4: |
|||
sport_client.Move(0,0.3,0) |
|||
elif test_option.id == 5: |
|||
sport_client.Move(0,0,0.5) |
|||
elif test_option.id == 6: |
|||
sport_client.StopMove() |
|||
elif test_option.id == 7: |
|||
sport_client.SwitchGait(0) |
|||
elif test_option.id == 8: |
|||
sport_client.SwitchGait(1) |
|||
elif test_option.id == 9: |
|||
sport_client.RecoveryStand() |
|||
elif test_option.id == 10: |
|||
sport_client.BalanceStand() |
|||
|
|||
time.sleep(1) |
|||
@ -0,0 +1,175 @@ |
|||
import time |
|||
import sys |
|||
|
|||
from unitree_sdk2py.core.channel import ChannelPublisher, ChannelFactoryInitialize |
|||
from unitree_sdk2py.core.channel import ChannelSubscriber, ChannelFactoryInitialize |
|||
from unitree_sdk2py.idl.default import unitree_go_msg_dds__LowCmd_ |
|||
from unitree_sdk2py.idl.default import unitree_go_msg_dds__LowState_ |
|||
from unitree_sdk2py.idl.unitree_go.msg.dds_ import LowCmd_ |
|||
from unitree_sdk2py.idl.unitree_go.msg.dds_ import LowState_ |
|||
from unitree_sdk2py.utils.thread import RecurrentThread |
|||
import unitree_legged_const as b2 |
|||
from unitree_sdk2py.comm.motion_switcher.motion_switcher_client import MotionSwitcherClient |
|||
from unitree_sdk2py.b2.sport.sport_client import SportClient |
|||
|
|||
from unitree_sdk2py.utils.crc import CRC |
|||
|
|||
class Custom: |
|||
def __init__(self): |
|||
self.Kp = 1000.0 |
|||
self.Kd = 10.0 |
|||
self.time_consume = 0 |
|||
self.rate_count = 0 |
|||
self.sin_count = 0 |
|||
self.motiontime = 0 |
|||
self.dt = 0.002 |
|||
|
|||
self.low_cmd = unitree_go_msg_dds__LowCmd_() |
|||
self.low_state = None |
|||
|
|||
self.targetPos_1 = [0.0, 1.36, -2.65, 0.0, 1.36, -2.65, |
|||
-0.2, 1.36, -2.65, 0.2, 1.36, -2.65] |
|||
|
|||
self.targetPos_2 = [0.0, 0.67, -1.3, 0.0, 0.67, -1.3, |
|||
0.0, 0.67, -1.3, 0.0, 0.67, -1.3] |
|||
|
|||
self.targetPos_3 = [-0.5, 1.36, -2.65, 0.5, 1.36, -2.65, |
|||
-0.5, 1.36, -2.65, 0.5, 1.36, -2.65] |
|||
|
|||
self.startPos = [0.0] * 12 |
|||
self.duration_1 = 500 |
|||
self.duration_2 = 900 |
|||
self.duration_3 = 1000 |
|||
self.duration_4 = 900 |
|||
self.percent_1 = 0 |
|||
self.percent_2 = 0 |
|||
self.percent_3 = 0 |
|||
self.percent_4 = 0 |
|||
|
|||
self.firstRun = True |
|||
self.done = False |
|||
|
|||
# thread handling |
|||
self.lowCmdWriteThreadPtr = None |
|||
|
|||
self.crc = CRC() |
|||
|
|||
def Init(self): |
|||
self.InitLowCmd() |
|||
|
|||
# create publisher # |
|||
self.lowcmd_publisher = ChannelPublisher("rt/lowcmd", LowCmd_) |
|||
self.lowcmd_publisher.Init() |
|||
|
|||
# create subscriber # |
|||
self.lowstate_subscriber = ChannelSubscriber("rt/lowstate", LowState_) |
|||
self.lowstate_subscriber.Init(self.LowStateMessageHandler, 10) |
|||
|
|||
self.sc = SportClient() |
|||
self.sc.SetTimeout(5.0) |
|||
self.sc.Init() |
|||
|
|||
self.msc = MotionSwitcherClient() |
|||
self.msc.SetTimeout(5.0) |
|||
self.msc.Init() |
|||
|
|||
status, result = self.msc.CheckMode() |
|||
while result['name']: |
|||
self.sc.StandDown() |
|||
self.msc.ReleaseMode() |
|||
status, result = self.msc.CheckMode() |
|||
time.sleep(1) |
|||
|
|||
|
|||
def Start(self): |
|||
self.lowCmdWriteThreadPtr = RecurrentThread( |
|||
interval=0.002, target=self.LowCmdWrite, name="writebasiccmd" |
|||
) |
|||
self.lowCmdWriteThreadPtr.Start() |
|||
|
|||
def InitLowCmd(self): |
|||
self.low_cmd.head[0] = 0xFE |
|||
self.low_cmd.head[1] = 0xEF |
|||
self.low_cmd.level_flag = 0xFF |
|||
self.low_cmd.gpio = 0 |
|||
for i in range(20): |
|||
self.low_cmd.motor_cmd[i].mode = 0x01 |
|||
self.low_cmd.motor_cmd[i].q= b2.PosStopF |
|||
self.low_cmd.motor_cmd[i].kp = 0 |
|||
self.low_cmd.motor_cmd[i].dq = b2.VelStopF |
|||
self.low_cmd.motor_cmd[i].kd = 0 |
|||
self.low_cmd.motor_cmd[i].tau = 0 |
|||
|
|||
def LowStateMessageHandler(self, msg: LowState_): |
|||
self.low_state = msg |
|||
|
|||
def LowCmdWrite(self): |
|||
|
|||
if self.firstRun: |
|||
for i in range(12): |
|||
self.startPos[i] = self.low_state.motor_state[i].q |
|||
self.firstRun = False |
|||
|
|||
self.percent_1 += 1.0 / self.duration_1 |
|||
self.percent_1 = min(self.percent_1, 1) |
|||
if self.percent_1 < 1: |
|||
for i in range(12): |
|||
self.low_cmd.motor_cmd[i].q = (1 - self.percent_1) * self.startPos[i] + self.percent_1 * self.targetPos_1[i] |
|||
self.low_cmd.motor_cmd[i].dq = 0 |
|||
self.low_cmd.motor_cmd[i].kp = self.Kp |
|||
self.low_cmd.motor_cmd[i].kd = self.Kd |
|||
self.low_cmd.motor_cmd[i].tau = 0 |
|||
|
|||
if (self.percent_1 == 1) and (self.percent_2 <= 1): |
|||
self.percent_2 += 1.0 / self.duration_2 |
|||
self.percent_2 = min(self.percent_2, 1) |
|||
for i in range(12): |
|||
self.low_cmd.motor_cmd[i].q = (1 - self.percent_2) * self.targetPos_1[i] + self.percent_2 * self.targetPos_2[i] |
|||
self.low_cmd.motor_cmd[i].dq = 0 |
|||
self.low_cmd.motor_cmd[i].kp = self.Kp |
|||
self.low_cmd.motor_cmd[i].kd = self.Kd |
|||
self.low_cmd.motor_cmd[i].tau = 0 |
|||
|
|||
if (self.percent_1 == 1) and (self.percent_2 == 1) and (self.percent_3 < 1): |
|||
self.percent_3 += 1.0 / self.duration_3 |
|||
self.percent_3 = min(self.percent_3, 1) |
|||
for i in range(12): |
|||
self.low_cmd.motor_cmd[i].q = self.targetPos_2[i] |
|||
self.low_cmd.motor_cmd[i].dq = 0 |
|||
self.low_cmd.motor_cmd[i].kp = self.Kp |
|||
self.low_cmd.motor_cmd[i].kd = self.Kd |
|||
self.low_cmd.motor_cmd[i].tau = 0 |
|||
|
|||
if (self.percent_1 == 1) and (self.percent_2 == 1) and (self.percent_3 == 1) and (self.percent_4 <= 1): |
|||
self.percent_4 += 1.0 / self.duration_4 |
|||
self.percent_4 = min(self.percent_4, 1) |
|||
for i in range(12): |
|||
self.low_cmd.motor_cmd[i].q = (1 - self.percent_4) * self.targetPos_2[i] + self.percent_4 * self.targetPos_3[i] |
|||
self.low_cmd.motor_cmd[i].dq = 0 |
|||
self.low_cmd.motor_cmd[i].kp = self.Kp |
|||
self.low_cmd.motor_cmd[i].kd = self.Kd |
|||
self.low_cmd.motor_cmd[i].tau = 0 |
|||
|
|||
self.low_cmd.crc = self.crc.Crc(self.low_cmd) |
|||
self.lowcmd_publisher.Write(self.low_cmd) |
|||
|
|||
if __name__ == '__main__': |
|||
|
|||
print("WARNING: Please ensure there are no obstacles around the robot while running this example.") |
|||
input("Press Enter to continue...") |
|||
|
|||
if len(sys.argv)>1: |
|||
ChannelFactoryInitialize(0, sys.argv[1]) |
|||
else: |
|||
ChannelFactoryInitialize(0) |
|||
|
|||
custom = Custom() |
|||
custom.Init() |
|||
custom.Start() |
|||
|
|||
while True: |
|||
if custom.percent_4 == 1.0: |
|||
time.sleep(1) |
|||
print("Done!") |
|||
sys.exit(-1) |
|||
time.sleep(1) |
|||
@ -0,0 +1,20 @@ |
|||
LegID = { |
|||
"FR_0": 0, # Front right hip |
|||
"FR_1": 1, # Front right thigh |
|||
"FR_2": 2, # Front right calf |
|||
"FL_0": 3, |
|||
"FL_1": 4, |
|||
"FL_2": 5, |
|||
"RR_0": 6, |
|||
"RR_1": 7, |
|||
"RR_2": 8, |
|||
"RL_0": 9, |
|||
"RL_1": 10, |
|||
"RL_2": 11, |
|||
} |
|||
|
|||
HIGHLEVEL = 0xEE |
|||
LOWLEVEL = 0xFF |
|||
TRIGERLEVEL = 0xF0 |
|||
PosStopF = 2.146e9 |
|||
VelStopF = 16000.0 |
|||
@ -0,0 +1,51 @@ |
|||
from unitree_sdk2py.core.channel import ChannelFactoryInitialize |
|||
from unitree_sdk2py.b2.front_video.front_video_client import FrontVideoClient |
|||
from unitree_sdk2py.b2.back_video.back_video_client import BackVideoClient |
|||
import cv2 |
|||
import numpy as np |
|||
import sys |
|||
|
|||
def display_image(window_name, data): |
|||
# If data is a list, we need to convert it to a bytes object |
|||
if isinstance(data, list): |
|||
data = bytes(data) |
|||
|
|||
# Now convert to numpy image |
|||
image_data = np.frombuffer(data, dtype=np.uint8) |
|||
image = cv2.imdecode(image_data, cv2.IMREAD_COLOR) |
|||
if image is not None: |
|||
cv2.imshow(window_name, image) |
|||
|
|||
if __name__ == "__main__": |
|||
if len(sys.argv) > 1: |
|||
ChannelFactoryInitialize(0, sys.argv[1]) |
|||
else: |
|||
ChannelFactoryInitialize(0) |
|||
|
|||
frontCameraClient = FrontVideoClient() # Create a front camera video client |
|||
frontCameraClient.SetTimeout(3.0) |
|||
frontCameraClient.Init() |
|||
|
|||
backCameraClient = BackVideoClient() # Create a back camera video client |
|||
backCameraClient.SetTimeout(3.0) |
|||
backCameraClient.Init() |
|||
|
|||
# Loop to continuously fetch images |
|||
while True: |
|||
# Get front camera image |
|||
front_code, front_data = frontCameraClient.GetImageSample() |
|||
if front_code == 0: |
|||
display_image("Front Camera", front_data) |
|||
|
|||
# Get back camera image |
|||
back_code, back_data = backCameraClient.GetImageSample() |
|||
if back_code == 0: |
|||
display_image("Back Camera", back_data) |
|||
|
|||
# Press ESC to stop |
|||
if cv2.waitKey(20) == 27: |
|||
break |
|||
|
|||
# Clean up windows |
|||
cv2.destroyAllWindows() |
|||
|
|||
@ -0,0 +1,51 @@ |
|||
import time |
|||
import os |
|||
import sys |
|||
|
|||
from unitree_sdk2py.core.channel import ChannelFactoryInitialize |
|||
from unitree_sdk2py.b2.front_video.front_video_client import FrontVideoClient |
|||
from unitree_sdk2py.b2.back_video.back_video_client import BackVideoClient |
|||
|
|||
if __name__ == "__main__": |
|||
if len(sys.argv) > 1: |
|||
ChannelFactoryInitialize(0, sys.argv[1]) |
|||
else: |
|||
ChannelFactoryInitialize(0) |
|||
|
|||
# 创建前置相机客户端 |
|||
front_client = FrontVideoClient() |
|||
front_client.SetTimeout(3.0) |
|||
front_client.Init() |
|||
|
|||
# 创建后置相机客户端 |
|||
back_client = BackVideoClient() |
|||
back_client.SetTimeout(3.0) |
|||
back_client.Init() |
|||
|
|||
print("##################Get Front Camera Image###################") |
|||
# 获取前置相机图像 |
|||
front_code, front_data = front_client.GetImageSample() |
|||
|
|||
if front_code != 0: |
|||
print("Get front camera image error. Code:", front_code) |
|||
else: |
|||
front_image_name = "./front_img.jpg" |
|||
print("Front Image Saved as:", front_image_name) |
|||
|
|||
with open(front_image_name, "+wb") as f: |
|||
f.write(bytes(front_data)) |
|||
|
|||
print("##################Get Back Camera Image###################") |
|||
# 获取后置相机图像 |
|||
back_code, back_data = back_client.GetImageSample() |
|||
|
|||
if back_code != 0: |
|||
print("Get back camera image error. Code:", back_code) |
|||
else: |
|||
back_image_name = "./back_img.jpg" |
|||
print("Back Image Saved as:", back_image_name) |
|||
|
|||
with open(back_image_name, "+wb") as f: |
|||
f.write(bytes(back_data)) |
|||
|
|||
time.sleep(1) |
|||
@ -0,0 +1,101 @@ |
|||
import time |
|||
import sys |
|||
from unitree_sdk2py.core.channel import ChannelSubscriber, ChannelFactoryInitialize |
|||
from unitree_sdk2py.b2.sport.sport_client import SportClient |
|||
import math |
|||
from dataclasses import dataclass |
|||
|
|||
@dataclass |
|||
class TestOption: |
|||
name: str |
|||
id: int |
|||
|
|||
option_list = [ |
|||
TestOption(name="damp", id=0), |
|||
TestOption(name="stand_up", id=1), |
|||
TestOption(name="stand_down", id=2), |
|||
TestOption(name="move forward", id=3), |
|||
TestOption(name="move lateral", id=4), |
|||
TestOption(name="move rotate", id=5), |
|||
TestOption(name="stop_move", id=6), |
|||
TestOption(name="switch_gait", id=7), |
|||
TestOption(name="switch_gait", id=8), |
|||
TestOption(name="recovery", id=9) |
|||
] |
|||
|
|||
class UserInterface: |
|||
def __init__(self): |
|||
self.test_option_ = None |
|||
|
|||
def convert_to_int(self, input_str): |
|||
try: |
|||
return int(input_str) |
|||
except ValueError: |
|||
return None |
|||
|
|||
def terminal_handle(self): |
|||
input_str = input("Enter id or name: \n") |
|||
|
|||
if input_str == "list": |
|||
self.test_option_.name = None |
|||
self.test_option_.id = None |
|||
for option in option_list: |
|||
print(f"name: {option.name}, id: {option.id}") |
|||
return |
|||
|
|||
for option in option_list: |
|||
if input_str == option.name or self.convert_to_int(input_str) == option.id: |
|||
self.test_option_.name = option.name |
|||
self.test_option_.id = option.id |
|||
print(f"Test: {self.test_option_.name}, test_id: {self.test_option_.id}") |
|||
return |
|||
|
|||
print("No matching test option found.") |
|||
|
|||
if __name__ == "__main__": |
|||
if len(sys.argv) < 2: |
|||
print(f"Usage: python3 {sys.argv[0]} networkInterface") |
|||
sys.exit(-1) |
|||
|
|||
print("WARNING: Please ensure there are no obstacles around the robot while running this example.") |
|||
input("Press Enter to continue...") |
|||
|
|||
ChannelFactoryInitialize(0, sys.argv[1]) |
|||
|
|||
test_option = TestOption(name=None, id=None) |
|||
user_interface = UserInterface() |
|||
user_interface.test_option_ = test_option |
|||
|
|||
sport_client = SportClient() |
|||
sport_client.SetTimeout(10.0) |
|||
sport_client.Init() |
|||
|
|||
print("Input \"list\" to list all test option ...") |
|||
|
|||
while True: |
|||
user_interface.terminal_handle() |
|||
|
|||
print(f"Updated Test Option: Name = {test_option.name}, ID = {test_option.id}\n") |
|||
|
|||
if test_option.id == 0: |
|||
sport_client.Damp() |
|||
elif test_option.id == 1: |
|||
sport_client.StandUp() |
|||
elif test_option.id == 2: |
|||
sport_client.StandDown() |
|||
elif test_option.id == 3: |
|||
sport_client.Move(0.3,0,0) |
|||
elif test_option.id == 4: |
|||
sport_client.Move(0,0.3,0) |
|||
elif test_option.id == 5: |
|||
sport_client.Move(0,0,0.5) |
|||
elif test_option.id == 6: |
|||
sport_client.StopMove() |
|||
elif test_option.id == 7: |
|||
sport_client.SwitchGait(0) |
|||
elif test_option.id == 8: |
|||
sport_client.SwitchGait(1) |
|||
elif test_option.id == 9: |
|||
sport_client.RecoveryStand() |
|||
|
|||
time.sleep(1) |
|||
@ -0,0 +1,196 @@ |
|||
import time |
|||
import sys |
|||
|
|||
from unitree_sdk2py.core.channel import ChannelPublisher, ChannelFactoryInitialize |
|||
from unitree_sdk2py.core.channel import ChannelSubscriber, ChannelFactoryInitialize |
|||
from unitree_sdk2py.idl.default import unitree_go_msg_dds__LowCmd_ |
|||
from unitree_sdk2py.idl.default import unitree_go_msg_dds__LowState_ |
|||
from unitree_sdk2py.idl.unitree_go.msg.dds_ import LowCmd_ |
|||
from unitree_sdk2py.idl.unitree_go.msg.dds_ import LowState_ |
|||
from unitree_sdk2py.utils.crc import CRC |
|||
from unitree_sdk2py.utils.thread import RecurrentThread |
|||
import unitree_legged_const as b2w |
|||
from unitree_sdk2py.comm.motion_switcher.motion_switcher_client import MotionSwitcherClient |
|||
from unitree_sdk2py.b2.sport.sport_client import SportClient |
|||
|
|||
class Custom: |
|||
def __init__(self): |
|||
self.Kp = 1000.0 |
|||
self.Kd = 10.0 |
|||
self.time_consume = 0 |
|||
self.rate_count = 0 |
|||
self.sin_count = 0 |
|||
self.motiontime = 0 |
|||
self.dt = 0.002 |
|||
|
|||
self.low_cmd = unitree_go_msg_dds__LowCmd_() |
|||
self.low_state = None |
|||
|
|||
self.targetPos_1 = [0.0, 1.36, -2.65, 0.0, 1.36, -2.65, |
|||
-0.2, 1.36, -2.65, 0.2, 1.36, -2.65] |
|||
|
|||
self.targetPos_2 = [0.0, 0.67, -1.3, 0.0, 0.67, -1.3, |
|||
0.0, 0.67, -1.3, 0.0, 0.67, -1.3] |
|||
|
|||
self.targetPos_3 = [-0.65, 1.36, -2.65, 0.65, 1.36, -2.65, |
|||
-0.65, 1.36, -2.65, 0.65, 1.36, -2.65] |
|||
|
|||
self.startPos = [0.0] * 12 |
|||
self.duration_1 = 800 |
|||
self.duration_2 = 800 |
|||
self.duration_3 = 2000 |
|||
self.duration_4 = 1500 |
|||
self.percent_1 = 0 |
|||
self.percent_2 = 0 |
|||
self.percent_3 = 0 |
|||
self.percent_4 = 0 |
|||
|
|||
self.firstRun = True |
|||
self.done = False |
|||
|
|||
# thread handling |
|||
self.lowCmdWriteThreadPtr = None |
|||
|
|||
self.crc = CRC() |
|||
|
|||
def Init(self): |
|||
self.InitLowCmd() |
|||
|
|||
# create publisher # |
|||
self.lowcmd_publisher = ChannelPublisher("rt/lowcmd", LowCmd_) |
|||
self.lowcmd_publisher.Init() |
|||
|
|||
# create subscriber # |
|||
self.lowstate_subscriber = ChannelSubscriber("rt/lowstate", LowState_) |
|||
self.lowstate_subscriber.Init(self.LowStateMessageHandler, 10) |
|||
|
|||
self.sc = SportClient() |
|||
self.sc.SetTimeout(5.0) |
|||
self.sc.Init() |
|||
|
|||
self.msc = MotionSwitcherClient() |
|||
self.msc.SetTimeout(5.0) |
|||
self.msc.Init() |
|||
|
|||
status, result = self.msc.CheckMode() |
|||
while result['name']: |
|||
self.sc.StandDown() |
|||
self.msc.ReleaseMode() |
|||
status, result = self.msc.CheckMode() |
|||
time.sleep(1) |
|||
|
|||
def Start(self): |
|||
self.lowCmdWriteThreadPtr = RecurrentThread( |
|||
name="writebasiccmd", interval=self.dt, target=self.LowCmdWrite, |
|||
) |
|||
self.lowCmdWriteThreadPtr.Start() |
|||
|
|||
def InitLowCmd(self): |
|||
self.low_cmd.head[0] = 0xFE |
|||
self.low_cmd.head[1] = 0xEF |
|||
self.low_cmd.level_flag = 0xFF |
|||
self.low_cmd.gpio = 0 |
|||
for i in range(20): |
|||
self.low_cmd.motor_cmd[i].mode = 0x01 # (PMSM) mode |
|||
self.low_cmd.motor_cmd[i].q = b2w.PosStopF |
|||
self.low_cmd.motor_cmd[i].kp = 0 |
|||
self.low_cmd.motor_cmd[i].dq = b2w.VelStopF |
|||
self.low_cmd.motor_cmd[i].kd = 0 |
|||
self.low_cmd.motor_cmd[i].tau = 0 |
|||
|
|||
def LowStateMessageHandler(self, msg: LowState_): |
|||
self.low_state = msg |
|||
|
|||
def LowCmdWrite(self): |
|||
if self.firstRun: |
|||
for i in range(12): |
|||
self.startPos[i] = self.low_state.motor_state[i].q |
|||
self.firstRun = False |
|||
|
|||
self.percent_1 += 1.0 / self.duration_1 |
|||
self.percent_1 = min(self.percent_1, 1) |
|||
if self.percent_1 < 1: |
|||
for i in range(12): |
|||
self.low_cmd.motor_cmd[i].q = (1 - self.percent_1) * self.startPos[i] + self.percent_1 * self.targetPos_1[i] |
|||
self.low_cmd.motor_cmd[i].dq = 0 |
|||
self.low_cmd.motor_cmd[i].kp = self.Kp |
|||
self.low_cmd.motor_cmd[i].kd = self.Kd |
|||
self.low_cmd.motor_cmd[i].tau = 0 |
|||
|
|||
if (self.percent_1 == 1) and (self.percent_2 <= 1): |
|||
self.percent_2 += 1.0 / self.duration_2 |
|||
self.percent_2 = min(self.percent_2, 1) |
|||
for i in range(12): |
|||
self.low_cmd.motor_cmd[i].q = (1 - self.percent_2) * self.targetPos_1[i] + self.percent_2 * self.targetPos_2[i] |
|||
self.low_cmd.motor_cmd[i].dq = 0 |
|||
self.low_cmd.motor_cmd[i].kp = self.Kp |
|||
self.low_cmd.motor_cmd[i].kd = self.Kd |
|||
self.low_cmd.motor_cmd[i].tau = 0 |
|||
|
|||
if (self.percent_1 == 1) and (self.percent_2 == 1) and (self.percent_3 < 1): |
|||
self.percent_3 += 1.0 / self.duration_3 |
|||
self.percent_3 = min(self.percent_3, 1) |
|||
for i in range(12): |
|||
self.low_cmd.motor_cmd[i].q = self.targetPos_2[i] |
|||
self.low_cmd.motor_cmd[i].dq = 0 |
|||
self.low_cmd.motor_cmd[i].kp = self.Kp |
|||
self.low_cmd.motor_cmd[i].kd = self.Kd |
|||
self.low_cmd.motor_cmd[i].tau = 0 |
|||
|
|||
if self.percent_3 < 0.4: |
|||
for i in range(12, 16): |
|||
self.low_cmd.motor_cmd[i].q = 0 |
|||
self.low_cmd.motor_cmd[i].kp = 0 |
|||
self.low_cmd.motor_cmd[i].dq = 3 |
|||
self.low_cmd.motor_cmd[i].kd = self.Kd |
|||
self.low_cmd.motor_cmd[i].tau = 0 |
|||
|
|||
if 0.4 <= self.percent_3 < 0.8: |
|||
for i in range(12, 16): |
|||
self.low_cmd.motor_cmd[i].q = 0 |
|||
self.low_cmd.motor_cmd[i].kp = 0 |
|||
self.low_cmd.motor_cmd[i].dq = -3 |
|||
self.low_cmd.motor_cmd[i].kd = self.Kd |
|||
self.low_cmd.motor_cmd[i].tau = 0 |
|||
|
|||
if self.percent_3 >= 0.8: |
|||
for i in range(12, 16): |
|||
self.low_cmd.motor_cmd[i].q = 0 |
|||
self.low_cmd.motor_cmd[i].kp = 0 |
|||
self.low_cmd.motor_cmd[i].dq = 0 |
|||
self.low_cmd.motor_cmd[i].kd = self.Kd |
|||
self.low_cmd.motor_cmd[i].tau = 0 |
|||
|
|||
if (self.percent_1 == 1) and (self.percent_2 == 1) and (self.percent_3 == 1) and (self.percent_4 <= 1): |
|||
self.percent_4 += 1.0 / self.duration_4 |
|||
self.percent_4 = min(self.percent_4, 1) |
|||
for i in range(12): |
|||
self.low_cmd.motor_cmd[i].q = (1 - self.percent_4) * self.targetPos_2[i] + self.percent_4 * self.targetPos_3[i] |
|||
self.low_cmd.motor_cmd[i].dq = 0 |
|||
self.low_cmd.motor_cmd[i].kp = self.Kp |
|||
self.low_cmd.motor_cmd[i].kd = self.Kd |
|||
self.low_cmd.motor_cmd[i].tau = 0 |
|||
|
|||
self.low_cmd.crc = self.crc.Crc(self.low_cmd) |
|||
self.lowcmd_publisher.Write(self.low_cmd) |
|||
|
|||
if __name__ == '__main__': |
|||
|
|||
print("WARNING: Please ensure there are no obstacles around the robot while running this example.") |
|||
input("Press Enter to continue...") |
|||
|
|||
if len(sys.argv)>1: |
|||
ChannelFactoryInitialize(0, sys.argv[1]) |
|||
else: |
|||
ChannelFactoryInitialize(0) |
|||
|
|||
custom = Custom() |
|||
custom.Init() |
|||
custom.Start() |
|||
|
|||
while True: |
|||
if custom.percent_4 == 1.0: |
|||
time.sleep(1) |
|||
print("Done!") |
|||
sys.exit(-1) |
|||
time.sleep(1) |
|||
@ -0,0 +1,24 @@ |
|||
LegID = { |
|||
"FR_0": 0, # Front right hip |
|||
"FR_1": 1, # Front right thigh |
|||
"FR_2": 2, # Front right calf |
|||
"FL_0": 3, |
|||
"FL_1": 4, |
|||
"FL_2": 5, |
|||
"RR_0": 6, |
|||
"RR_1": 7, |
|||
"RR_2": 8, |
|||
"RL_0": 9, |
|||
"RL_1": 10, |
|||
"RL_2": 11, |
|||
"FR_w": 12, # Front right wheel |
|||
"FL_w": 13, # Front left wheel |
|||
"RR_w": 14, # Rear right wheel |
|||
"RL_w": 15, # Rear left wheel |
|||
} |
|||
|
|||
HIGHLEVEL = 0xEE |
|||
LOWLEVEL = 0xFF |
|||
TRIGERLEVEL = 0xF0 |
|||
PosStopF = 2.146e9 |
|||
VelStopF = 16000.0 |
|||
@ -0,0 +1,44 @@ |
|||
import time |
|||
import sys |
|||
from unitree_sdk2py.core.channel import ChannelSubscriber, ChannelFactoryInitialize |
|||
from unitree_sdk2py.g1.audio.g1_audio_client import AudioClient |
|||
from unitree_sdk2py.g1.loco.g1_loco_client import LocoClient |
|||
|
|||
if __name__ == "__main__": |
|||
if len(sys.argv) < 2: |
|||
print(f"Usage: python3 {sys.argv[0]} networkInterface") |
|||
sys.exit(-1) |
|||
|
|||
ChannelFactoryInitialize(0, sys.argv[1]) |
|||
|
|||
audio_client = AudioClient() |
|||
audio_client.SetTimeout(10.0) |
|||
audio_client.Init() |
|||
|
|||
sport_client = LocoClient() |
|||
sport_client.SetTimeout(10.0) |
|||
sport_client.Init() |
|||
|
|||
ret = audio_client.GetVolume() |
|||
print("debug GetVolume: ",ret) |
|||
|
|||
audio_client.SetVolume(85) |
|||
|
|||
ret = audio_client.GetVolume() |
|||
print("debug GetVolume: ",ret) |
|||
|
|||
sport_client.WaveHand() |
|||
|
|||
audio_client.TtsMaker("大家好!我是宇树科技人形机器人。语音开发测试例程运行成功! 很高兴认识你!",0) |
|||
time.sleep(8) |
|||
audio_client.TtsMaker("接下来测试灯带开发例程!",0) |
|||
time.sleep(1) |
|||
audio_client.LedControl(255,0,0) |
|||
time.sleep(1) |
|||
audio_client.LedControl(0,255,0) |
|||
time.sleep(1) |
|||
audio_client.LedControl(0,0,255) |
|||
|
|||
time.sleep(3) |
|||
audio_client.TtsMaker("测试完毕,谢谢大家!",0) |
|||
|
|||
@ -0,0 +1,192 @@ |
|||
import time |
|||
import sys |
|||
|
|||
from unitree_sdk2py.core.channel import ChannelPublisher, ChannelFactoryInitialize |
|||
from unitree_sdk2py.core.channel import ChannelSubscriber, ChannelFactoryInitialize |
|||
from unitree_sdk2py.idl.default import unitree_hg_msg_dds__LowCmd_ |
|||
from unitree_sdk2py.idl.default import unitree_hg_msg_dds__LowState_ |
|||
from unitree_sdk2py.idl.unitree_hg.msg.dds_ import LowCmd_ |
|||
from unitree_sdk2py.idl.unitree_hg.msg.dds_ import LowState_ |
|||
from unitree_sdk2py.utils.crc import CRC |
|||
from unitree_sdk2py.utils.thread import RecurrentThread |
|||
from unitree_sdk2py.comm.motion_switcher.motion_switcher_client import MotionSwitcherClient |
|||
|
|||
import numpy as np |
|||
|
|||
kPi = 3.141592654 |
|||
kPi_2 = 1.57079632 |
|||
|
|||
class G1JointIndex: |
|||
# Left leg |
|||
LeftHipPitch = 0 |
|||
LeftHipRoll = 1 |
|||
LeftHipYaw = 2 |
|||
LeftKnee = 3 |
|||
LeftAnklePitch = 4 |
|||
LeftAnkleB = 4 |
|||
LeftAnkleRoll = 5 |
|||
LeftAnkleA = 5 |
|||
|
|||
# Right leg |
|||
RightHipPitch = 6 |
|||
RightHipRoll = 7 |
|||
RightHipYaw = 8 |
|||
RightKnee = 9 |
|||
RightAnklePitch = 10 |
|||
RightAnkleB = 10 |
|||
RightAnkleRoll = 11 |
|||
RightAnkleA = 11 |
|||
|
|||
WaistYaw = 12 |
|||
WaistRoll = 13 # NOTE: INVALID for g1 23dof/29dof with waist locked |
|||
WaistA = 13 # NOTE: INVALID for g1 23dof/29dof with waist locked |
|||
WaistPitch = 14 # NOTE: INVALID for g1 23dof/29dof with waist locked |
|||
WaistB = 14 # NOTE: INVALID for g1 23dof/29dof with waist locked |
|||
|
|||
# Left arm |
|||
LeftShoulderPitch = 15 |
|||
LeftShoulderRoll = 16 |
|||
LeftShoulderYaw = 17 |
|||
LeftElbow = 18 |
|||
LeftWristRoll = 19 |
|||
LeftWristPitch = 20 # NOTE: INVALID for g1 23dof |
|||
LeftWristYaw = 21 # NOTE: INVALID for g1 23dof |
|||
|
|||
# Right arm |
|||
RightShoulderPitch = 22 |
|||
RightShoulderRoll = 23 |
|||
RightShoulderYaw = 24 |
|||
RightElbow = 25 |
|||
RightWristRoll = 26 |
|||
RightWristPitch = 27 # NOTE: INVALID for g1 23dof |
|||
RightWristYaw = 28 # NOTE: INVALID for g1 23dof |
|||
|
|||
kNotUsedJoint = 29 # NOTE: Weight |
|||
|
|||
class Custom: |
|||
def __init__(self): |
|||
self.time_ = 0.0 |
|||
self.control_dt_ = 0.02 |
|||
self.duration_ = 3.0 |
|||
self.counter_ = 0 |
|||
self.weight = 0. |
|||
self.weight_rate = 0.2 |
|||
self.kp = 60. |
|||
self.kd = 1.5 |
|||
self.dq = 0. |
|||
self.tau_ff = 0. |
|||
self.mode_machine_ = 0 |
|||
self.low_cmd = unitree_hg_msg_dds__LowCmd_() |
|||
self.low_state = None |
|||
self.first_update_low_state = False |
|||
self.crc = CRC() |
|||
self.done = False |
|||
|
|||
self.target_pos = [ |
|||
0.0, kPi_2, 0.0, kPi_2, 0.0, |
|||
0.0, -kPi_2, 0.0, kPi_2, 0.0, |
|||
0.0, 0.0, 0.0 |
|||
] |
|||
|
|||
self.arm_joints = [ |
|||
G1JointIndex.LeftShoulderPitch, G1JointIndex.LeftShoulderRoll, |
|||
G1JointIndex.LeftShoulderYaw, G1JointIndex.LeftElbow, |
|||
G1JointIndex.LeftWristRoll, |
|||
G1JointIndex.RightShoulderPitch, G1JointIndex.RightShoulderRoll, |
|||
G1JointIndex.RightShoulderYaw, G1JointIndex.RightElbow, |
|||
G1JointIndex.RightWristRoll, |
|||
G1JointIndex.WaistYaw, |
|||
G1JointIndex.WaistRoll, |
|||
G1JointIndex.WaistPitch |
|||
] |
|||
|
|||
def Init(self): |
|||
# create publisher # |
|||
self.arm_sdk_publisher = ChannelPublisher("rt/arm_sdk", LowCmd_) |
|||
self.arm_sdk_publisher.Init() |
|||
|
|||
# create subscriber # |
|||
self.lowstate_subscriber = ChannelSubscriber("rt/lowstate", LowState_) |
|||
self.lowstate_subscriber.Init(self.LowStateHandler, 10) |
|||
|
|||
def Start(self): |
|||
self.lowCmdWriteThreadPtr = RecurrentThread( |
|||
interval=self.control_dt_, target=self.LowCmdWrite, name="control" |
|||
) |
|||
while self.first_update_low_state == False: |
|||
time.sleep(1) |
|||
|
|||
if self.first_update_low_state == True: |
|||
self.lowCmdWriteThreadPtr.Start() |
|||
|
|||
def LowStateHandler(self, msg: LowState_): |
|||
self.low_state = msg |
|||
|
|||
if self.first_update_low_state == False: |
|||
self.first_update_low_state = True |
|||
|
|||
def LowCmdWrite(self): |
|||
self.time_ += self.control_dt_ |
|||
|
|||
if self.time_ < self.duration_ : |
|||
# [Stage 1]: set robot to zero posture |
|||
self.low_cmd.motor_cmd[G1JointIndex.kNotUsedJoint].q = 1 # 1:Enable arm_sdk, 0:Disable arm_sdk |
|||
for i,joint in enumerate(self.arm_joints): |
|||
ratio = np.clip(self.time_ / self.duration_, 0.0, 1.0) |
|||
self.low_cmd.motor_cmd[joint].tau = 0. |
|||
self.low_cmd.motor_cmd[joint].q = (1.0 - ratio) * self.low_state.motor_state[joint].q |
|||
self.low_cmd.motor_cmd[joint].dq = 0. |
|||
self.low_cmd.motor_cmd[joint].kp = self.kp |
|||
self.low_cmd.motor_cmd[joint].kd = self.kd |
|||
|
|||
elif self.time_ < self.duration_ * 3 : |
|||
# [Stage 2]: lift arms up |
|||
for i,joint in enumerate(self.arm_joints): |
|||
ratio = np.clip((self.time_ - self.duration_) / (self.duration_ * 2), 0.0, 1.0) |
|||
self.low_cmd.motor_cmd[joint].tau = 0. |
|||
self.low_cmd.motor_cmd[joint].q = ratio * self.target_pos[i] + (1.0 - ratio) * self.low_state.motor_state[joint].q |
|||
self.low_cmd.motor_cmd[joint].dq = 0. |
|||
self.low_cmd.motor_cmd[joint].kp = self.kp |
|||
self.low_cmd.motor_cmd[joint].kd = self.kd |
|||
|
|||
elif self.time_ < self.duration_ * 6 : |
|||
# [Stage 3]: set robot back to zero posture |
|||
for i,joint in enumerate(self.arm_joints): |
|||
ratio = np.clip((self.time_ - self.duration_*3) / (self.duration_ * 3), 0.0, 1.0) |
|||
self.low_cmd.motor_cmd[joint].tau = 0. |
|||
self.low_cmd.motor_cmd[joint].q = (1.0 - ratio) * self.low_state.motor_state[joint].q |
|||
self.low_cmd.motor_cmd[joint].dq = 0. |
|||
self.low_cmd.motor_cmd[joint].kp = self.kp |
|||
self.low_cmd.motor_cmd[joint].kd = self.kd |
|||
|
|||
elif self.time_ < self.duration_ * 7 : |
|||
# [Stage 4]: release arm_sdk |
|||
for i,joint in enumerate(self.arm_joints): |
|||
ratio = np.clip((self.time_ - self.duration_*6) / (self.duration_), 0.0, 1.0) |
|||
self.low_cmd.motor_cmd[G1JointIndex.kNotUsedJoint].q = (1 - ratio) # 1:Enable arm_sdk, 0:Disable arm_sdk |
|||
|
|||
else: |
|||
self.done = True |
|||
|
|||
self.low_cmd.crc = self.crc.Crc(self.low_cmd) |
|||
self.arm_sdk_publisher.Write(self.low_cmd) |
|||
|
|||
if __name__ == '__main__': |
|||
|
|||
print("WARNING: Please ensure there are no obstacles around the robot while running this example.") |
|||
input("Press Enter to continue...") |
|||
|
|||
if len(sys.argv)>1: |
|||
ChannelFactoryInitialize(0, sys.argv[1]) |
|||
else: |
|||
ChannelFactoryInitialize(0) |
|||
|
|||
custom = Custom() |
|||
custom.Init() |
|||
custom.Start() |
|||
|
|||
while True: |
|||
time.sleep(1) |
|||
if custom.done: |
|||
print("Done!") |
|||
sys.exit(-1) |
|||
@ -0,0 +1,194 @@ |
|||
import time |
|||
import sys |
|||
|
|||
from unitree_sdk2py.core.channel import ChannelPublisher, ChannelFactoryInitialize |
|||
from unitree_sdk2py.core.channel import ChannelSubscriber, ChannelFactoryInitialize |
|||
from unitree_sdk2py.idl.default import unitree_hg_msg_dds__LowCmd_ |
|||
from unitree_sdk2py.idl.default import unitree_hg_msg_dds__LowState_ |
|||
from unitree_sdk2py.idl.unitree_hg.msg.dds_ import LowCmd_ |
|||
from unitree_sdk2py.idl.unitree_hg.msg.dds_ import LowState_ |
|||
from unitree_sdk2py.utils.crc import CRC |
|||
from unitree_sdk2py.utils.thread import RecurrentThread |
|||
from unitree_sdk2py.comm.motion_switcher.motion_switcher_client import MotionSwitcherClient |
|||
|
|||
import numpy as np |
|||
|
|||
kPi = 3.141592654 |
|||
kPi_2 = 1.57079632 |
|||
|
|||
class G1JointIndex: |
|||
# Left leg |
|||
LeftHipPitch = 0 |
|||
LeftHipRoll = 1 |
|||
LeftHipYaw = 2 |
|||
LeftKnee = 3 |
|||
LeftAnklePitch = 4 |
|||
LeftAnkleB = 4 |
|||
LeftAnkleRoll = 5 |
|||
LeftAnkleA = 5 |
|||
|
|||
# Right leg |
|||
RightHipPitch = 6 |
|||
RightHipRoll = 7 |
|||
RightHipYaw = 8 |
|||
RightKnee = 9 |
|||
RightAnklePitch = 10 |
|||
RightAnkleB = 10 |
|||
RightAnkleRoll = 11 |
|||
RightAnkleA = 11 |
|||
|
|||
WaistYaw = 12 |
|||
WaistRoll = 13 # NOTE: INVALID for g1 23dof/29dof with waist locked |
|||
WaistA = 13 # NOTE: INVALID for g1 23dof/29dof with waist locked |
|||
WaistPitch = 14 # NOTE: INVALID for g1 23dof/29dof with waist locked |
|||
WaistB = 14 # NOTE: INVALID for g1 23dof/29dof with waist locked |
|||
|
|||
# Left arm |
|||
LeftShoulderPitch = 15 |
|||
LeftShoulderRoll = 16 |
|||
LeftShoulderYaw = 17 |
|||
LeftElbow = 18 |
|||
LeftWristRoll = 19 |
|||
LeftWristPitch = 20 # NOTE: INVALID for g1 23dof |
|||
LeftWristYaw = 21 # NOTE: INVALID for g1 23dof |
|||
|
|||
# Right arm |
|||
RightShoulderPitch = 22 |
|||
RightShoulderRoll = 23 |
|||
RightShoulderYaw = 24 |
|||
RightElbow = 25 |
|||
RightWristRoll = 26 |
|||
RightWristPitch = 27 # NOTE: INVALID for g1 23dof |
|||
RightWristYaw = 28 # NOTE: INVALID for g1 23dof |
|||
|
|||
kNotUsedJoint = 29 # NOTE: Weight |
|||
|
|||
class Custom: |
|||
def __init__(self): |
|||
self.time_ = 0.0 |
|||
self.control_dt_ = 0.02 |
|||
self.duration_ = 3.0 |
|||
self.counter_ = 0 |
|||
self.weight = 0. |
|||
self.weight_rate = 0.2 |
|||
self.kp = 60. |
|||
self.kd = 1.5 |
|||
self.dq = 0. |
|||
self.tau_ff = 0. |
|||
self.mode_machine_ = 0 |
|||
self.low_cmd = unitree_hg_msg_dds__LowCmd_() |
|||
self.low_state = None |
|||
self.first_update_low_state = False |
|||
self.crc = CRC() |
|||
self.done = False |
|||
|
|||
self.target_pos = [ |
|||
0., kPi_2, 0., kPi_2, 0., 0., 0., |
|||
0., -kPi_2, 0., kPi_2, 0., 0., 0., |
|||
0, 0, 0 |
|||
] |
|||
|
|||
self.arm_joints = [ |
|||
G1JointIndex.LeftShoulderPitch, G1JointIndex.LeftShoulderRoll, |
|||
G1JointIndex.LeftShoulderYaw, G1JointIndex.LeftElbow, |
|||
G1JointIndex.LeftWristRoll, G1JointIndex.LeftWristPitch, |
|||
G1JointIndex.LeftWristYaw, |
|||
G1JointIndex.RightShoulderPitch, G1JointIndex.RightShoulderRoll, |
|||
G1JointIndex.RightShoulderYaw, G1JointIndex.RightElbow, |
|||
G1JointIndex.RightWristRoll, G1JointIndex.RightWristPitch, |
|||
G1JointIndex.RightWristYaw, |
|||
G1JointIndex.WaistYaw, |
|||
G1JointIndex.WaistRoll, |
|||
G1JointIndex.WaistPitch |
|||
] |
|||
|
|||
def Init(self): |
|||
# create publisher # |
|||
self.arm_sdk_publisher = ChannelPublisher("rt/arm_sdk", LowCmd_) |
|||
self.arm_sdk_publisher.Init() |
|||
|
|||
# create subscriber # |
|||
self.lowstate_subscriber = ChannelSubscriber("rt/lowstate", LowState_) |
|||
self.lowstate_subscriber.Init(self.LowStateHandler, 10) |
|||
|
|||
def Start(self): |
|||
self.lowCmdWriteThreadPtr = RecurrentThread( |
|||
interval=self.control_dt_, target=self.LowCmdWrite, name="control" |
|||
) |
|||
while self.first_update_low_state == False: |
|||
time.sleep(1) |
|||
|
|||
if self.first_update_low_state == True: |
|||
self.lowCmdWriteThreadPtr.Start() |
|||
|
|||
def LowStateHandler(self, msg: LowState_): |
|||
self.low_state = msg |
|||
|
|||
if self.first_update_low_state == False: |
|||
self.first_update_low_state = True |
|||
|
|||
def LowCmdWrite(self): |
|||
self.time_ += self.control_dt_ |
|||
|
|||
if self.time_ < self.duration_ : |
|||
# [Stage 1]: set robot to zero posture |
|||
self.low_cmd.motor_cmd[G1JointIndex.kNotUsedJoint].q = 1 # 1:Enable arm_sdk, 0:Disable arm_sdk |
|||
for i,joint in enumerate(self.arm_joints): |
|||
ratio = np.clip(self.time_ / self.duration_, 0.0, 1.0) |
|||
self.low_cmd.motor_cmd[joint].tau = 0. |
|||
self.low_cmd.motor_cmd[joint].q = (1.0 - ratio) * self.low_state.motor_state[joint].q |
|||
self.low_cmd.motor_cmd[joint].dq = 0. |
|||
self.low_cmd.motor_cmd[joint].kp = self.kp |
|||
self.low_cmd.motor_cmd[joint].kd = self.kd |
|||
|
|||
elif self.time_ < self.duration_ * 3 : |
|||
# [Stage 2]: lift arms up |
|||
for i,joint in enumerate(self.arm_joints): |
|||
ratio = np.clip((self.time_ - self.duration_) / (self.duration_ * 2), 0.0, 1.0) |
|||
self.low_cmd.motor_cmd[joint].tau = 0. |
|||
self.low_cmd.motor_cmd[joint].q = ratio * self.target_pos[i] + (1.0 - ratio) * self.low_state.motor_state[joint].q |
|||
self.low_cmd.motor_cmd[joint].dq = 0. |
|||
self.low_cmd.motor_cmd[joint].kp = self.kp |
|||
self.low_cmd.motor_cmd[joint].kd = self.kd |
|||
|
|||
elif self.time_ < self.duration_ * 6 : |
|||
# [Stage 3]: set robot back to zero posture |
|||
for i,joint in enumerate(self.arm_joints): |
|||
ratio = np.clip((self.time_ - self.duration_*3) / (self.duration_ * 3), 0.0, 1.0) |
|||
self.low_cmd.motor_cmd[joint].tau = 0. |
|||
self.low_cmd.motor_cmd[joint].q = (1.0 - ratio) * self.low_state.motor_state[joint].q |
|||
self.low_cmd.motor_cmd[joint].dq = 0. |
|||
self.low_cmd.motor_cmd[joint].kp = self.kp |
|||
self.low_cmd.motor_cmd[joint].kd = self.kd |
|||
|
|||
elif self.time_ < self.duration_ * 7 : |
|||
# [Stage 4]: release arm_sdk |
|||
for i,joint in enumerate(self.arm_joints): |
|||
ratio = np.clip((self.time_ - self.duration_*6) / (self.duration_), 0.0, 1.0) |
|||
self.low_cmd.motor_cmd[G1JointIndex.kNotUsedJoint].q = (1 - ratio) # 1:Enable arm_sdk, 0:Disable arm_sdk |
|||
|
|||
else: |
|||
self.done = True |
|||
|
|||
self.low_cmd.crc = self.crc.Crc(self.low_cmd) |
|||
self.arm_sdk_publisher.Write(self.low_cmd) |
|||
|
|||
if __name__ == '__main__': |
|||
|
|||
print("WARNING: Please ensure there are no obstacles around the robot while running this example.") |
|||
input("Press Enter to continue...") |
|||
|
|||
if len(sys.argv)>1: |
|||
ChannelFactoryInitialize(0, sys.argv[1]) |
|||
else: |
|||
ChannelFactoryInitialize(0) |
|||
|
|||
custom = Custom() |
|||
custom.Init() |
|||
custom.Start() |
|||
|
|||
while True: |
|||
time.sleep(1) |
|||
if custom.done: |
|||
print("Done!") |
|||
sys.exit(-1) |
|||
@ -0,0 +1,117 @@ |
|||
import time |
|||
import sys |
|||
from unitree_sdk2py.core.channel import ChannelSubscriber, ChannelFactoryInitialize |
|||
from unitree_sdk2py.idl.default import unitree_go_msg_dds__SportModeState_ |
|||
from unitree_sdk2py.idl.unitree_go.msg.dds_ import SportModeState_ |
|||
from unitree_sdk2py.g1.loco.g1_loco_client import LocoClient |
|||
import math |
|||
from dataclasses import dataclass |
|||
|
|||
@dataclass |
|||
class TestOption: |
|||
name: str |
|||
id: int |
|||
|
|||
option_list = [ |
|||
TestOption(name="damp", id=0), |
|||
TestOption(name="Squat2StandUp", id=1), |
|||
TestOption(name="StandUp2Squat", id=2), |
|||
TestOption(name="move forward", id=3), |
|||
TestOption(name="move lateral", id=4), |
|||
TestOption(name="move rotate", id=5), |
|||
TestOption(name="low stand", id=6), |
|||
TestOption(name="high stand", id=7), |
|||
TestOption(name="zero torque", id=8), |
|||
TestOption(name="wave hand1", id=9), # wave hand without turning around |
|||
TestOption(name="wave hand2", id=10), # wave hand and trun around |
|||
TestOption(name="shake hand", id=11), |
|||
TestOption(name="Lie2StandUp", id=12), |
|||
] |
|||
|
|||
class UserInterface: |
|||
def __init__(self): |
|||
self.test_option_ = None |
|||
|
|||
def convert_to_int(self, input_str): |
|||
try: |
|||
return int(input_str) |
|||
except ValueError: |
|||
return None |
|||
|
|||
def terminal_handle(self): |
|||
input_str = input("Enter id or name: \n") |
|||
|
|||
if input_str == "list": |
|||
self.test_option_.name = None |
|||
self.test_option_.id = None |
|||
for option in option_list: |
|||
print(f"{option.name}, id: {option.id}") |
|||
return |
|||
|
|||
for option in option_list: |
|||
if input_str == option.name or self.convert_to_int(input_str) == option.id: |
|||
self.test_option_.name = option.name |
|||
self.test_option_.id = option.id |
|||
print(f"Test: {self.test_option_.name}, test_id: {self.test_option_.id}") |
|||
return |
|||
|
|||
print("No matching test option found.") |
|||
|
|||
if __name__ == "__main__": |
|||
if len(sys.argv) < 2: |
|||
print(f"Usage: python3 {sys.argv[0]} networkInterface") |
|||
sys.exit(-1) |
|||
|
|||
print("WARNING: Please ensure there are no obstacles around the robot while running this example.") |
|||
input("Press Enter to continue...") |
|||
|
|||
ChannelFactoryInitialize(0, sys.argv[1]) |
|||
|
|||
test_option = TestOption(name=None, id=None) |
|||
user_interface = UserInterface() |
|||
user_interface.test_option_ = test_option |
|||
|
|||
sport_client = LocoClient() |
|||
sport_client.SetTimeout(10.0) |
|||
sport_client.Init() |
|||
|
|||
print("Input \"list\" to list all test option ...") |
|||
while True: |
|||
user_interface.terminal_handle() |
|||
|
|||
print(f"Updated Test Option: Name = {test_option.name}, ID = {test_option.id}") |
|||
|
|||
if test_option.id == 0: |
|||
sport_client.Damp() |
|||
elif test_option.id == 1: |
|||
sport_client.Damp() |
|||
time.sleep(0.5) |
|||
sport_client.Squat2StandUp() |
|||
elif test_option.id == 2: |
|||
sport_client.StandUp2Squat() |
|||
elif test_option.id == 3: |
|||
sport_client.Move(0.3,0,0) |
|||
elif test_option.id == 4: |
|||
sport_client.Move(0,0.3,0) |
|||
elif test_option.id == 5: |
|||
sport_client.Move(0,0,0.3) |
|||
elif test_option.id == 6: |
|||
sport_client.LowStand() |
|||
elif test_option.id == 7: |
|||
sport_client.HighStand() |
|||
elif test_option.id == 8: |
|||
sport_client.ZeroTorque() |
|||
elif test_option.id == 9: |
|||
sport_client.WaveHand() |
|||
elif test_option.id == 10: |
|||
sport_client.WaveHand(True) |
|||
elif test_option.id == 11: |
|||
sport_client.ShakeHand() |
|||
time.sleep(3) |
|||
sport_client.ShakeHand() |
|||
elif test_option.id == 12: |
|||
sport_client.Damp() |
|||
time.sleep(0.5) |
|||
sport_client.Lie2StandUp() # When using the Lie2StandUp function, ensure that the robot faces up and the ground is hard, flat and rough. |
|||
|
|||
time.sleep(1) |
|||
@ -0,0 +1,205 @@ |
|||
import time |
|||
import sys |
|||
|
|||
from unitree_sdk2py.core.channel import ChannelPublisher, ChannelFactoryInitialize |
|||
from unitree_sdk2py.core.channel import ChannelSubscriber, ChannelFactoryInitialize |
|||
from unitree_sdk2py.idl.default import unitree_hg_msg_dds__LowCmd_ |
|||
from unitree_sdk2py.idl.default import unitree_hg_msg_dds__LowState_ |
|||
from unitree_sdk2py.idl.unitree_hg.msg.dds_ import LowCmd_ |
|||
from unitree_sdk2py.idl.unitree_hg.msg.dds_ import LowState_ |
|||
from unitree_sdk2py.utils.crc import CRC |
|||
from unitree_sdk2py.utils.thread import RecurrentThread |
|||
from unitree_sdk2py.comm.motion_switcher.motion_switcher_client import MotionSwitcherClient |
|||
|
|||
import numpy as np |
|||
|
|||
G1_NUM_MOTOR = 29 |
|||
|
|||
Kp = [ |
|||
60, 60, 60, 100, 40, 40, # legs |
|||
60, 60, 60, 100, 40, 40, # legs |
|||
60, 40, 40, # waist |
|||
40, 40, 40, 40, 40, 40, 40, # arms |
|||
40, 40, 40, 40, 40, 40, 40 # arms |
|||
] |
|||
|
|||
Kd = [ |
|||
1, 1, 1, 2, 1, 1, # legs |
|||
1, 1, 1, 2, 1, 1, # legs |
|||
1, 1, 1, # waist |
|||
1, 1, 1, 1, 1, 1, 1, # arms |
|||
1, 1, 1, 1, 1, 1, 1 # arms |
|||
] |
|||
|
|||
class G1JointIndex: |
|||
LeftHipPitch = 0 |
|||
LeftHipRoll = 1 |
|||
LeftHipYaw = 2 |
|||
LeftKnee = 3 |
|||
LeftAnklePitch = 4 |
|||
LeftAnkleB = 4 |
|||
LeftAnkleRoll = 5 |
|||
LeftAnkleA = 5 |
|||
RightHipPitch = 6 |
|||
RightHipRoll = 7 |
|||
RightHipYaw = 8 |
|||
RightKnee = 9 |
|||
RightAnklePitch = 10 |
|||
RightAnkleB = 10 |
|||
RightAnkleRoll = 11 |
|||
RightAnkleA = 11 |
|||
WaistYaw = 12 |
|||
WaistRoll = 13 # NOTE: INVALID for g1 23dof/29dof with waist locked |
|||
WaistA = 13 # NOTE: INVALID for g1 23dof/29dof with waist locked |
|||
WaistPitch = 14 # NOTE: INVALID for g1 23dof/29dof with waist locked |
|||
WaistB = 14 # NOTE: INVALID for g1 23dof/29dof with waist locked |
|||
LeftShoulderPitch = 15 |
|||
LeftShoulderRoll = 16 |
|||
LeftShoulderYaw = 17 |
|||
LeftElbow = 18 |
|||
LeftWristRoll = 19 |
|||
LeftWristPitch = 20 # NOTE: INVALID for g1 23dof |
|||
LeftWristYaw = 21 # NOTE: INVALID for g1 23dof |
|||
RightShoulderPitch = 22 |
|||
RightShoulderRoll = 23 |
|||
RightShoulderYaw = 24 |
|||
RightElbow = 25 |
|||
RightWristRoll = 26 |
|||
RightWristPitch = 27 # NOTE: INVALID for g1 23dof |
|||
RightWristYaw = 28 # NOTE: INVALID for g1 23dof |
|||
|
|||
|
|||
class Mode: |
|||
PR = 0 # Series Control for Pitch/Roll Joints |
|||
AB = 1 # Parallel Control for A/B Joints |
|||
|
|||
class Custom: |
|||
def __init__(self): |
|||
self.time_ = 0.0 |
|||
self.control_dt_ = 0.002 # [2ms] |
|||
self.duration_ = 3.0 # [3 s] |
|||
self.counter_ = 0 |
|||
self.mode_pr_ = Mode.PR |
|||
self.mode_machine_ = 0 |
|||
self.low_cmd = unitree_hg_msg_dds__LowCmd_() |
|||
self.low_state = None |
|||
self.update_mode_machine_ = False |
|||
self.crc = CRC() |
|||
|
|||
def Init(self): |
|||
self.msc = MotionSwitcherClient() |
|||
self.msc.SetTimeout(5.0) |
|||
self.msc.Init() |
|||
|
|||
status, result = self.msc.CheckMode() |
|||
while result['name']: |
|||
self.msc.ReleaseMode() |
|||
status, result = self.msc.CheckMode() |
|||
time.sleep(1) |
|||
|
|||
# create publisher # |
|||
self.lowcmd_publisher_ = ChannelPublisher("rt/lowcmd", LowCmd_) |
|||
self.lowcmd_publisher_.Init() |
|||
|
|||
# create subscriber # |
|||
self.lowstate_subscriber = ChannelSubscriber("rt/lowstate", LowState_) |
|||
self.lowstate_subscriber.Init(self.LowStateHandler, 10) |
|||
|
|||
def Start(self): |
|||
self.lowCmdWriteThreadPtr = RecurrentThread( |
|||
interval=self.control_dt_, target=self.LowCmdWrite, name="control" |
|||
) |
|||
while self.update_mode_machine_ == False: |
|||
time.sleep(1) |
|||
|
|||
if self.update_mode_machine_ == True: |
|||
self.lowCmdWriteThreadPtr.Start() |
|||
|
|||
def LowStateHandler(self, msg: LowState_): |
|||
self.low_state = msg |
|||
|
|||
if self.update_mode_machine_ == False: |
|||
self.mode_machine_ = self.low_state.mode_machine |
|||
self.update_mode_machine_ = True |
|||
|
|||
self.counter_ +=1 |
|||
if (self.counter_ % 500 == 0) : |
|||
self.counter_ = 0 |
|||
print(self.low_state.imu_state.rpy) |
|||
|
|||
def LowCmdWrite(self): |
|||
self.time_ += self.control_dt_ |
|||
|
|||
if self.time_ < self.duration_ : |
|||
# [Stage 1]: set robot to zero posture |
|||
for i in range(G1_NUM_MOTOR): |
|||
ratio = np.clip(self.time_ / self.duration_, 0.0, 1.0) |
|||
self.low_cmd.mode_pr = Mode.PR |
|||
self.low_cmd.mode_machine = self.mode_machine_ |
|||
self.low_cmd.motor_cmd[i].mode = 1 # 1:Enable, 0:Disable |
|||
self.low_cmd.motor_cmd[i].tau = 0. |
|||
self.low_cmd.motor_cmd[i].q = (1.0 - ratio) * self.low_state.motor_state[i].q |
|||
self.low_cmd.motor_cmd[i].dq = 0. |
|||
self.low_cmd.motor_cmd[i].kp = Kp[i] |
|||
self.low_cmd.motor_cmd[i].kd = Kd[i] |
|||
|
|||
elif self.time_ < self.duration_ * 2 : |
|||
# [Stage 2]: swing ankle using PR mode |
|||
max_P = np.pi * 30.0 / 180.0 |
|||
max_R = np.pi * 10.0 / 180.0 |
|||
t = self.time_ - self.duration_ |
|||
L_P_des = max_P * np.sin(2.0 * np.pi * t) |
|||
L_R_des = max_R * np.sin(2.0 * np.pi * t) |
|||
R_P_des = max_P * np.sin(2.0 * np.pi * t) |
|||
R_R_des = -max_R * np.sin(2.0 * np.pi * t) |
|||
|
|||
self.low_cmd.mode_pr = Mode.PR |
|||
self.low_cmd.mode_machine = self.mode_machine_ |
|||
self.low_cmd.motor_cmd[G1JointIndex.LeftAnklePitch].q = L_P_des |
|||
self.low_cmd.motor_cmd[G1JointIndex.LeftAnkleRoll].q = L_R_des |
|||
self.low_cmd.motor_cmd[G1JointIndex.RightAnklePitch].q = R_P_des |
|||
self.low_cmd.motor_cmd[G1JointIndex.RightAnkleRoll].q = R_R_des |
|||
|
|||
else : |
|||
# [Stage 3]: swing ankle using AB mode |
|||
max_A = np.pi * 30.0 / 180.0 |
|||
max_B = np.pi * 10.0 / 180.0 |
|||
t = self.time_ - self.duration_ * 2 |
|||
L_A_des = max_A * np.sin(2.0 * np.pi * t) |
|||
L_B_des = max_B * np.sin(2.0 * np.pi * t + np.pi) |
|||
R_A_des = -max_A * np.sin(2.0 * np.pi * t) |
|||
R_B_des = -max_B * np.sin(2.0 * np.pi * t + np.pi) |
|||
|
|||
self.low_cmd.mode_pr = Mode.AB |
|||
self.low_cmd.mode_machine = self.mode_machine_ |
|||
self.low_cmd.motor_cmd[G1JointIndex.LeftAnkleA].q = L_A_des |
|||
self.low_cmd.motor_cmd[G1JointIndex.LeftAnkleB].q = L_B_des |
|||
self.low_cmd.motor_cmd[G1JointIndex.RightAnkleA].q = R_A_des |
|||
self.low_cmd.motor_cmd[G1JointIndex.RightAnkleB].q = R_B_des |
|||
|
|||
max_WristYaw = np.pi * 30.0 / 180.0 |
|||
L_WristYaw_des = max_WristYaw * np.sin(2.0 * np.pi * t) |
|||
R_WristYaw_des = max_WristYaw * np.sin(2.0 * np.pi * t) |
|||
self.low_cmd.motor_cmd[G1JointIndex.LeftWristRoll].q = L_WristYaw_des |
|||
self.low_cmd.motor_cmd[G1JointIndex.RightWristRoll].q = R_WristYaw_des |
|||
|
|||
|
|||
self.low_cmd.crc = self.crc.Crc(self.low_cmd) |
|||
self.lowcmd_publisher_.Write(self.low_cmd) |
|||
|
|||
if __name__ == '__main__': |
|||
|
|||
print("WARNING: Please ensure there are no obstacles around the robot while running this example.") |
|||
input("Press Enter to continue...") |
|||
|
|||
if len(sys.argv)>1: |
|||
ChannelFactoryInitialize(0, sys.argv[1]) |
|||
else: |
|||
ChannelFactoryInitialize(0) |
|||
|
|||
custom = Custom() |
|||
custom.Init() |
|||
custom.Start() |
|||
|
|||
while True: |
|||
time.sleep(1) |
|||
@ -0,0 +1,225 @@ |
|||
import time |
|||
import sys |
|||
|
|||
import numpy as np |
|||
|
|||
from unitree_sdk2py.core.channel import ChannelPublisher, ChannelSubscriber, ChannelFactoryInitialize |
|||
from unitree_sdk2py.idl.unitree_hg.msg.dds_ import HandCmd_, HandState_ |
|||
from unitree_sdk2py.idl.default import unitree_hg_msg_dds__HandCmd_ |
|||
from unitree_sdk2py.utils.crc import CRC |
|||
from unitree_sdk2py.utils.thread import RecurrentThread |
|||
|
|||
MOTOR_NUM_HAND = 7 |
|||
|
|||
Kp = [0.5] * MOTOR_NUM_HAND |
|||
Kd = [0.1] * MOTOR_NUM_HAND |
|||
|
|||
Kp[0] = 2.0 |
|||
|
|||
maxTorqueLimits_left = [1.05, 1.05, 1.75, 0.0, 0.0, 0.0, 0.0] |
|||
minTorqueLimits_left = [-1.05, -0.72, 0.0, -1.57, -1.75, -1.57, -1.75] |
|||
maxTorqueLimits_right = [1.05, 0.74, 0.0, 1.57, 1.75, 1.57, 1.75] |
|||
minTorqueLimits_right = [-1.05, -1.05, -1.75, 0.0, 0.0, 0.0, 0.0] |
|||
|
|||
|
|||
def make_hand_mode(motor_index): |
|||
status = 0x01 |
|||
timeout = 0x01 |
|||
mode = (motor_index & 0x0F) |
|||
mode |= (status << 4) # bits [4..6] |
|||
mode |= (timeout << 7) # bit 7 |
|||
return mode |
|||
|
|||
|
|||
class HandControl: |
|||
def __init__(self, network_interface="enp36s0f1"): |
|||
ChannelFactoryInitialize(0, network_interface) |
|||
|
|||
self.left_cmd_pub = ChannelPublisher("rt/dex3/left/cmd", HandCmd_) |
|||
self.right_cmd_pub = ChannelPublisher("rt/dex3/right/cmd", HandCmd_) |
|||
|
|||
self.left_state_sub = ChannelSubscriber("rt/dex3/left/state", HandState_) |
|||
self.right_state_sub = ChannelSubscriber("rt/dex3/right/state", HandState_) |
|||
|
|||
self.left_cmd_pub.Init() |
|||
self.right_cmd_pub.Init() |
|||
|
|||
self.left_state_sub.Init(self.left_state_handler, 10) |
|||
self.right_state_sub.Init(self.right_state_handler, 10) |
|||
|
|||
self.left_state = None |
|||
self.right_state = None |
|||
|
|||
self.crc = CRC() |
|||
|
|||
# Control loop timing |
|||
self.control_dt = 0.01 # 10 ms |
|||
self.time_ = 0.0 |
|||
self.stage_time = 3.0 # 3s per stage |
|||
|
|||
# To let the code run once we start: |
|||
self.run_flag = False |
|||
|
|||
self.counter = 0 |
|||
|
|||
def left_state_handler(self, msg: HandState_): |
|||
self.left_state = msg |
|||
|
|||
# self.counter +=1 |
|||
# if (self.counter % 1000 == 0) : |
|||
# self.counter = 0 |
|||
# print('Left hand state:') |
|||
# for i in range(MOTOR_NUM_HAND): |
|||
# print(180/np.pi*self.left_state.motor_state[i].q) |
|||
|
|||
def right_state_handler(self, msg: HandState_): |
|||
self.right_state = msg |
|||
|
|||
self.counter += 1 |
|||
if (self.counter % 1000 == 0): |
|||
self.counter = 0 |
|||
print('Right hand state:') |
|||
for i in range(MOTOR_NUM_HAND): |
|||
print(180 / np.pi * self.right_state.motor_state[i].q) |
|||
|
|||
def start(self): |
|||
""" |
|||
Kick off the main control loop thread. |
|||
""" |
|||
self.run_flag = True |
|||
self.control_thread = RecurrentThread(interval=self.control_dt, |
|||
target=self.hand_control_loop, |
|||
name="HandControlLoop") |
|||
self.control_thread.Start() |
|||
|
|||
def hand_control_loop(self): |
|||
""" |
|||
This gets called at a fixed rate (every self.control_dt seconds). |
|||
We'll demonstrate 3 stages of motion: |
|||
1) Move from current position to 'zero' (or some nominal) in 3s |
|||
2) Sinusoidal motion in stage 2 (3s) |
|||
3) Another motion in stage 3 (final) |
|||
""" |
|||
if not self.run_flag: |
|||
return |
|||
|
|||
self.time_ += self.control_dt |
|||
t = self.time_ |
|||
cmd_left = unitree_hg_msg_dds__HandCmd_() |
|||
cmd_right = unitree_hg_msg_dds__HandCmd_() |
|||
|
|||
# cmd_left.motor_cmd.resize(MOTOR_NUM_HAND) |
|||
# cmd_right.motor_cmd.resize(MOTOR_NUM_HAND) |
|||
|
|||
# Prepare stage times |
|||
stage1_end = self.stage_time |
|||
stage2_end = self.stage_time * 2.0 |
|||
|
|||
# We'll fetch current positions for left and right if available |
|||
# so we can blend from actual state to zero. |
|||
# If we haven't gotten a state yet, default to 0. |
|||
left_q_now = [0.0] * MOTOR_NUM_HAND |
|||
right_q_now = [0.0] * MOTOR_NUM_HAND |
|||
|
|||
if self.left_state is not None: |
|||
for i in range(MOTOR_NUM_HAND): |
|||
left_q_now[i] = self.left_state.motor_state[i].q |
|||
|
|||
if self.right_state is not None: |
|||
for i in range(MOTOR_NUM_HAND): |
|||
right_q_now[i] = self.right_state.motor_state[i].q |
|||
|
|||
left_q_desired = np.deg2rad([50.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]) |
|||
|
|||
right_q_desired = np.deg2rad([50.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]) |
|||
|
|||
# Decide the desired position for each stage: |
|||
if t < stage1_end: |
|||
# Stage 1: Move from the current joint positions to zero in [0..3s] |
|||
ratio = np.clip(t / stage1_end, 0.0, 1.0) |
|||
# Simple linear blend: final = (1-ratio)*initial + ratio*0 |
|||
left_q_des = np.zeros(MOTOR_NUM_HAND) |
|||
right_q_des = np.zeros(MOTOR_NUM_HAND) |
|||
for i in range(MOTOR_NUM_HAND): |
|||
left_q_des[i] = (1.0 - ratio) * (left_q_now[i] - left_q_desired[i]) + left_q_desired[i] |
|||
right_q_des[i] = (1.0 - ratio) * (right_q_now[i] - right_q_desired[i]) + right_q_desired[i] |
|||
|
|||
else: |
|||
# Stage 2: Some sinusoidal wave |
|||
# We'll wave only the first 2 or 3 motors, just as a demo |
|||
dt2 = t - stage1_end |
|||
freq = 1.0 # 1 Hz |
|||
amp = 0.3 # ~ 0.3 rad amplitude |
|||
|
|||
left_q_des = left_q_desired |
|||
right_q_des = right_q_desired |
|||
|
|||
# E.g. wave motor 0 and 1: |
|||
left_q_des[0] += amp * np.sin(2 * np.pi * freq * dt2) |
|||
left_q_des[1] += amp * (1 - np.cos(2 * np.pi * freq * dt2)) |
|||
left_q_des[2] += amp * (1 - np.cos(2 * np.pi * freq * dt2)) |
|||
right_q_des[0] += amp * np.sin(2 * np.pi * freq * dt2) |
|||
right_q_des[1] += amp * (np.cos(2 * np.pi * freq * dt2) - 1) |
|||
right_q_des[2] += amp * (np.cos(2 * np.pi * freq * dt2) - 1) |
|||
|
|||
freqA = 2.0 |
|||
freqB = 2.0 |
|||
ampA = 0.2 |
|||
ampB = 0.4 |
|||
|
|||
left_q_des[3] += ampA * (np.cos(2 * np.pi * freqA * dt2) - 1) |
|||
left_q_des[4] += ampB * (np.cos(2 * np.pi * freqB * dt2) - 1) |
|||
left_q_des[5] += ampA * (np.cos(2 * np.pi * freqA * dt2) - 1) |
|||
left_q_des[6] += ampB * (np.cos(2 * np.pi * freqB * dt2) - 1) |
|||
right_q_des[3] += ampA * (1 - np.cos(2 * np.pi * freqA * dt2)) |
|||
right_q_des[4] += ampB * (1 - np.cos(2 * np.pi * freqB * dt2)) |
|||
right_q_des[5] += ampA * (1 - np.cos(2 * np.pi * freqA * dt2)) |
|||
right_q_des[6] += ampB * (1 - np.cos(2 * np.pi * freqB * dt2)) |
|||
|
|||
# Fill in the commands |
|||
for i in range(MOTOR_NUM_HAND): |
|||
# Build the bitfield mode (see your C++ example) |
|||
mode_val = make_hand_mode(i) |
|||
|
|||
# Left |
|||
cmd_left.motor_cmd[i].mode = mode_val |
|||
cmd_left.motor_cmd[i].q = left_q_des[i] |
|||
cmd_left.motor_cmd[i].dq = 0.0 |
|||
cmd_left.motor_cmd[i].tau = 0.0 |
|||
cmd_left.motor_cmd[i].kp = Kp[i] |
|||
cmd_left.motor_cmd[i].kd = Kd[i] |
|||
|
|||
# Right |
|||
cmd_right.motor_cmd[i].mode = mode_val |
|||
cmd_right.motor_cmd[i].q = right_q_des[i] |
|||
cmd_right.motor_cmd[i].dq = 0.0 |
|||
cmd_right.motor_cmd[i].tau = 0.0 |
|||
cmd_right.motor_cmd[i].kp = Kp[i] |
|||
cmd_right.motor_cmd[i].kd = Kd[i] |
|||
|
|||
# Compute CRC if your firmware requires it |
|||
# cmd_left.crc = self.crc.Crc(cmd_left) |
|||
# cmd_right.crc = self.crc.Crc(cmd_right) |
|||
|
|||
# Publish |
|||
self.left_cmd_pub.Write(cmd_left) |
|||
self.right_cmd_pub.Write(cmd_right) |
|||
|
|||
|
|||
if __name__ == "__main__": |
|||
print("WARNING: Make sure your robot’s hands can move freely before running.") |
|||
input("Press Enter to continue...") |
|||
|
|||
# Optionally pass a specific interface name, e.g. "enp37s0f0" or "eth0" |
|||
if len(sys.argv) > 1: |
|||
net_if = sys.argv[1] |
|||
else: |
|||
net_if = "enp36s0f1" |
|||
|
|||
# Create and start |
|||
hand_control = HandControl(network_interface=net_if) |
|||
hand_control.start() |
|||
|
|||
# Just wait |
|||
while True: |
|||
time.sleep(1) |
|||
@ -0,0 +1,5 @@ |
|||
This example is a test of Unitree G1/H1-2 robot. |
|||
|
|||
**Note:** |
|||
idl/unitree_go is used for Unitree Go2/B2/H1/B2w/Go2w robots |
|||
idl/unitree_hg is used for Unitree G1/H1-2 robots |
|||
@ -0,0 +1,41 @@ |
|||
from unitree_sdk2py.core.channel import ChannelFactoryInitialize |
|||
from unitree_sdk2py.go2.video.video_client import VideoClient |
|||
import cv2 |
|||
import numpy as np |
|||
import sys |
|||
|
|||
|
|||
if __name__ == "__main__": |
|||
if len(sys.argv)>1: |
|||
ChannelFactoryInitialize(0, sys.argv[1]) |
|||
else: |
|||
ChannelFactoryInitialize(0) |
|||
|
|||
client = VideoClient() # Create a video client |
|||
client.SetTimeout(3.0) |
|||
client.Init() |
|||
|
|||
code, data = client.GetImageSample() |
|||
|
|||
# Request normal when code==0 |
|||
while code == 0: |
|||
# Get Image data from Go2 robot |
|||
code, data = client.GetImageSample() |
|||
|
|||
# Convert to numpy image |
|||
image_data = np.frombuffer(bytes(data), dtype=np.uint8) |
|||
image = cv2.imdecode(image_data, cv2.IMREAD_COLOR) |
|||
|
|||
# Display image |
|||
cv2.imshow("front_camera", image) |
|||
# Press ESC to stop |
|||
if cv2.waitKey(20) == 27: |
|||
break |
|||
|
|||
if code != 0: |
|||
print("Get image sample error. code:", code) |
|||
else: |
|||
# Capture an image |
|||
cv2.imwrite("front_image.jpg", image) |
|||
|
|||
cv2.destroyWindow("front_camera") |
|||
@ -0,0 +1,30 @@ |
|||
import time |
|||
import os |
|||
import sys |
|||
|
|||
from unitree_sdk2py.core.channel import ChannelFactoryInitialize |
|||
from unitree_sdk2py.go2.video.video_client import VideoClient |
|||
|
|||
if __name__ == "__main__": |
|||
if len(sys.argv)>1: |
|||
ChannelFactoryInitialize(0, sys.argv[1]) |
|||
else: |
|||
ChannelFactoryInitialize(0) |
|||
|
|||
client = VideoClient() |
|||
client.SetTimeout(3.0) |
|||
client.Init() |
|||
|
|||
print("##################GetImageSample###################") |
|||
code, data = client.GetImageSample() |
|||
|
|||
if code != 0: |
|||
print("get image sample error. code:", code) |
|||
else: |
|||
imageName = "./img.jpg" |
|||
print("ImageName:", imageName) |
|||
|
|||
with open(imageName, "+wb") as f: |
|||
f.write(bytes(data)) |
|||
|
|||
time.sleep(1) |
|||
@ -0,0 +1,170 @@ |
|||
import time |
|||
import sys |
|||
from unitree_sdk2py.core.channel import ChannelSubscriber, ChannelFactoryInitialize |
|||
from unitree_sdk2py.idl.default import unitree_go_msg_dds__SportModeState_ |
|||
from unitree_sdk2py.idl.unitree_go.msg.dds_ import SportModeState_ |
|||
from unitree_sdk2py.go2.sport.sport_client import ( |
|||
SportClient, |
|||
PathPoint, |
|||
SPORT_PATH_POINT_SIZE, |
|||
) |
|||
import math |
|||
from dataclasses import dataclass |
|||
|
|||
@dataclass |
|||
class TestOption: |
|||
name: str |
|||
id: int |
|||
|
|||
option_list = [ |
|||
TestOption(name="damp", id=0), |
|||
TestOption(name="stand_up", id=1), |
|||
TestOption(name="stand_down", id=2), |
|||
TestOption(name="move forward", id=3), |
|||
TestOption(name="move lateral", id=4), |
|||
TestOption(name="move rotate", id=5), |
|||
TestOption(name="stop_move", id=6), |
|||
TestOption(name="switch_gait", id=7), |
|||
TestOption(name="switch_gait", id=8), |
|||
TestOption(name="balanced stand", id=9), |
|||
TestOption(name="recovery", id=10), |
|||
TestOption(name="recovery", id=10), |
|||
TestOption(name="left flip", id=11), |
|||
TestOption(name="back flip", id=12), |
|||
TestOption(name="free walk", id=13), |
|||
TestOption(name="free bound", id=14), |
|||
TestOption(name="free avoid", id=15), |
|||
TestOption(name="walk stair", id=16), |
|||
TestOption(name="walk upright", id=17), |
|||
TestOption(name="cross step", id=18), |
|||
TestOption(name="free jump", id=19) |
|||
] |
|||
|
|||
class UserInterface: |
|||
def __init__(self): |
|||
self.test_option_ = None |
|||
|
|||
def convert_to_int(self, input_str): |
|||
try: |
|||
return int(input_str) |
|||
except ValueError: |
|||
return None |
|||
|
|||
def terminal_handle(self): |
|||
input_str = input("Enter id or name: \n") |
|||
|
|||
if input_str == "list": |
|||
self.test_option_.name = None |
|||
self.test_option_.id = None |
|||
for option in option_list: |
|||
print(f"{option.name}, id: {option.id}") |
|||
return |
|||
|
|||
for option in option_list: |
|||
if input_str == option.name or self.convert_to_int(input_str) == option.id: |
|||
self.test_option_.name = option.name |
|||
self.test_option_.id = option.id |
|||
print(f"Test: {self.test_option_.name}, test_id: {self.test_option_.id}") |
|||
return |
|||
|
|||
print("No matching test option found.") |
|||
|
|||
if __name__ == "__main__": |
|||
|
|||
if len(sys.argv) < 2: |
|||
print(f"Usage: python3 {sys.argv[0]} networkInterface") |
|||
sys.exit(-1) |
|||
|
|||
print("WARNING: Please ensure there are no obstacles around the robot while running this example.") |
|||
input("Press Enter to continue...") |
|||
|
|||
ChannelFactoryInitialize(0, sys.argv[1]) |
|||
|
|||
test_option = TestOption(name=None, id=None) |
|||
user_interface = UserInterface() |
|||
user_interface.test_option_ = test_option |
|||
|
|||
sport_client = SportClient() |
|||
sport_client.SetTimeout(10.0) |
|||
sport_client.Init() |
|||
while True: |
|||
|
|||
user_interface.terminal_handle() |
|||
|
|||
print(f"Updated Test Option: Name = {test_option.name}, ID = {test_option.id}\n") |
|||
|
|||
if test_option.id == 0: |
|||
sport_client.Damp() |
|||
elif test_option.id == 1: |
|||
sport_client.StandUp() |
|||
elif test_option.id == 2: |
|||
sport_client.StandDown() |
|||
elif test_option.id == 3: |
|||
sport_client.Move(0.3,0,0) |
|||
elif test_option.id == 4: |
|||
sport_client.Move(0,0.3,0) |
|||
elif test_option.id == 5: |
|||
sport_client.Move(0,0,0.5) |
|||
elif test_option.id == 6: |
|||
sport_client.StopMove() |
|||
elif test_option.id == 7: |
|||
sport_client.SwitchGait(0) |
|||
elif test_option.id == 8: |
|||
sport_client.SwitchGait(1) |
|||
elif test_option.id == 9: |
|||
sport_client.BalanceStand() |
|||
elif test_option.id == 10: |
|||
sport_client.RecoveryStand() |
|||
elif test_option.id == 11: |
|||
ret = sport_client.LeftFlip() |
|||
print("ret: ",ret) |
|||
elif test_option.id == 12: |
|||
ret = sport_client.BackFlip() |
|||
print("ret: ",ret) |
|||
elif test_option.id == 13: |
|||
ret = sport_client.FreeWalk(True) |
|||
print("ret: ",ret) |
|||
elif test_option.id == 14: |
|||
ret = sport_client.FreeBound(True) |
|||
print("ret: ",ret) |
|||
time.sleep(2) |
|||
ret = sport_client.FreeBound(False) |
|||
print("ret: ",ret) |
|||
elif test_option.id == 14: |
|||
ret = sport_client.FreeBound(True) |
|||
print("ret: ",ret) |
|||
time.sleep(2) |
|||
ret = sport_client.FreeBound(False) |
|||
print("ret: ",ret) |
|||
elif test_option.id == 15: |
|||
ret = sport_client.FreeAvoid(True) |
|||
print("ret: ",ret) |
|||
time.sleep(2) |
|||
ret = sport_client.FreeAvoid(False) |
|||
print("ret: ",ret) |
|||
elif test_option.id == 16: |
|||
ret = sport_client.WalkStair(True) |
|||
print("ret: ",ret) |
|||
time.sleep(10) |
|||
ret = sport_client.WalkStair(False) |
|||
print("ret: ",ret) |
|||
elif test_option.id == 17: |
|||
ret = sport_client.WalkUpright(True) |
|||
print("ret: ",ret) |
|||
time.sleep(4) |
|||
ret = sport_client.WalkUpright(False) |
|||
print("ret: ",ret) |
|||
elif test_option.id == 18: |
|||
ret = sport_client.CrossStep(True) |
|||
print("ret: ",ret) |
|||
time.sleep(4) |
|||
ret = sport_client.CrossStep(False) |
|||
print("ret: ",ret) |
|||
elif test_option.id == 19: |
|||
ret = sport_client.FreeJump(True) |
|||
print("ret: ",ret) |
|||
time.sleep(4) |
|||
ret = sport_client.FreeJump(False) |
|||
print("ret: ",ret) |
|||
|
|||
time.sleep(1) |
|||
@ -0,0 +1,39 @@ |
|||
import time |
|||
import sys |
|||
|
|||
from unitree_sdk2py.core.channel import ChannelPublisher, ChannelFactoryInitialize |
|||
from unitree_sdk2py.core.channel import ChannelSubscriber, ChannelFactoryInitialize |
|||
from unitree_sdk2py.idl.std_msgs.msg.dds_ import String_ |
|||
from unitree_sdk2py.idl.default import std_msgs_msg_dds__String_ |
|||
|
|||
class Custom: |
|||
def __init__(self): |
|||
# create publisher # |
|||
self.publisher = ChannelPublisher("rt/utlidar/switch", String_) |
|||
self.publisher.Init() |
|||
self.low_cmd = std_msgs_msg_dds__String_() |
|||
|
|||
def go2_utlidar_switch(self,status): |
|||
if status == "OFF": |
|||
self.low_cmd.data = "OFF" |
|||
elif status == "ON": |
|||
self.low_cmd.data = "ON" |
|||
|
|||
self.publisher.Write(self.low_cmd) |
|||
|
|||
|
|||
if __name__ == '__main__': |
|||
|
|||
print("WARNING: Please ensure there are no obstacles around the robot while running this example.") |
|||
input("Press Enter to continue...") |
|||
|
|||
if len(sys.argv)>1: |
|||
ChannelFactoryInitialize(0, sys.argv[1]) |
|||
else: |
|||
ChannelFactoryInitialize(0) |
|||
|
|||
custom = Custom() |
|||
custom.go2_utlidar_switch("OFF") |
|||
|
|||
|
|||
|
|||
@ -0,0 +1,176 @@ |
|||
import time |
|||
import sys |
|||
|
|||
from unitree_sdk2py.core.channel import ChannelPublisher, ChannelFactoryInitialize |
|||
from unitree_sdk2py.core.channel import ChannelSubscriber, ChannelFactoryInitialize |
|||
from unitree_sdk2py.idl.default import unitree_go_msg_dds__LowCmd_ |
|||
from unitree_sdk2py.idl.default import unitree_go_msg_dds__LowState_ |
|||
from unitree_sdk2py.idl.unitree_go.msg.dds_ import LowCmd_ |
|||
from unitree_sdk2py.idl.unitree_go.msg.dds_ import LowState_ |
|||
from unitree_sdk2py.utils.crc import CRC |
|||
from unitree_sdk2py.utils.thread import RecurrentThread |
|||
import unitree_legged_const as go2 |
|||
from unitree_sdk2py.comm.motion_switcher.motion_switcher_client import MotionSwitcherClient |
|||
from unitree_sdk2py.go2.sport.sport_client import SportClient |
|||
|
|||
class Custom: |
|||
def __init__(self): |
|||
self.Kp = 60.0 |
|||
self.Kd = 5.0 |
|||
self.time_consume = 0 |
|||
self.rate_count = 0 |
|||
self.sin_count = 0 |
|||
self.motiontime = 0 |
|||
self.dt = 0.002 # 0.001~0.01 |
|||
|
|||
self.low_cmd = unitree_go_msg_dds__LowCmd_() |
|||
self.low_state = None |
|||
|
|||
self._targetPos_1 = [0.0, 1.36, -2.65, 0.0, 1.36, -2.65, |
|||
-0.2, 1.36, -2.65, 0.2, 1.36, -2.65] |
|||
self._targetPos_2 = [0.0, 0.67, -1.3, 0.0, 0.67, -1.3, |
|||
0.0, 0.67, -1.3, 0.0, 0.67, -1.3] |
|||
self._targetPos_3 = [-0.35, 1.36, -2.65, 0.35, 1.36, -2.65, |
|||
-0.5, 1.36, -2.65, 0.5, 1.36, -2.65] |
|||
|
|||
self.startPos = [0.0] * 12 |
|||
self.duration_1 = 500 |
|||
self.duration_2 = 500 |
|||
self.duration_3 = 1000 |
|||
self.duration_4 = 900 |
|||
self.percent_1 = 0 |
|||
self.percent_2 = 0 |
|||
self.percent_3 = 0 |
|||
self.percent_4 = 0 |
|||
|
|||
self.firstRun = True |
|||
self.done = False |
|||
|
|||
# thread handling |
|||
self.lowCmdWriteThreadPtr = None |
|||
|
|||
self.crc = CRC() |
|||
|
|||
# Public methods |
|||
def Init(self): |
|||
self.InitLowCmd() |
|||
|
|||
# create publisher # |
|||
self.lowcmd_publisher = ChannelPublisher("rt/lowcmd", LowCmd_) |
|||
self.lowcmd_publisher.Init() |
|||
|
|||
# create subscriber # |
|||
self.lowstate_subscriber = ChannelSubscriber("rt/lowstate", LowState_) |
|||
self.lowstate_subscriber.Init(self.LowStateMessageHandler, 10) |
|||
|
|||
self.sc = SportClient() |
|||
self.sc.SetTimeout(5.0) |
|||
self.sc.Init() |
|||
|
|||
self.msc = MotionSwitcherClient() |
|||
self.msc.SetTimeout(5.0) |
|||
self.msc.Init() |
|||
|
|||
status, result = self.msc.CheckMode() |
|||
while result['name']: |
|||
self.sc.StandDown() |
|||
self.msc.ReleaseMode() |
|||
status, result = self.msc.CheckMode() |
|||
time.sleep(1) |
|||
|
|||
def Start(self): |
|||
self.lowCmdWriteThreadPtr = RecurrentThread( |
|||
interval=0.002, target=self.LowCmdWrite, name="writebasiccmd" |
|||
) |
|||
self.lowCmdWriteThreadPtr.Start() |
|||
|
|||
# Private methods |
|||
def InitLowCmd(self): |
|||
self.low_cmd.head[0]=0xFE |
|||
self.low_cmd.head[1]=0xEF |
|||
self.low_cmd.level_flag = 0xFF |
|||
self.low_cmd.gpio = 0 |
|||
for i in range(20): |
|||
self.low_cmd.motor_cmd[i].mode = 0x01 # (PMSM) mode |
|||
self.low_cmd.motor_cmd[i].q= go2.PosStopF |
|||
self.low_cmd.motor_cmd[i].kp = 0 |
|||
self.low_cmd.motor_cmd[i].dq = go2.VelStopF |
|||
self.low_cmd.motor_cmd[i].kd = 0 |
|||
self.low_cmd.motor_cmd[i].tau = 0 |
|||
|
|||
def LowStateMessageHandler(self, msg: LowState_): |
|||
self.low_state = msg |
|||
# print("FR_0 motor state: ", msg.motor_state[go2.LegID["FR_0"]]) |
|||
# print("IMU state: ", msg.imu_state) |
|||
# print("Battery state: voltage: ", msg.power_v, "current: ", msg.power_a) |
|||
|
|||
def LowCmdWrite(self): |
|||
|
|||
if self.firstRun: |
|||
for i in range(12): |
|||
self.startPos[i] = self.low_state.motor_state[i].q |
|||
self.firstRun = False |
|||
|
|||
self.percent_1 += 1.0 / self.duration_1 |
|||
self.percent_1 = min(self.percent_1, 1) |
|||
if self.percent_1 < 1: |
|||
for i in range(12): |
|||
self.low_cmd.motor_cmd[i].q = (1 - self.percent_1) * self.startPos[i] + self.percent_1 * self._targetPos_1[i] |
|||
self.low_cmd.motor_cmd[i].dq = 0 |
|||
self.low_cmd.motor_cmd[i].kp = self.Kp |
|||
self.low_cmd.motor_cmd[i].kd = self.Kd |
|||
self.low_cmd.motor_cmd[i].tau = 0 |
|||
|
|||
if (self.percent_1 == 1) and (self.percent_2 <= 1): |
|||
self.percent_2 += 1.0 / self.duration_2 |
|||
self.percent_2 = min(self.percent_2, 1) |
|||
for i in range(12): |
|||
self.low_cmd.motor_cmd[i].q = (1 - self.percent_2) * self._targetPos_1[i] + self.percent_2 * self._targetPos_2[i] |
|||
self.low_cmd.motor_cmd[i].dq = 0 |
|||
self.low_cmd.motor_cmd[i].kp = self.Kp |
|||
self.low_cmd.motor_cmd[i].kd = self.Kd |
|||
self.low_cmd.motor_cmd[i].tau = 0 |
|||
|
|||
if (self.percent_1 == 1) and (self.percent_2 == 1) and (self.percent_3 < 1): |
|||
self.percent_3 += 1.0 / self.duration_3 |
|||
self.percent_3 = min(self.percent_3, 1) |
|||
for i in range(12): |
|||
self.low_cmd.motor_cmd[i].q = self._targetPos_2[i] |
|||
self.low_cmd.motor_cmd[i].dq = 0 |
|||
self.low_cmd.motor_cmd[i].kp = self.Kp |
|||
self.low_cmd.motor_cmd[i].kd = self.Kd |
|||
self.low_cmd.motor_cmd[i].tau = 0 |
|||
|
|||
if (self.percent_1 == 1) and (self.percent_2 == 1) and (self.percent_3 == 1) and (self.percent_4 <= 1): |
|||
self.percent_4 += 1.0 / self.duration_4 |
|||
self.percent_4 = min(self.percent_4, 1) |
|||
for i in range(12): |
|||
self.low_cmd.motor_cmd[i].q = (1 - self.percent_4) * self._targetPos_2[i] + self.percent_4 * self._targetPos_3[i] |
|||
self.low_cmd.motor_cmd[i].dq = 0 |
|||
self.low_cmd.motor_cmd[i].kp = self.Kp |
|||
self.low_cmd.motor_cmd[i].kd = self.Kd |
|||
self.low_cmd.motor_cmd[i].tau = 0 |
|||
|
|||
self.low_cmd.crc = self.crc.Crc(self.low_cmd) |
|||
self.lowcmd_publisher.Write(self.low_cmd) |
|||
|
|||
if __name__ == '__main__': |
|||
|
|||
print("WARNING: Please ensure there are no obstacles around the robot while running this example.") |
|||
input("Press Enter to continue...") |
|||
|
|||
if len(sys.argv)>1: |
|||
ChannelFactoryInitialize(0, sys.argv[1]) |
|||
else: |
|||
ChannelFactoryInitialize(0) |
|||
|
|||
custom = Custom() |
|||
custom.Init() |
|||
custom.Start() |
|||
|
|||
while True: |
|||
if custom.percent_4 == 1.0: |
|||
time.sleep(1) |
|||
print("Done!") |
|||
sys.exit(-1) |
|||
time.sleep(1) |
|||
@ -0,0 +1,20 @@ |
|||
LegID = { |
|||
"FR_0": 0, # Front right hip |
|||
"FR_1": 1, # Front right thigh |
|||
"FR_2": 2, # Front right calf |
|||
"FL_0": 3, |
|||
"FL_1": 4, |
|||
"FL_2": 5, |
|||
"RR_0": 6, |
|||
"RR_1": 7, |
|||
"RR_2": 8, |
|||
"RL_0": 9, |
|||
"RL_1": 10, |
|||
"RL_2": 11, |
|||
} |
|||
|
|||
HIGHLEVEL = 0xEE |
|||
LOWLEVEL = 0xFF |
|||
TRIGERLEVEL = 0xF0 |
|||
PosStopF = 2.146e9 |
|||
VelStopF = 16000.0 |
|||
@ -0,0 +1,99 @@ |
|||
import time |
|||
import sys |
|||
from unitree_sdk2py.core.channel import ChannelSubscriber, ChannelFactoryInitialize |
|||
from unitree_sdk2py.idl.default import unitree_go_msg_dds__SportModeState_ |
|||
from unitree_sdk2py.idl.unitree_go.msg.dds_ import SportModeState_ |
|||
from unitree_sdk2py.go2.sport.sport_client import SportClient |
|||
import math |
|||
from dataclasses import dataclass |
|||
|
|||
@dataclass |
|||
class TestOption: |
|||
name: str |
|||
id: int |
|||
|
|||
option_list = [ |
|||
TestOption(name="damp", id=0), |
|||
TestOption(name="stand_up", id=1), |
|||
TestOption(name="stand_down", id=2), |
|||
TestOption(name="move", id=3), |
|||
TestOption(name="stop_move", id=4), |
|||
TestOption(name="speed_level", id=5), |
|||
TestOption(name="switch_gait", id=6), |
|||
TestOption(name="get_state", id=7), |
|||
TestOption(name="recovery", id=8), |
|||
TestOption(name="balance", id=9) |
|||
] |
|||
|
|||
class UserInterface: |
|||
def __init__(self): |
|||
self.test_option_ = None |
|||
|
|||
def convert_to_int(self, input_str): |
|||
try: |
|||
return int(input_str) |
|||
except ValueError: |
|||
return None |
|||
|
|||
def terminal_handle(self): |
|||
input_str = input("Enter id or name: \n") |
|||
|
|||
if input_str == "list": |
|||
self.test_option_.name = None |
|||
self.test_option_.id = None |
|||
for option in option_list: |
|||
print(f"{option.name}, id: {option.id}") |
|||
return |
|||
|
|||
for option in option_list: |
|||
if input_str == option.name or self.convert_to_int(input_str) == option.id: |
|||
self.test_option_.name = option.name |
|||
self.test_option_.id = option.id |
|||
print(f"Test: {self.test_option_.name}, test_id: {self.test_option_.id}") |
|||
return |
|||
|
|||
print("No matching test option found.") |
|||
|
|||
if __name__ == "__main__": |
|||
if len(sys.argv) < 2: |
|||
print(f"Usage: python3 {sys.argv[0]} networkInterface") |
|||
sys.exit(-1) |
|||
|
|||
print("WARNING: Please ensure there are no obstacles around the robot while running this example.") |
|||
input("Press Enter to continue...") |
|||
|
|||
ChannelFactoryInitialize(0, sys.argv[1]) |
|||
|
|||
test_option = TestOption(name=None, id=None) |
|||
user_interface = UserInterface() |
|||
user_interface.test_option_ = test_option |
|||
|
|||
sport_client = SportClient() |
|||
sport_client.SetTimeout(10.0) |
|||
sport_client.Init() |
|||
|
|||
while True: |
|||
user_interface.terminal_handle() |
|||
|
|||
print(f"Updated Test Option: Name = {test_option.name}, ID = {test_option.id}\n") |
|||
|
|||
if test_option.id == 0: |
|||
sport_client.Damp() |
|||
elif test_option.id == 1: |
|||
sport_client.StandUp() |
|||
elif test_option.id == 2: |
|||
sport_client.StandDown() |
|||
elif test_option.id == 3: |
|||
sport_client.Move(0.5,0,0) |
|||
elif test_option.id == 4: |
|||
sport_client.StopMove() |
|||
elif test_option.id == 5: |
|||
sport_client.SpeedLevel(1) |
|||
elif test_option.id == 6: |
|||
sport_client.SwitchGait(1) |
|||
elif test_option.id == 8: |
|||
sport_client.RecoveryStand() |
|||
elif test_option.id == 9: |
|||
sport_client.BalanceStand() |
|||
|
|||
time.sleep(1) |
|||
@ -0,0 +1,196 @@ |
|||
import time |
|||
import sys |
|||
|
|||
from unitree_sdk2py.core.channel import ChannelPublisher, ChannelFactoryInitialize |
|||
from unitree_sdk2py.core.channel import ChannelSubscriber, ChannelFactoryInitialize |
|||
from unitree_sdk2py.idl.default import unitree_go_msg_dds__LowCmd_ |
|||
from unitree_sdk2py.idl.default import unitree_go_msg_dds__LowState_ |
|||
from unitree_sdk2py.idl.unitree_go.msg.dds_ import LowCmd_ |
|||
from unitree_sdk2py.idl.unitree_go.msg.dds_ import LowState_ |
|||
from unitree_sdk2py.utils.crc import CRC |
|||
from unitree_sdk2py.utils.thread import RecurrentThread |
|||
import unitree_legged_const as go2w |
|||
from unitree_sdk2py.go2.robot_state.robot_state_client import RobotStateClient |
|||
from unitree_sdk2py.comm.motion_switcher.motion_switcher_client import MotionSwitcherClient |
|||
from unitree_sdk2py.go2.sport.sport_client import SportClient |
|||
|
|||
class Custom: |
|||
def __init__(self): |
|||
self.Kp = 70.0 |
|||
self.Kd = 5.0 |
|||
self.time_consume = 0 |
|||
self.rate_count = 0 |
|||
self.sin_count = 0 |
|||
self.motiontime = 0 |
|||
self.dt = 0.002 |
|||
|
|||
self.low_cmd = unitree_go_msg_dds__LowCmd_() |
|||
self.low_state = None |
|||
|
|||
self.targetPos_1 = [0.0, 1.36, -2.65, 0.0, 1.36, -2.65, |
|||
-0.2, 1.36, -2.65, 0.2, 1.36, -2.65] |
|||
|
|||
self.targetPos_2 = [0.0, 0.67, -1.3, 0.0, 0.67, -1.3, |
|||
0.0, 0.67, -1.3, 0.0, 0.67, -1.3] |
|||
|
|||
self.targetPos_3 = [-0.35, 1.36, -2.65, 0.35, 1.36, -2.65, |
|||
-0.5, 1.36, -2.65, 0.5, 1.36, -2.65] |
|||
|
|||
self.startPos = [0.0] * 12 |
|||
self.duration_1 = 500 |
|||
self.duration_2 = 500 |
|||
self.duration_3 = 2000 |
|||
self.duration_4 = 900 |
|||
self.percent_1 = 0 |
|||
self.percent_2 = 0 |
|||
self.percent_3 = 0 |
|||
self.percent_4 = 0 |
|||
|
|||
self.firstRun = True |
|||
self.done = False |
|||
|
|||
# thread handling |
|||
self.lowCmdWriteThreadPtr = None |
|||
|
|||
self.crc = CRC() |
|||
|
|||
# Public methods |
|||
def Init(self): |
|||
self.InitLowCmd() |
|||
|
|||
# create publisher # |
|||
self.lowcmd_publisher = ChannelPublisher("rt/lowcmd", LowCmd_) |
|||
self.lowcmd_publisher.Init() |
|||
|
|||
# create subscriber # |
|||
self.lowstate_subscriber = ChannelSubscriber("rt/lowstate", LowState_) |
|||
self.lowstate_subscriber.Init(self.LowStateMessageHandler, 10) |
|||
|
|||
self.sc = SportClient() |
|||
self.sc.SetTimeout(5.0) |
|||
self.sc.Init() |
|||
|
|||
self.msc = MotionSwitcherClient() |
|||
self.msc.SetTimeout(5.0) |
|||
self.msc.Init() |
|||
|
|||
status, result = self.msc.CheckMode() |
|||
while result['name']: |
|||
self.sc.StandUp() |
|||
self.sc.StandDown() |
|||
self.msc.ReleaseMode() |
|||
status, result = self.msc.CheckMode() |
|||
time.sleep(1) |
|||
|
|||
def Start(self): |
|||
self.lowCmdWriteThreadPtr = RecurrentThread( |
|||
name="writebasiccmd", interval=0.002, target=self.LowCmdWrite, |
|||
) |
|||
self.lowCmdWriteThreadPtr.Start() |
|||
|
|||
def InitLowCmd(self): |
|||
self.low_cmd.head[0] = 0xFE |
|||
self.low_cmd.head[1] = 0xEF |
|||
self.low_cmd.level_flag = 0xFF |
|||
self.low_cmd.gpio = 0 |
|||
for i in range(20): |
|||
self.low_cmd.motor_cmd[i].mode = 0x01 |
|||
self.low_cmd.motor_cmd[i].q= go2w.PosStopF |
|||
self.low_cmd.motor_cmd[i].kp = 0 |
|||
self.low_cmd.motor_cmd[i].dq = go2w.VelStopF |
|||
self.low_cmd.motor_cmd[i].kd = 0 |
|||
self.low_cmd.motor_cmd[i].tau = 0 |
|||
|
|||
def LowStateMessageHandler(self, msg: LowState_): |
|||
self.low_state = msg |
|||
|
|||
def LowCmdWrite(self): |
|||
if self.firstRun: |
|||
for i in range(12): |
|||
self.startPos[i] = self.low_state.motor_state[i].q |
|||
self.firstRun = False |
|||
|
|||
self.percent_1 += 1.0 / self.duration_1 |
|||
self.percent_1 = min(self.percent_1, 1) |
|||
if self.percent_1 < 1: |
|||
for i in range(12): |
|||
self.low_cmd.motor_cmd[i].q = (1 - self.percent_1) * self.startPos[i] + self.percent_1 * self.targetPos_1[i] |
|||
self.low_cmd.motor_cmd[i].dq = 0 |
|||
self.low_cmd.motor_cmd[i].kp = self.Kp |
|||
self.low_cmd.motor_cmd[i].kd = self.Kd |
|||
self.low_cmd.motor_cmd[i].tau = 0 |
|||
|
|||
if (self.percent_1 == 1) and (self.percent_2 <= 1): |
|||
self.percent_2 += 1.0 / self.duration_2 |
|||
self.percent_2 = min(self.percent_2, 1) |
|||
for i in range(12): |
|||
self.low_cmd.motor_cmd[i].q = (1 - self.percent_2) * self.targetPos_1[i] + self.percent_2 * self.targetPos_2[i] |
|||
self.low_cmd.motor_cmd[i].dq = 0 |
|||
self.low_cmd.motor_cmd[i].kp = self.Kp |
|||
self.low_cmd.motor_cmd[i].kd = self.Kd |
|||
self.low_cmd.motor_cmd[i].tau = 0 |
|||
|
|||
if (self.percent_1 == 1) and (self.percent_2 == 1) and (self.percent_3 < 1): |
|||
self.percent_3 += 1.0 / self.duration_3 |
|||
self.percent_3 = min(self.percent_3, 1) |
|||
for i in range(12): |
|||
self.low_cmd.motor_cmd[i].q = self.targetPos_2[i] |
|||
self.low_cmd.motor_cmd[i].dq = 0 |
|||
self.low_cmd.motor_cmd[i].kp = self.Kp |
|||
self.low_cmd.motor_cmd[i].kd = self.Kd |
|||
self.low_cmd.motor_cmd[i].tau = 0 |
|||
|
|||
if self.percent_3 < 0.4: |
|||
for i in range(12, 16): |
|||
self.low_cmd.motor_cmd[i].q = 0 |
|||
self.low_cmd.motor_cmd[i].kp = 0.0 |
|||
self.low_cmd.motor_cmd[i].dq = 3 |
|||
self.low_cmd.motor_cmd[i].kd = self.Kd |
|||
self.low_cmd.motor_cmd[i].tau = 0 |
|||
|
|||
if 0.4 <= self.percent_3 < 0.8: |
|||
for i in range(12, 16): |
|||
self.low_cmd.motor_cmd[i].q = 0 |
|||
self.low_cmd.motor_cmd[i].kp = 0 |
|||
self.low_cmd.motor_cmd[i].dq = -3 |
|||
self.low_cmd.motor_cmd[i].kd = self.Kd |
|||
self.low_cmd.motor_cmd[i].tau = 0 |
|||
|
|||
if self.percent_3 >= 0.8: |
|||
for i in range(12, 16): |
|||
self.low_cmd.motor_cmd[i].q = 0 |
|||
self.low_cmd.motor_cmd[i].kp = 0 |
|||
self.low_cmd.motor_cmd[i].dq = 0 |
|||
self.low_cmd.motor_cmd[i].kd = self.Kd |
|||
self.low_cmd.motor_cmd[i].tau = 0 |
|||
|
|||
if (self.percent_1 == 1) and (self.percent_2 == 1) and (self.percent_3 == 1) and (self.percent_4 <= 1): |
|||
self.percent_4 += 1.0 / self.duration_4 |
|||
self.percent_4 = min(self.percent_4, 1) |
|||
for i in range(12): |
|||
self.low_cmd.motor_cmd[i].q = (1 - self.percent_4) * self.targetPos_2[i] + self.percent_4 * self.targetPos_3[i] |
|||
self.low_cmd.motor_cmd[i].dq = 0 |
|||
self.low_cmd.motor_cmd[i].kp = self.Kp |
|||
self.low_cmd.motor_cmd[i].kd = self.Kd |
|||
self.low_cmd.motor_cmd[i].tau = 0 |
|||
|
|||
self.low_cmd.crc = self.crc.Crc(self.low_cmd) |
|||
self.lowcmd_publisher.Write(self.low_cmd) |
|||
|
|||
if __name__ == '__main__': |
|||
|
|||
if len(sys.argv)>1: |
|||
ChannelFactoryInitialize(0, sys.argv[1]) |
|||
else: |
|||
ChannelFactoryInitialize(0) |
|||
|
|||
custom = Custom() |
|||
custom.Init() |
|||
custom.Start() |
|||
|
|||
while True: |
|||
if custom.percent_4 == 1.0: |
|||
time.sleep(1) |
|||
print("Done!") |
|||
sys.exit(-1) |
|||
time.sleep(1) |
|||
@ -0,0 +1,24 @@ |
|||
LegID = { |
|||
"FR_0": 0, # Front right hip |
|||
"FR_1": 1, # Front right thigh |
|||
"FR_2": 2, # Front right calf |
|||
"FL_0": 3, |
|||
"FL_1": 4, |
|||
"FL_2": 5, |
|||
"RR_0": 6, |
|||
"RR_1": 7, |
|||
"RR_2": 8, |
|||
"RL_0": 9, |
|||
"RL_1": 10, |
|||
"RL_2": 11, |
|||
"FR_w": 12, # Front right wheel |
|||
"FL_w": 13, # Front left wheel |
|||
"RR_w": 14, # Rear right wheel |
|||
"RL_w": 15, # Rear left wheel |
|||
} |
|||
|
|||
HIGHLEVEL = 0xEE |
|||
LOWLEVEL = 0xFF |
|||
TRIGERLEVEL = 0xF0 |
|||
PosStopF = 2.146e9 |
|||
VelStopF = 16000.0 |
|||
@ -0,0 +1,96 @@ |
|||
import time |
|||
import sys |
|||
from unitree_sdk2py.core.channel import ChannelSubscriber, ChannelFactoryInitialize |
|||
from unitree_sdk2py.idl.default import unitree_go_msg_dds__SportModeState_ |
|||
from unitree_sdk2py.idl.unitree_go.msg.dds_ import SportModeState_ |
|||
from unitree_sdk2py.h1.loco.h1_loco_client import LocoClient |
|||
import math |
|||
from dataclasses import dataclass |
|||
|
|||
@dataclass |
|||
class TestOption: |
|||
name: str |
|||
id: int |
|||
|
|||
option_list = [ |
|||
TestOption(name="damp", id=0), |
|||
TestOption(name="stand_up", id=1), |
|||
TestOption(name="move forward", id=3), |
|||
TestOption(name="move lateral", id=4), |
|||
TestOption(name="move rotate", id=5), |
|||
TestOption(name="low stand", id=6), |
|||
TestOption(name="high stand", id=7), |
|||
TestOption(name="zero torque", id=8) |
|||
] |
|||
|
|||
class UserInterface: |
|||
def __init__(self): |
|||
self.test_option_ = None |
|||
|
|||
def convert_to_int(self, input_str): |
|||
try: |
|||
return int(input_str) |
|||
except ValueError: |
|||
return None |
|||
|
|||
def terminal_handle(self): |
|||
input_str = input("Enter id or name: \n") |
|||
|
|||
if input_str == "list": |
|||
self.test_option_.name = None |
|||
self.test_option_.id = None |
|||
for option in option_list: |
|||
print(f"{option.name}, id: {option.id}") |
|||
return |
|||
|
|||
for option in option_list: |
|||
if input_str == option.name or self.convert_to_int(input_str) == option.id: |
|||
self.test_option_.name = option.name |
|||
self.test_option_.id = option.id |
|||
print(f"Test: {self.test_option_.name}, test_id: {self.test_option_.id}") |
|||
return |
|||
|
|||
print("No matching test option found.") |
|||
|
|||
if __name__ == "__main__": |
|||
|
|||
print("WARNING: Please ensure there are no obstacles around the robot while running this example.") |
|||
input("Press Enter to continue...") |
|||
|
|||
if len(sys.argv)>1: |
|||
ChannelFactoryInitialize(0, sys.argv[1]) |
|||
else: |
|||
ChannelFactoryInitialize(0) |
|||
|
|||
test_option = TestOption(name=None, id=None) |
|||
user_interface = UserInterface() |
|||
user_interface.test_option_ = test_option |
|||
|
|||
sport_client = LocoClient() |
|||
sport_client.SetTimeout(10.0) |
|||
sport_client.Init() |
|||
|
|||
while True: |
|||
|
|||
user_interface.terminal_handle() |
|||
|
|||
print(f"Updated Test Option: Name = {test_option.name}, ID = {test_option.id}\n") |
|||
|
|||
if test_option.id == 0: |
|||
sport_client.Damp() |
|||
elif test_option.id == 1: |
|||
sport_client.StandUp() |
|||
elif test_option.id == 3: |
|||
sport_client.Move(0.3,0,0) |
|||
elif test_option.id == 4: |
|||
sport_client.Move(0,0.3,0) |
|||
elif test_option.id == 5: |
|||
sport_client.Move(0,0,0.3) |
|||
elif test_option.id == 6: |
|||
sport_client.LowStand() |
|||
elif test_option.id == 7: |
|||
sport_client.HighStand() |
|||
elif test_option.id == 8: |
|||
sport_client.ZeroTorque() |
|||
|
|||
time.sleep(1) |
|||
@ -0,0 +1,167 @@ |
|||
import time |
|||
import sys |
|||
|
|||
from unitree_sdk2py.core.channel import ChannelPublisher, ChannelFactoryInitialize |
|||
from unitree_sdk2py.core.channel import ChannelSubscriber, ChannelFactoryInitialize |
|||
from unitree_sdk2py.idl.default import unitree_go_msg_dds__LowCmd_ |
|||
from unitree_sdk2py.idl.default import unitree_go_msg_dds__LowState_ |
|||
from unitree_sdk2py.idl.unitree_go.msg.dds_ import LowCmd_ |
|||
from unitree_sdk2py.idl.unitree_go.msg.dds_ import LowState_ |
|||
from unitree_sdk2py.utils.crc import CRC |
|||
from unitree_sdk2py.utils.thread import RecurrentThread |
|||
from unitree_sdk2py.comm.motion_switcher.motion_switcher_client import MotionSwitcherClient |
|||
import unitree_legged_const as h1 |
|||
import numpy as np |
|||
|
|||
H1_NUM_MOTOR = 20 |
|||
|
|||
class H1JointIndex: |
|||
# Right leg |
|||
kRightHipYaw = 8 |
|||
kRightHipRoll = 0 |
|||
kRightHipPitch = 1 |
|||
kRightKnee = 2 |
|||
kRightAnkle = 11 |
|||
# Left leg |
|||
kLeftHipYaw = 7 |
|||
kLeftHipRoll = 3 |
|||
kLeftHipPitch = 4 |
|||
kLeftKnee = 5 |
|||
kLeftAnkle = 10 |
|||
|
|||
kWaistYaw = 6 |
|||
|
|||
kNotUsedJoint = 9 |
|||
|
|||
# Right arm |
|||
kRightShoulderPitch = 12 |
|||
kRightShoulderRoll = 13 |
|||
kRightShoulderYaw = 14 |
|||
kRightElbow = 15 |
|||
# Left arm |
|||
kLeftShoulderPitch = 16 |
|||
kLeftShoulderRoll = 17 |
|||
kLeftShoulderYaw = 18 |
|||
kLeftElbow = 19 |
|||
|
|||
class Custom: |
|||
def __init__(self): |
|||
self.time_ = 0.0 |
|||
self.control_dt_ = 0.01 |
|||
self.duration_ = 10.0 |
|||
self.counter_ = 0 |
|||
self.kp_low_ = 60.0 |
|||
self.kp_high_ = 200.0 |
|||
self.kd_low_ = 1.5 |
|||
self.kd_high_ = 5.0 |
|||
self.low_cmd = unitree_go_msg_dds__LowCmd_() |
|||
self.InitLowCmd() |
|||
self.low_state = None |
|||
self.crc = CRC() |
|||
|
|||
def Init(self): |
|||
# # create publisher # |
|||
self.lowcmd_publisher_ = ChannelPublisher("rt/lowcmd", LowCmd_) |
|||
self.lowcmd_publisher_.Init() |
|||
|
|||
# # create subscriber # |
|||
self.lowstate_subscriber = ChannelSubscriber("rt/lowstate", LowState_) |
|||
self.lowstate_subscriber.Init(self.LowStateHandler, 10) |
|||
|
|||
self.msc = MotionSwitcherClient() |
|||
self.msc.SetTimeout(5.0) |
|||
self.msc.Init() |
|||
|
|||
status, result = self.msc.CheckMode() |
|||
while result['name']: |
|||
self.msc.ReleaseMode() |
|||
status, result = self.msc.CheckMode() |
|||
time.sleep(1) |
|||
|
|||
self.report_rpy_ptr_ = RecurrentThread( |
|||
interval=0.1, target=self.ReportRPY, name="report_rpy" |
|||
) |
|||
|
|||
self.report_rpy_ptr_.Start() |
|||
|
|||
def is_weak_motor(self,motor_index): |
|||
return motor_index in { |
|||
H1JointIndex.kLeftAnkle, |
|||
H1JointIndex.kRightAnkle, |
|||
H1JointIndex.kRightShoulderPitch, |
|||
H1JointIndex.kRightShoulderRoll, |
|||
H1JointIndex.kRightShoulderYaw, |
|||
H1JointIndex.kRightElbow, |
|||
H1JointIndex.kLeftShoulderPitch, |
|||
H1JointIndex.kLeftShoulderRoll, |
|||
H1JointIndex.kLeftShoulderYaw, |
|||
H1JointIndex.kLeftElbow, |
|||
} |
|||
|
|||
def InitLowCmd(self): |
|||
self.low_cmd.head[0] = 0xFE |
|||
self.low_cmd.head[1] = 0xEF |
|||
self.low_cmd.level_flag = 0xFF |
|||
self.low_cmd.gpio = 0 |
|||
for i in range(H1_NUM_MOTOR): |
|||
if self.is_weak_motor(i): |
|||
self.low_cmd.motor_cmd[i].mode = 0x01 |
|||
else: |
|||
self.low_cmd.motor_cmd[i].mode = 0x0A |
|||
self.low_cmd.motor_cmd[i].q= h1.PosStopF |
|||
self.low_cmd.motor_cmd[i].kp = 0 |
|||
self.low_cmd.motor_cmd[i].dq = h1.VelStopF |
|||
self.low_cmd.motor_cmd[i].kd = 0 |
|||
self.low_cmd.motor_cmd[i].tau = 0 |
|||
|
|||
def Start(self): |
|||
self.lowCmdWriteThreadPtr = RecurrentThread( |
|||
interval=self.control_dt_, target=self.LowCmdWrite, name="control" |
|||
) |
|||
self.lowCmdWriteThreadPtr.Start() |
|||
|
|||
def LowStateHandler(self, msg: LowState_): |
|||
self.low_state = msg |
|||
|
|||
def ReportRPY(self): |
|||
print("rpy: [",self.low_state.imu_state.rpy[0],", " |
|||
,self.low_state.imu_state.rpy[1],", " |
|||
,self.low_state.imu_state.rpy[2],"]" |
|||
) |
|||
|
|||
def LowCmdWrite(self): |
|||
self.time_ += self.control_dt_ |
|||
self.time_ = np.clip(self.time_ , 0.0, self.duration_) |
|||
|
|||
# set robot to zero posture |
|||
for i in range(H1_NUM_MOTOR): |
|||
ratio = self.time_ / self.duration_ |
|||
self.low_cmd.motor_cmd[i].tau = 0. |
|||
self.low_cmd.motor_cmd[i].q = (1.0 - ratio) * self.low_state.motor_state[i].q |
|||
self.low_cmd.motor_cmd[i].dq = 0. |
|||
self.low_cmd.motor_cmd[i].kp = self.kp_low_ if self.is_weak_motor(i) else self.kp_high_ |
|||
self.low_cmd.motor_cmd[i].kd = self.kd_low_ if self.is_weak_motor(i) else self.kd_high_ |
|||
|
|||
self.low_cmd.crc = self.crc.Crc(self.low_cmd) |
|||
self.lowcmd_publisher_.Write(self.low_cmd) |
|||
|
|||
if __name__ == '__main__': |
|||
|
|||
print("WARNING: Please ensure there are no obstacles around the robot while running this example.") |
|||
input("Press Enter to continue...") |
|||
|
|||
if len(sys.argv)>1: |
|||
ChannelFactoryInitialize(0, sys.argv[1]) |
|||
else: |
|||
ChannelFactoryInitialize(0) |
|||
|
|||
custom = Custom() |
|||
custom.Init() |
|||
custom.Start() |
|||
|
|||
while True: |
|||
if custom.time_ == custom.duration_: |
|||
time.sleep(1) |
|||
print("Done!") |
|||
sys.exit(-1) |
|||
time.sleep(1) |
|||
@ -0,0 +1,5 @@ |
|||
HIGHLEVEL = 0xEE |
|||
LOWLEVEL = 0xFF |
|||
TRIGERLEVEL = 0xF0 |
|||
PosStopF = 2.146e9 |
|||
VelStopF = 16000.0 |
|||
@ -0,0 +1,201 @@ |
|||
import time |
|||
import sys |
|||
|
|||
from unitree_sdk2py.core.channel import ChannelPublisher, ChannelFactoryInitialize |
|||
from unitree_sdk2py.core.channel import ChannelSubscriber, ChannelFactoryInitialize |
|||
from unitree_sdk2py.idl.default import unitree_hg_msg_dds__LowCmd_ |
|||
from unitree_sdk2py.idl.default import unitree_hg_msg_dds__LowState_ |
|||
from unitree_sdk2py.idl.unitree_hg.msg.dds_ import LowCmd_ |
|||
from unitree_sdk2py.idl.unitree_hg.msg.dds_ import LowState_ |
|||
from unitree_sdk2py.utils.crc import CRC |
|||
from unitree_sdk2py.utils.thread import RecurrentThread |
|||
from unitree_sdk2py.comm.motion_switcher.motion_switcher_client import MotionSwitcherClient |
|||
|
|||
import numpy as np |
|||
|
|||
H1_2_NUM_MOTOR = 27 |
|||
|
|||
class H1_2_JointIndex: |
|||
# legs |
|||
LeftHipYaw = 0 |
|||
LeftHipPitch = 1 |
|||
LeftHipRoll = 2 |
|||
LeftKnee = 3 |
|||
LeftAnklePitch = 4 |
|||
LeftAnkleB = 4 |
|||
LeftAnkleRoll = 5 |
|||
LeftAnkleA = 5 |
|||
RightHipYaw = 6 |
|||
RightHipPitch = 7 |
|||
RightHipRoll = 8 |
|||
RightKnee = 9 |
|||
RightAnklePitch = 10 |
|||
RightAnkleB = 10 |
|||
RightAnkleRoll = 11 |
|||
RightAnkleA = 11 |
|||
# torso |
|||
WaistYaw = 12 |
|||
# arms |
|||
LeftShoulderPitch = 13 |
|||
LeftShoulderRoll = 14 |
|||
LeftShoulderYaw = 15 |
|||
LeftElbow = 16 |
|||
LeftWristRoll = 17 |
|||
LeftWristPitch = 18 |
|||
LeftWristYaw = 19 |
|||
RightShoulderPitch = 20 |
|||
RightShoulderRoll = 21 |
|||
RightShoulderYaw = 22 |
|||
RightElbow = 23 |
|||
RightWristRoll = 24 |
|||
RightWristPitch = 25 |
|||
RightWristYaw = 26 |
|||
|
|||
|
|||
class Mode: |
|||
PR = 0 # Series Control for Pitch/Roll Joints |
|||
AB = 1 # Parallel Control for A/B Joints |
|||
|
|||
class Custom: |
|||
def __init__(self): |
|||
self.time_ = 0.0 |
|||
self.control_dt_ = 0.002 # [2ms] |
|||
self.duration_ = 3.0 # [3 s] |
|||
self.counter_ = 0 |
|||
self.mode_pr_ = Mode.PR |
|||
self.mode_machine_ = 0 |
|||
self.low_cmd = unitree_hg_msg_dds__LowCmd_() |
|||
self.low_state = None |
|||
self.update_mode_machine_ = False |
|||
self.crc = CRC() |
|||
|
|||
def Init(self): |
|||
self.msc = MotionSwitcherClient() |
|||
self.msc.SetTimeout(5.0) |
|||
self.msc.Init() |
|||
|
|||
status, result = self.msc.CheckMode() |
|||
while result['name']: |
|||
self.msc.ReleaseMode() |
|||
status, result = self.msc.CheckMode() |
|||
time.sleep(1) |
|||
|
|||
# create publisher # |
|||
self.lowcmd_publisher_ = ChannelPublisher("rt/lowcmd", LowCmd_) |
|||
self.lowcmd_publisher_.Init() |
|||
|
|||
# create subscriber # |
|||
self.lowstate_subscriber = ChannelSubscriber("rt/lowstate", LowState_) |
|||
self.lowstate_subscriber.Init(self.LowStateHandler, 10) |
|||
|
|||
def Start(self): |
|||
self.lowCmdWriteThreadPtr = RecurrentThread( |
|||
interval=self.control_dt_, target=self.LowCmdWrite, name="control" |
|||
) |
|||
while self.update_mode_machine_ == False: |
|||
time.sleep(1) |
|||
|
|||
if self.update_mode_machine_ == True: |
|||
self.lowCmdWriteThreadPtr.Start() |
|||
|
|||
def LowStateHandler(self, msg: LowState_): |
|||
self.low_state = msg |
|||
|
|||
if self.update_mode_machine_ == False: |
|||
self.mode_machine_ = self.low_state.mode_machine |
|||
self.update_mode_machine_ = True |
|||
|
|||
self.counter_ +=1 |
|||
if (self.counter_ % 500 == 0) : |
|||
self.counter_ = 0 |
|||
print(self.low_state.imu_state.rpy) |
|||
|
|||
def LowCmdWrite(self): |
|||
self.time_ += self.control_dt_ |
|||
self.low_cmd.mode_pr = Mode.PR |
|||
self.low_cmd.mode_machine = self.mode_machine_ |
|||
for i in range(H1_2_NUM_MOTOR): |
|||
ratio = np.clip(self.time_ / self.duration_, 0.0, 1.0) |
|||
self.low_cmd.motor_cmd[i].mode = 1 # 1:Enable, 0:Disable |
|||
self.low_cmd.motor_cmd[i].tau = 0.0 |
|||
self.low_cmd.motor_cmd[i].q = 0.0 |
|||
self.low_cmd.motor_cmd[i].dq = 0.0 |
|||
self.low_cmd.motor_cmd[i].kp = 100.0 if i < 13 else 50.0 |
|||
self.low_cmd.motor_cmd[i].kd = 1.0 |
|||
|
|||
if self.time_ < self.duration_ : |
|||
# [Stage 1]: set robot to zero posture |
|||
for i in range(H1_2_NUM_MOTOR): |
|||
ratio = np.clip(self.time_ / self.duration_, 0.0, 1.0) |
|||
self.low_cmd.mode_pr = Mode.PR |
|||
self.low_cmd.mode_machine = self.mode_machine_ |
|||
self.low_cmd.motor_cmd[i].mode = 1 # 1:Enable, 0:Disable |
|||
self.low_cmd.motor_cmd[i].tau = 0. |
|||
self.low_cmd.motor_cmd[i].q = (1.0 - ratio) * self.low_state.motor_state[i].q |
|||
self.low_cmd.motor_cmd[i].dq = 0. |
|||
self.low_cmd.motor_cmd[i].kp = 100.0 if i < 13 else 50.0 |
|||
self.low_cmd.motor_cmd[i].kd = 1.0 |
|||
else : |
|||
# [Stage 2]: swing ankle using PR mode |
|||
max_P = 0.25 |
|||
max_R = 0.25 |
|||
t = self.time_ - self.duration_ |
|||
L_P_des = max_P * np.cos(2.0 * np.pi * t) |
|||
L_R_des = max_R * np.sin(2.0 * np.pi * t) |
|||
R_P_des = max_P * np.cos(2.0 * np.pi * t) |
|||
R_R_des = -max_R * np.sin(2.0 * np.pi * t) |
|||
|
|||
Kp_Pitch = 80 |
|||
Kd_Pitch = 1 |
|||
Kp_Roll = 80 |
|||
Kd_Roll = 1 |
|||
|
|||
self.low_cmd.mode_pr = Mode.PR |
|||
self.low_cmd.mode_machine = self.mode_machine_ |
|||
self.low_cmd.motor_cmd[H1_2_JointIndex.LeftAnklePitch].q = L_P_des |
|||
self.low_cmd.motor_cmd[H1_2_JointIndex.LeftAnklePitch].dq = 0 |
|||
self.low_cmd.motor_cmd[H1_2_JointIndex.LeftAnklePitch].kp = Kp_Pitch |
|||
self.low_cmd.motor_cmd[H1_2_JointIndex.LeftAnklePitch].kd = Kd_Pitch |
|||
self.low_cmd.motor_cmd[H1_2_JointIndex.LeftAnkleRoll].q = L_R_des |
|||
self.low_cmd.motor_cmd[H1_2_JointIndex.LeftAnkleRoll].dq = 0 |
|||
self.low_cmd.motor_cmd[H1_2_JointIndex.LeftAnkleRoll].kp = Kp_Roll |
|||
self.low_cmd.motor_cmd[H1_2_JointIndex.LeftAnkleRoll].kd = Kd_Roll |
|||
self.low_cmd.motor_cmd[H1_2_JointIndex.RightAnklePitch].q = R_P_des |
|||
self.low_cmd.motor_cmd[H1_2_JointIndex.RightAnklePitch].dq = 0 |
|||
self.low_cmd.motor_cmd[H1_2_JointIndex.RightAnklePitch].kp = Kp_Pitch |
|||
self.low_cmd.motor_cmd[H1_2_JointIndex.RightAnklePitch].kd = Kd_Pitch |
|||
self.low_cmd.motor_cmd[H1_2_JointIndex.RightAnkleRoll].q = R_R_des |
|||
self.low_cmd.motor_cmd[H1_2_JointIndex.RightAnkleRoll].dq = 0 |
|||
self.low_cmd.motor_cmd[H1_2_JointIndex.RightAnkleRoll].kp = Kp_Roll |
|||
self.low_cmd.motor_cmd[H1_2_JointIndex.RightAnkleRoll].kd = Kd_Roll |
|||
|
|||
max_wrist_roll_angle = 0.5; # [rad] |
|||
WristRoll_des = max_wrist_roll_angle * np.sin(2.0 * np.pi * t) |
|||
self.low_cmd.motor_cmd[H1_2_JointIndex.LeftWristRoll].q = WristRoll_des |
|||
self.low_cmd.motor_cmd[H1_2_JointIndex.LeftWristRoll].dq = 0 |
|||
self.low_cmd.motor_cmd[H1_2_JointIndex.LeftWristRoll].kp = 50 |
|||
self.low_cmd.motor_cmd[H1_2_JointIndex.LeftWristRoll].kd = 1 |
|||
self.low_cmd.motor_cmd[H1_2_JointIndex.RightWristRoll].q = WristRoll_des |
|||
self.low_cmd.motor_cmd[H1_2_JointIndex.RightWristRoll].dq = 0 |
|||
self.low_cmd.motor_cmd[H1_2_JointIndex.RightWristRoll].kp = 50 |
|||
self.low_cmd.motor_cmd[H1_2_JointIndex.RightWristRoll].kd = 1 |
|||
|
|||
self.low_cmd.crc = self.crc.Crc(self.low_cmd) |
|||
self.lowcmd_publisher_.Write(self.low_cmd) |
|||
|
|||
if __name__ == '__main__': |
|||
|
|||
print("WARNING: Please ensure there are no obstacles around the robot while running this example.") |
|||
input("Press Enter to continue...") |
|||
|
|||
if len(sys.argv)>1: |
|||
ChannelFactoryInitialize(0, sys.argv[1]) |
|||
else: |
|||
ChannelFactoryInitialize(0) |
|||
|
|||
custom = Custom() |
|||
custom.Init() |
|||
custom.Start() |
|||
|
|||
while True: |
|||
time.sleep(1) |
|||
@ -0,0 +1,28 @@ |
|||
import time |
|||
|
|||
from unitree_sdk2py.core.channel import ChannelPublisher, ChannelFactoryInitialize |
|||
from user_data import * |
|||
|
|||
|
|||
if __name__ == "__main__": |
|||
ChannelFactoryInitialize() |
|||
|
|||
# Create a publisher to publish the data defined in UserData class |
|||
pub = ChannelPublisher("topic", UserData) |
|||
pub.Init() |
|||
|
|||
for i in range(30): |
|||
# Create a Userdata message |
|||
msg = UserData(" ", 0) |
|||
msg.string_data = "Hello world" |
|||
msg.float_data = time.time() |
|||
|
|||
# Publish message |
|||
if pub.Write(msg, 0.5): |
|||
print("Publish success. msg:", msg) |
|||
else: |
|||
print("Waitting for subscriber.") |
|||
|
|||
time.sleep(1) |
|||
|
|||
pub.Close() |
|||
@ -0,0 +1,20 @@ |
|||
import time |
|||
|
|||
from unitree_sdk2py.core.channel import ChannelSubscriber, ChannelFactoryInitialize |
|||
from user_data import * |
|||
|
|||
|
|||
if __name__ == "__main__": |
|||
ChannelFactoryInitialize() |
|||
# Create a subscriber to subscribe the data defined in UserData class |
|||
sub = ChannelSubscriber("topic", UserData) |
|||
sub.Init() |
|||
|
|||
while True: |
|||
msg = sub.Read() |
|||
if msg is not None: |
|||
print("Subscribe success. msg:", msg) |
|||
else: |
|||
print("No data subscribed.") |
|||
break |
|||
sub.Close() |
|||
@ -0,0 +1,9 @@ |
|||
from dataclasses import dataclass |
|||
from cyclonedds.idl import IdlStruct |
|||
|
|||
|
|||
# This class defines user data consisting of a float data and a string data |
|||
@dataclass |
|||
class UserData(IdlStruct, typename="UserData"): |
|||
string_data: str |
|||
float_data: float |
|||
@ -0,0 +1,36 @@ |
|||
import time |
|||
import sys |
|||
|
|||
from unitree_sdk2py.core.channel import ChannelFactoryInitialize |
|||
from unitree_sdk2py.comm.motion_switcher.motion_switcher_client import MotionSwitcherClient |
|||
|
|||
|
|||
class Custom: |
|||
def __init__(self): |
|||
self.msc = MotionSwitcherClient() |
|||
self.msc.SetTimeout(5.0) |
|||
self.msc.Init() |
|||
|
|||
def selectMode(self,name): |
|||
ret = self.msc.SelectMode(name) |
|||
return ret |
|||
|
|||
|
|||
if __name__ == '__main__': |
|||
|
|||
print("WARNING: Please ensure there are no obstacles around the robot while running this example.") |
|||
input("Press Enter to continue...") |
|||
|
|||
if len(sys.argv)>1: |
|||
ChannelFactoryInitialize(0, sys.argv[1]) |
|||
else: |
|||
ChannelFactoryInitialize(0) |
|||
|
|||
custom = Custom() |
|||
selectMode = "ai" |
|||
# selectMode = "normal" |
|||
# selectMode = "advanced" |
|||
# selectMode = "ai-w" # for wheeled robot |
|||
ret = custom.selectMode(selectMode) |
|||
print("ret: ",ret) |
|||
|
|||
@ -0,0 +1,35 @@ |
|||
import time |
|||
import sys |
|||
|
|||
from unitree_sdk2py.core.channel import ChannelFactoryInitialize |
|||
from unitree_sdk2py.go2.obstacles_avoid.obstacles_avoid_client import ObstaclesAvoidClient |
|||
|
|||
if __name__ == "__main__": |
|||
if len(sys.argv)>1: |
|||
ChannelFactoryInitialize(0, sys.argv[1]) |
|||
else: |
|||
ChannelFactoryInitialize(0) |
|||
|
|||
try: |
|||
client = ObstaclesAvoidClient() |
|||
client.SetTimeout(3.0) |
|||
client.Init() |
|||
|
|||
while not client.SwitchGet()[1]: |
|||
client.SwitchSet(True) |
|||
time.sleep(0.1) |
|||
|
|||
print("obstacles avoid switch on") |
|||
|
|||
client.UseRemoteCommandFromApi(True) |
|||
time.sleep(0.5) |
|||
client.Move(0.5, 0.0, 0.0) |
|||
time.sleep(1.0) # move 1s |
|||
client.Move(0.0, 0.0, 0.0) |
|||
client.UseRemoteCommandFromApi(False) |
|||
|
|||
except KeyboardInterrupt: |
|||
client.Move(0.0, 0.0, 0.0) |
|||
client.UseRemoteCommandFromApi(False) |
|||
print("exit!!") |
|||
|
|||
@ -0,0 +1,94 @@ |
|||
import time |
|||
import sys |
|||
|
|||
from unitree_sdk2py.core.channel import ChannelFactoryInitialize |
|||
from unitree_sdk2py.go2.obstacles_avoid.obstacles_avoid_client import ObstaclesAvoidClient |
|||
|
|||
if __name__ == "__main__": |
|||
if len(sys.argv)>1: |
|||
ChannelFactoryInitialize(0, sys.argv[1]) |
|||
else: |
|||
ChannelFactoryInitialize(0) |
|||
|
|||
client = ObstaclesAvoidClient() |
|||
client.SetTimeout(3.0) |
|||
client.Init() |
|||
|
|||
while True: |
|||
print("##################GetServerApiVersion###################") |
|||
code, serverAPiVersion = client.GetServerApiVersion() |
|||
if code != 0: |
|||
print("get server api error. code:", code) |
|||
else: |
|||
print("get server api version:", serverAPiVersion) |
|||
|
|||
if serverAPiVersion != client.GetApiVersion(): |
|||
print("api version not equal.") |
|||
|
|||
time.sleep(3) |
|||
|
|||
print("##################SwitchGet###################") |
|||
code, enable = client.SwitchGet() |
|||
if code != 0: |
|||
print("switch get error. code:", code) |
|||
else: |
|||
print("switch get success. enable:", enable) |
|||
|
|||
time.sleep(3) |
|||
|
|||
print("##################SwitchSet (on)###################") |
|||
code = client.SwitchSet(True) |
|||
if code != 0: |
|||
print("switch set error. code:", code) |
|||
else: |
|||
print("switch set success.") |
|||
|
|||
time.sleep(3) |
|||
|
|||
print("##################SwitchGet###################") |
|||
code, enable1 = client.SwitchGet() |
|||
if code != 0: |
|||
print("switch get error. code:", code) |
|||
else: |
|||
print("switch get success. enable:", enable1) |
|||
|
|||
time.sleep(3) |
|||
|
|||
print("##################SwitchSet (off)###################") |
|||
code = client.SwitchSet(False) |
|||
if code != 0: |
|||
print("switch set error. code:", code) |
|||
else: |
|||
print("switch set success.") |
|||
|
|||
time.sleep(3) |
|||
|
|||
print("##################SwitchGet###################") |
|||
code, enable1 = client.SwitchGet() |
|||
if code != 0: |
|||
print("switch get error. code:", code) |
|||
else: |
|||
print("switch get success. enable:", enable1) |
|||
|
|||
time.sleep(3) |
|||
|
|||
|
|||
print("##################SwitchSet (enable)###################") |
|||
|
|||
code = client.SwitchSet(enable) |
|||
if code != 0: |
|||
print("switch set error. code:", code) |
|||
else: |
|||
print("switch set success. enable:", enable) |
|||
|
|||
time.sleep(3) |
|||
|
|||
print("##################SwitchGet###################") |
|||
code, enable = client.SwitchGet() |
|||
if code != 0: |
|||
print("switch get error. code:", code) |
|||
else: |
|||
print("switch get success. enable:", enable) |
|||
|
|||
time.sleep(3) |
|||
|
|||
@ -0,0 +1,77 @@ |
|||
import time |
|||
import sys |
|||
|
|||
from unitree_sdk2py.core.channel import ChannelFactoryInitialize |
|||
from unitree_sdk2py.go2.vui.vui_client import VuiClient |
|||
|
|||
if __name__ == "__main__": |
|||
if len(sys.argv)>1: |
|||
ChannelFactoryInitialize(0, sys.argv[1]) |
|||
else: |
|||
ChannelFactoryInitialize(0) |
|||
|
|||
client = VuiClient() |
|||
client.SetTimeout(3.0) |
|||
client.Init() |
|||
|
|||
for i in range(1, 11): |
|||
print("#################GetBrightness####################") |
|||
code, level = client.GetBrightness() |
|||
|
|||
if code != 0: |
|||
print("get brightness error. code:", code) |
|||
else: |
|||
print("get brightness success. level:", level) |
|||
|
|||
time.sleep(1) |
|||
|
|||
print("#################SetBrightness####################") |
|||
|
|||
code = client.SetBrightness(i) |
|||
|
|||
if code != 0: |
|||
print("set brightness error. code:", code) |
|||
else: |
|||
print("set brightness success. level:", i) |
|||
|
|||
time.sleep(1) |
|||
|
|||
print("#################SetBrightness 0####################") |
|||
|
|||
code = client.SetBrightness(0) |
|||
|
|||
if code != 0: |
|||
print("set brightness error. code:", code) |
|||
else: |
|||
print("set brightness 0 success.") |
|||
|
|||
for i in range(1, 11): |
|||
print("#################GetVolume####################") |
|||
code, level = client.GetVolume() |
|||
|
|||
if code != 0: |
|||
print("get volume error. code:", code) |
|||
else: |
|||
print("get volume success. level:", level) |
|||
|
|||
time.sleep(1) |
|||
|
|||
print("#################SetVolume####################") |
|||
|
|||
code = client.SetVolume(i) |
|||
|
|||
if code != 0: |
|||
print("set volume error. code:", code) |
|||
else: |
|||
print("set volume success. level:", i) |
|||
|
|||
time.sleep(1) |
|||
|
|||
print("#################SetVolume 0####################") |
|||
|
|||
code = client.SetVolume(0) |
|||
|
|||
if code != 0: |
|||
print("set volume error. code:", code) |
|||
else: |
|||
print("set volume 0 success.") |
|||
@ -0,0 +1,131 @@ |
|||
import time |
|||
import sys |
|||
import struct |
|||
|
|||
from unitree_sdk2py.core.channel import ChannelSubscriber, ChannelFactoryInitialize |
|||
|
|||
# Uncomment the following two lines when using Go2、Go2-W、B2、B2-W、H1 robot |
|||
# from unitree_sdk2py.idl.default import unitree_go_msg_dds__LowState_ |
|||
# from unitree_sdk2py.idl.unitree_go.msg.dds_ import LowState_ |
|||
|
|||
# Uncomment the following two lines when using G1、H1-2 robot |
|||
from unitree_sdk2py.idl.default import unitree_hg_msg_dds__LowState_ |
|||
from unitree_sdk2py.idl.unitree_hg.msg.dds_ import LowState_ |
|||
|
|||
class unitreeRemoteController: |
|||
def __init__(self): |
|||
# key |
|||
self.Lx = 0 |
|||
self.Rx = 0 |
|||
self.Ry = 0 |
|||
self.Ly = 0 |
|||
|
|||
# button |
|||
self.L1 = 0 |
|||
self.L2 = 0 |
|||
self.R1 = 0 |
|||
self.R2 = 0 |
|||
self.A = 0 |
|||
self.B = 0 |
|||
self.X = 0 |
|||
self.Y = 0 |
|||
self.Up = 0 |
|||
self.Down = 0 |
|||
self.Left = 0 |
|||
self.Right = 0 |
|||
self.Select = 0 |
|||
self.F1 = 0 |
|||
self.F3 = 0 |
|||
self.Start = 0 |
|||
|
|||
def parse_botton(self,data1,data2): |
|||
self.R1 = (data1 >> 0) & 1 |
|||
self.L1 = (data1 >> 1) & 1 |
|||
self.Start = (data1 >> 2) & 1 |
|||
self.Select = (data1 >> 3) & 1 |
|||
self.R2 = (data1 >> 4) & 1 |
|||
self.L2 = (data1 >> 5) & 1 |
|||
self.F1 = (data1 >> 6) & 1 |
|||
self.F3 = (data1 >> 7) & 1 |
|||
self.A = (data2 >> 0) & 1 |
|||
self.B = (data2 >> 1) & 1 |
|||
self.X = (data2 >> 2) & 1 |
|||
self.Y = (data2 >> 3) & 1 |
|||
self.Up = (data2 >> 4) & 1 |
|||
self.Right = (data2 >> 5) & 1 |
|||
self.Down = (data2 >> 6) & 1 |
|||
self.Left = (data2 >> 7) & 1 |
|||
|
|||
def parse_key(self,data): |
|||
lx_offset = 4 |
|||
self.Lx = struct.unpack('<f', data[lx_offset:lx_offset + 4])[0] |
|||
rx_offset = 8 |
|||
self.Rx = struct.unpack('<f', data[rx_offset:rx_offset + 4])[0] |
|||
ry_offset = 12 |
|||
self.Ry = struct.unpack('<f', data[ry_offset:ry_offset + 4])[0] |
|||
L2_offset = 16 |
|||
L2 = struct.unpack('<f', data[L2_offset:L2_offset + 4])[0] # Placeholder,unused |
|||
ly_offset = 20 |
|||
self.Ly = struct.unpack('<f', data[ly_offset:ly_offset + 4])[0] |
|||
|
|||
|
|||
def parse(self,remoteData): |
|||
self.parse_key(remoteData) |
|||
self.parse_botton(remoteData[2],remoteData[3]) |
|||
|
|||
print("debug unitreeRemoteController: ") |
|||
print("Lx:", self.Lx) |
|||
print("Rx:", self.Rx) |
|||
print("Ry:", self.Ry) |
|||
print("Ly:", self.Ly) |
|||
|
|||
print("L1:", self.L1) |
|||
print("L2:", self.L2) |
|||
print("R1:", self.R1) |
|||
print("R2:", self.R2) |
|||
print("A:", self.A) |
|||
print("B:", self.B) |
|||
print("X:", self.X) |
|||
print("Y:", self.Y) |
|||
print("Up:", self.Up) |
|||
print("Down:", self.Down) |
|||
print("Left:", self.Left) |
|||
print("Right:", self.Right) |
|||
print("Select:", self.Select) |
|||
print("F1:", self.F1) |
|||
print("F3:", self.F3) |
|||
print("Start:", self.Start) |
|||
print("\n") |
|||
|
|||
|
|||
class Custom: |
|||
def __init__(self): |
|||
self.low_state = None |
|||
self.remoteController = unitreeRemoteController() |
|||
|
|||
def Init(self): |
|||
self.lowstate_subscriber = ChannelSubscriber("rt/lf/lowstate", LowState_) |
|||
self.lowstate_subscriber.Init(self.LowStateMessageHandler, 10) |
|||
|
|||
|
|||
def LowStateMessageHandler(self, msg: LowState_): |
|||
self.low_state = msg |
|||
wireless_remote_data = self.low_state.wireless_remote |
|||
self.remoteController.parse(wireless_remote_data) |
|||
|
|||
|
|||
if __name__ == '__main__': |
|||
|
|||
print("WARNING: Please ensure there are no obstacles around the robot while running this example.") |
|||
input("Press Enter to continue...") |
|||
|
|||
if len(sys.argv)>1: |
|||
ChannelFactoryInitialize(0, sys.argv[1]) |
|||
else: |
|||
ChannelFactoryInitialize(0) |
|||
|
|||
custom = Custom() |
|||
custom.Init() |
|||
|
|||
while True: |
|||
time.sleep(1) |
|||
@ -0,0 +1,3 @@ |
|||
[build-system] |
|||
requires = ["setuptools", "wheel"] |
|||
build-backend = "setuptools.build_meta" |
|||
@ -0,0 +1,21 @@ |
|||
from setuptools import setup, find_packages |
|||
|
|||
setup(name='unitree_sdk2py', |
|||
version='1.0.1', |
|||
author='UnitreeRobotics', |
|||
author_email='unitree@unitree.com', |
|||
long_description=open('README.md').read(), |
|||
long_description_content_type="text/markdown", |
|||
license="BSD-3-Clause", |
|||
packages=find_packages(include=['unitree_sdk2py','unitree_sdk2py.*']), |
|||
description='Unitree robot sdk version 2 for python', |
|||
project_urls={ |
|||
"Source Code": "https://github.com/unitreerobotics/unitree_sdk2_python", |
|||
}, |
|||
python_requires='>=3.8', |
|||
install_requires=[ |
|||
"cyclonedds==0.10.2", |
|||
"numpy", |
|||
"opencv-python", |
|||
], |
|||
) |
|||
@ -0,0 +1,10 @@ |
|||
from . import idl, utils, core, rpc, go2, b2 |
|||
|
|||
__all__ = [ |
|||
"idl" |
|||
"utils" |
|||
"core", |
|||
"rpc", |
|||
"go2", |
|||
"b2", |
|||
] |
|||
@ -0,0 +1,16 @@ |
|||
""" |
|||
" service name |
|||
""" |
|||
ROBOT_BACK_VIDEO_SERVICE_NAME = "back_videohub" |
|||
|
|||
|
|||
""" |
|||
" service api version |
|||
""" |
|||
ROBOT_BACK_VIDEO_API_VERSION = "1.0.0.0" |
|||
|
|||
|
|||
""" |
|||
" api id |
|||
""" |
|||
ROBOT_BACK_VIDEO_API_ID_GETIMAGESAMPLE = 1001 |
|||
@ -0,0 +1,23 @@ |
|||
import json |
|||
|
|||
from ...rpc.client import Client |
|||
from .back_video_api import * |
|||
|
|||
|
|||
""" |
|||
" class FrontVideoClient |
|||
""" |
|||
class BackVideoClient(Client): |
|||
def __init__(self): |
|||
super().__init__(ROBOT_BACK_VIDEO_SERVICE_NAME, False) |
|||
|
|||
|
|||
def Init(self): |
|||
# set api version |
|||
self._SetApiVerson(ROBOT_BACK_VIDEO_API_VERSION) |
|||
# regist api |
|||
self._RegistApi(ROBOT_BACK_VIDEO_API_ID_GETIMAGESAMPLE, 0) |
|||
|
|||
# 1001 |
|||
def GetImageSample(self): |
|||
return self._CallBinary(ROBOT_BACK_VIDEO_API_ID_GETIMAGESAMPLE, []) |
|||
@ -0,0 +1,16 @@ |
|||
""" |
|||
" service name |
|||
""" |
|||
ROBOT_FRONT_VIDEO_SERVICE_NAME = "front_videohub" |
|||
|
|||
|
|||
""" |
|||
" service api version |
|||
""" |
|||
ROBOT_FRONT_VIDEO_API_VERSION = "1.0.0.0" |
|||
|
|||
|
|||
""" |
|||
" api id |
|||
""" |
|||
ROBOT_FRONT_VIDEO_API_ID_GETIMAGESAMPLE = 1001 |
|||
@ -0,0 +1,23 @@ |
|||
import json |
|||
|
|||
from ...rpc.client import Client |
|||
from .front_video_api import * |
|||
|
|||
|
|||
""" |
|||
" class FrontVideoClient |
|||
""" |
|||
class FrontVideoClient(Client): |
|||
def __init__(self): |
|||
super().__init__(ROBOT_FRONT_VIDEO_SERVICE_NAME, False) |
|||
|
|||
|
|||
def Init(self): |
|||
# set api version |
|||
self._SetApiVerson(ROBOT_FRONT_VIDEO_API_VERSION) |
|||
# regist api |
|||
self._RegistApi(ROBOT_FRONT_VIDEO_API_ID_GETIMAGESAMPLE, 0) |
|||
|
|||
# 1001 |
|||
def GetImageSample(self): |
|||
return self._CallBinary(ROBOT_FRONT_VIDEO_API_ID_GETIMAGESAMPLE, []) |
|||
@ -0,0 +1,25 @@ |
|||
""" |
|||
" service name |
|||
""" |
|||
ROBOT_STATE_SERVICE_NAME = "robot_state" |
|||
|
|||
|
|||
""" |
|||
" service api version |
|||
""" |
|||
ROBOT_STATE_API_VERSION = "1.0.0.1" |
|||
|
|||
|
|||
""" |
|||
" api id |
|||
""" |
|||
ROBOT_STATE_API_ID_SERVICE_SWITCH = 1001 |
|||
ROBOT_STATE_API_ID_REPORT_FREQ = 1002 |
|||
ROBOT_STATE_API_ID_SERVICE_LIST = 1003 |
|||
|
|||
|
|||
""" |
|||
" error code |
|||
""" |
|||
ROBOT_STATE_ERR_SERVICE_SWITCH = 5201 |
|||
ROBOT_STATE_ERR_SERVICE_PROTECTED = 5202 |
|||
@ -0,0 +1,84 @@ |
|||
import json |
|||
|
|||
from ...rpc.client import Client |
|||
from ...rpc.client_internal import * |
|||
from .robot_state_api import * |
|||
|
|||
|
|||
""" |
|||
" class ServiceState |
|||
""" |
|||
class ServiceState: |
|||
def __init__(self, name: str = None, status: int = None, protect: bool = None): |
|||
self.name = name |
|||
self.status = status |
|||
self.protect = protect |
|||
|
|||
""" |
|||
" class RobotStateClient |
|||
""" |
|||
class RobotStateClient(Client): |
|||
def __init__(self): |
|||
super().__init__(ROBOT_STATE_SERVICE_NAME, False) |
|||
|
|||
def Init(self): |
|||
# set api version |
|||
self._SetApiVerson(ROBOT_STATE_API_VERSION) |
|||
# regist api |
|||
self._RegistApi(ROBOT_STATE_API_ID_SERVICE_SWITCH, 0) |
|||
self._RegistApi(ROBOT_STATE_API_ID_REPORT_FREQ, 0) |
|||
self._RegistApi(ROBOT_STATE_API_ID_SERVICE_LIST, 0) |
|||
|
|||
def ServiceList(self): |
|||
p = {} |
|||
parameter = json.dumps(p) |
|||
|
|||
code, data = self._Call(ROBOT_STATE_API_ID_SERVICE_LIST, parameter) |
|||
|
|||
if code != 0: |
|||
return code, None |
|||
|
|||
lst = [] |
|||
|
|||
d = json.loads(data) |
|||
for t in d: |
|||
s = ServiceState() |
|||
s.name = t["name"] |
|||
s.status = t["status"] |
|||
s.protect = t["protect"] |
|||
lst.append(s) |
|||
|
|||
return code, lst |
|||
|
|||
|
|||
def ServiceSwitch(self, name: str, switch: bool): |
|||
p = {} |
|||
p["name"] = name |
|||
p["switch"] = int(switch) |
|||
parameter = json.dumps(p) |
|||
|
|||
code, data = self._Call(ROBOT_STATE_API_ID_SERVICE_SWITCH, parameter) |
|||
|
|||
if code != 0: |
|||
return code |
|||
|
|||
d = json.loads(data) |
|||
|
|||
status = d["status"] |
|||
|
|||
if status == 5: |
|||
return ROBOT_STATE_ERR_SERVICE_PROTECTED |
|||
|
|||
if status != 0 and status != 1: |
|||
return ROBOT_STATE_ERR_SERVICE_SWITCH |
|||
|
|||
return code |
|||
|
|||
def SetReportFreq(self, interval: int, duration: int): |
|||
p = {} |
|||
p["interval"] = interval |
|||
p["duration"] = duration |
|||
parameter = json.dumps(p) |
|||
|
|||
code, data = self._Call(ROBOT_STATE_API_ID_REPORT_FREQ, p) |
|||
return code |
|||
@ -0,0 +1,58 @@ |
|||
""" |
|||
" service name |
|||
""" |
|||
SPORT_SERVICE_NAME = "sport" |
|||
|
|||
|
|||
""" |
|||
" service api version |
|||
""" |
|||
SPORT_API_VERSION = "1.0.0.1" |
|||
|
|||
|
|||
""" |
|||
" api id |
|||
""" |
|||
SPORT_API_ID_DAMP = 1001; |
|||
SPORT_API_ID_BALANCESTAND = 1002; |
|||
SPORT_API_ID_STOPMOVE = 1003; |
|||
SPORT_API_ID_STANDUP = 1004; |
|||
SPORT_API_ID_STANDDOWN = 1005; |
|||
SPORT_API_ID_RECOVERYSTAND = 1006; |
|||
SPORT_API_ID_EULER = 1007; |
|||
SPORT_API_ID_MOVE = 1008; |
|||
SPORT_API_ID_SIT = 1009; |
|||
SPORT_API_ID_RISESIT = 1010; |
|||
SPORT_API_ID_SWITCHGAIT = 1011; |
|||
SPORT_API_ID_TRIGGER = 1012; |
|||
SPORT_API_ID_BODYHEIGHT = 1013; |
|||
SPORT_API_ID_FOOTRAISEHEIGHT = 1014; |
|||
SPORT_API_ID_SPEEDLEVEL = 1015; |
|||
SPORT_API_ID_HELLO = 1016; |
|||
SPORT_API_ID_STRETCH = 1017; |
|||
SPORT_API_ID_TRAJECTORYFOLLOW = 1018; |
|||
SPORT_API_ID_CONTINUOUSGAIT = 1019; |
|||
SPORT_API_ID_CONTENT = 1020; |
|||
SPORT_API_ID_WALLOW = 1021; |
|||
SPORT_API_ID_DANCE1 = 1022; |
|||
SPORT_API_ID_DANCE2 = 1023; |
|||
SPORT_API_ID_GETBODYHEIGHT = 1024; |
|||
SPORT_API_ID_GETFOOTRAISEHEIGHT = 1025; |
|||
SPORT_API_ID_GETSPEEDLEVEL = 1026; |
|||
SPORT_API_ID_SWITCHJOYSTICK = 1027; |
|||
SPORT_API_ID_POSE = 1028; |
|||
SPORT_API_ID_FRONTJUMP = 1031; |
|||
SPORT_API_ID_ECONOMICGAIT = 1035; |
|||
SPORT_API_ID_MOVETOPOS = 1036; |
|||
SPORT_API_ID_SWITCHEULERMODE = 1037; |
|||
SPORT_API_ID_SWITCHMOVEMODE = 1038; |
|||
|
|||
|
|||
""" |
|||
" error code |
|||
""" |
|||
# client side |
|||
SPORT_ERR_CLIENT_POINT_PATH = 4101 |
|||
# server side |
|||
SPORT_ERR_SERVER_OVERTIME = 4201 |
|||
SPORT_ERR_SERVER_NOT_INIT = 4202 |
|||
@ -0,0 +1,241 @@ |
|||
import json |
|||
|
|||
from ...rpc.client import Client |
|||
from .sport_api import * |
|||
|
|||
""" |
|||
" SPORT_PATH_POINT_SIZE |
|||
""" |
|||
SPORT_PATH_POINT_SIZE = 30 |
|||
|
|||
|
|||
""" |
|||
" class PathPoint |
|||
""" |
|||
class PathPoint: |
|||
def __init__(self, timeFromStart: float, x: float, y: float, yaw: float, vx: float, vy: float, vyaw: float): |
|||
self.timeFromStart = timeFromStart |
|||
self.x = x |
|||
self.y = y |
|||
self.yaw = yaw |
|||
self.vx = vx |
|||
self.vy = vy |
|||
self.vyaw = vyaw |
|||
|
|||
|
|||
""" |
|||
" class SportClient |
|||
""" |
|||
class SportClient(Client): |
|||
def __init__(self, enableLease: bool = False): |
|||
super().__init__(SPORT_SERVICE_NAME, enableLease) |
|||
|
|||
|
|||
def Init(self): |
|||
# set api version |
|||
self._SetApiVerson(SPORT_API_VERSION) |
|||
|
|||
# regist api |
|||
self._RegistApi(SPORT_API_ID_DAMP, 0) |
|||
self._RegistApi(SPORT_API_ID_BALANCESTAND, 0) |
|||
self._RegistApi(SPORT_API_ID_STOPMOVE, 0) |
|||
self._RegistApi(SPORT_API_ID_STANDUP, 0) |
|||
self._RegistApi(SPORT_API_ID_STANDDOWN, 0) |
|||
self._RegistApi(SPORT_API_ID_RECOVERYSTAND, 0) |
|||
self._RegistApi(SPORT_API_ID_EULER, 0) |
|||
self._RegistApi(SPORT_API_ID_MOVE, 0) |
|||
self._RegistApi(SPORT_API_ID_SIT, 0) |
|||
self._RegistApi(SPORT_API_ID_SWITCHGAIT, 0) |
|||
self._RegistApi(SPORT_API_ID_BODYHEIGHT, 0) |
|||
self._RegistApi(SPORT_API_ID_FOOTRAISEHEIGHT, 0) |
|||
self._RegistApi(SPORT_API_ID_SPEEDLEVEL, 0) |
|||
self._RegistApi(SPORT_API_ID_TRAJECTORYFOLLOW, 0) |
|||
self._RegistApi(SPORT_API_ID_CONTINUOUSGAIT, 0) |
|||
self._RegistApi(SPORT_API_ID_MOVETOPOS, 0) |
|||
self._RegistApi(SPORT_API_ID_FRONTJUMP, 0) |
|||
self._RegistApi(SPORT_API_ID_ECONOMICGAIT, 0) |
|||
self._RegistApi(SPORT_API_ID_POSE, 0) |
|||
self._RegistApi(SPORT_API_ID_SWITCHEULERMODE, 0) |
|||
self._RegistApi(SPORT_API_ID_SWITCHMOVEMODE, 0) |
|||
|
|||
# 1001 |
|||
def Damp(self): |
|||
p = {} |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(SPORT_API_ID_DAMP, parameter) |
|||
return code |
|||
|
|||
# 1002 |
|||
def BalanceStand(self): |
|||
p = {} |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(SPORT_API_ID_BALANCESTAND, parameter) |
|||
return code |
|||
|
|||
# 1003 |
|||
def StopMove(self): |
|||
p = {} |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(SPORT_API_ID_STOPMOVE, parameter) |
|||
return code |
|||
|
|||
# 1004 |
|||
def StandUp(self): |
|||
p = {} |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(SPORT_API_ID_STANDUP, parameter) |
|||
return code |
|||
|
|||
# 1005 |
|||
def StandDown(self): |
|||
p = {} |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(SPORT_API_ID_STANDDOWN, parameter) |
|||
return code |
|||
|
|||
# 1006 |
|||
def RecoveryStand(self): |
|||
p = {} |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(SPORT_API_ID_RECOVERYSTAND, parameter) |
|||
return code |
|||
|
|||
# 1007 |
|||
def Euler(self, roll: float, pitch: float, yaw: float): |
|||
p = {} |
|||
p["x"] = roll |
|||
p["y"] = pitch |
|||
p["z"] = yaw |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(SPORT_API_ID_EULER, parameter) |
|||
return code |
|||
|
|||
# 1008 |
|||
def Move(self, vx: float, vy: float, vyaw: float): |
|||
p = {} |
|||
p["x"] = vx |
|||
p["y"] = vy |
|||
p["z"] = vyaw |
|||
parameter = json.dumps(p) |
|||
code = self._CallNoReply(SPORT_API_ID_MOVE, parameter) |
|||
return code |
|||
|
|||
# 1009 |
|||
def Sit(self): |
|||
p = {} |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(SPORT_API_ID_SIT, parameter) |
|||
return code |
|||
|
|||
# 1011 |
|||
def SwitchGait(self, t: int): |
|||
p = {} |
|||
p["data"] = t |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(SPORT_API_ID_SWITCHGAIT, parameter) |
|||
return code |
|||
|
|||
# 1013 |
|||
def BodyHeight(self, height: float): |
|||
p = {} |
|||
p["data"] = height |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(SPORT_API_ID_BODYHEIGHT, parameter) |
|||
return code |
|||
|
|||
# 1014 |
|||
def FootRaiseHeight(self, height: float): |
|||
p = {} |
|||
p["data"] = height |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(SPORT_API_ID_FOOTRAISEHEIGHT, parameter) |
|||
return code |
|||
|
|||
# 1015 |
|||
def SpeedLevel(self, level: int): |
|||
p = {} |
|||
p["data"] = level |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(SPORT_API_ID_SPEEDLEVEL, parameter) |
|||
return code |
|||
|
|||
# 1018 |
|||
def TrajectoryFollow(self, path: list): |
|||
l = len(path) |
|||
if l != SPORT_PATH_POINT_SIZE: |
|||
return SPORT_ERR_CLIENT_POINT_PATH |
|||
|
|||
path_p = [] |
|||
for i in range(l): |
|||
point = path[i] |
|||
p = {} |
|||
p["t_from_start"] = point.timeFromStart |
|||
p["x"] = point.x |
|||
p["y"] = point.y |
|||
p["yaw"] = point.yaw |
|||
p["vx"] = point.vx |
|||
p["vy"] = point.vy |
|||
p["vyaw"] = point.vyaw |
|||
path_p.append(p) |
|||
|
|||
parameter = json.dumps(path_p) |
|||
code = self._CallNoReply(SPORT_API_ID_TRAJECTORYFOLLOW, parameter) |
|||
return code |
|||
|
|||
# 1019 |
|||
def ContinuousGait(self, flag: int): |
|||
p = {} |
|||
p["data"] = flag |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(SPORT_API_ID_CONTINUOUSGAIT, parameter) |
|||
return code |
|||
|
|||
# 1036 |
|||
def MoveToPos(self, x: float, y: float, yaw: float): |
|||
p = {} |
|||
p["x"] = x |
|||
p["y"] = y |
|||
p["yaw"] = yaw |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(SPORT_API_ID_MOVETOPOS, parameter) |
|||
return code |
|||
|
|||
# 1031 |
|||
def FrontJump(self): |
|||
p = {} |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(SPORT_API_ID_FRONTJUMP, parameter) |
|||
return code |
|||
|
|||
|
|||
# 1035 |
|||
def EconomicGait(self, flag: bool): |
|||
p = {} |
|||
p["data"] = flag |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(SPORT_API_ID_ECONOMICGAIT, parameter) |
|||
return code |
|||
|
|||
# 1028 |
|||
def Pose(self, flag: bool): |
|||
p = {} |
|||
p["data"] = flag |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(SPORT_API_ID_POSE, parameter) |
|||
return code |
|||
|
|||
# 1037 |
|||
def SwitchEulerMode(self, flag: bool): |
|||
p = {} |
|||
p["data"] = flag |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(SPORT_API_ID_SWITCHEULERMODE, parameter) |
|||
return code |
|||
|
|||
# 1038 |
|||
def SwitchMoveMode(self, flag: bool): |
|||
p = {} |
|||
p["data"] = flag |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(SPORT_API_ID_SWITCHMOVEMODE, parameter) |
|||
return code |
|||
@ -0,0 +1,21 @@ |
|||
""" |
|||
" service name |
|||
""" |
|||
VUI_SERVICE_NAME = "vui" |
|||
|
|||
|
|||
""" |
|||
" service api version |
|||
""" |
|||
VUI_API_VERSION = "1.0.0.1" |
|||
|
|||
|
|||
""" |
|||
" api id |
|||
""" |
|||
VUI_API_ID_SETSWITCH = 1001 |
|||
VUI_API_ID_GETSWITCH = 1002 |
|||
VUI_API_ID_SETVOLUME = 1003 |
|||
VUI_API_ID_GETVOLUME = 1004 |
|||
VUI_API_ID_SETBRIGHTNESS = 1005 |
|||
VUI_API_ID_GETBRIGHTNESS = 1006 |
|||
@ -0,0 +1,86 @@ |
|||
import json |
|||
|
|||
from ...rpc.client import Client |
|||
from .vui_api import * |
|||
|
|||
|
|||
""" |
|||
" class VideoClient |
|||
""" |
|||
class VuiClient(Client): |
|||
def __init__(self): |
|||
super().__init__(VUI_SERVICE_NAME, False) |
|||
|
|||
def Init(self): |
|||
# set api version |
|||
self._SetApiVerson(VUI_API_VERSION) |
|||
# regist api |
|||
self._RegistApi(VUI_API_ID_SETSWITCH, 0) |
|||
self._RegistApi(VUI_API_ID_GETSWITCH, 0) |
|||
self._RegistApi(VUI_API_ID_SETVOLUME, 0) |
|||
self._RegistApi(VUI_API_ID_GETVOLUME, 0) |
|||
self._RegistApi(VUI_API_ID_SETBRIGHTNESS, 0) |
|||
self._RegistApi(VUI_API_ID_GETBRIGHTNESS, 0) |
|||
|
|||
# 1001 |
|||
def SetSwitch(self, enable: int): |
|||
p = {} |
|||
p["enable"] = enable |
|||
parameter = json.dumps(p) |
|||
|
|||
code, data = self._Call(VUI_API_ID_SETSWITCH, parameter) |
|||
return code |
|||
|
|||
# 1002 |
|||
def GetSwitch(self): |
|||
p = {} |
|||
parameter = json.dumps(p) |
|||
|
|||
code, data = self._Call(VUI_API_ID_GETSWITCH, parameter) |
|||
if code == 0: |
|||
d = json.loads(data) |
|||
return code, d["enable"] |
|||
else: |
|||
return code, None |
|||
|
|||
# 1003 |
|||
def SetVolume(self, level: int): |
|||
p = {} |
|||
p["volume"] = level |
|||
parameter = json.dumps(p) |
|||
|
|||
code, data = self._Call(VUI_API_ID_SETVOLUME, parameter) |
|||
return code |
|||
|
|||
# 1006 |
|||
def GetVolume(self): |
|||
p = {} |
|||
parameter = json.dumps(p) |
|||
|
|||
code, data = self._Call(VUI_API_ID_GETVOLUME, parameter) |
|||
if code == 0: |
|||
d = json.loads(data) |
|||
return code, d["volume"] |
|||
else: |
|||
return code, None |
|||
|
|||
# 1005 |
|||
def SetBrightness(self, level: int): |
|||
p = {} |
|||
p["brightness"] = level |
|||
parameter = json.dumps(p) |
|||
|
|||
code, data = self._Call(VUI_API_ID_SETBRIGHTNESS, parameter) |
|||
return code |
|||
|
|||
# 1006 |
|||
def GetBrightness(self): |
|||
p = {} |
|||
parameter = json.dumps(p) |
|||
|
|||
code, data = self._Call(VUI_API_ID_GETBRIGHTNESS, parameter) |
|||
if code == 0: |
|||
d = json.loads(data) |
|||
return code, d["brightness"] |
|||
else: |
|||
return code, None |
|||
@ -0,0 +1,29 @@ |
|||
""" |
|||
" service name |
|||
""" |
|||
MOTION_SWITCHER_SERVICE_NAME = "motion_switcher" |
|||
|
|||
|
|||
""" |
|||
" service api version |
|||
""" |
|||
MOTION_SWITCHER_API_VERSION = "1.0.0.1" |
|||
|
|||
|
|||
""" |
|||
" api id |
|||
""" |
|||
MOTION_SWITCHER_API_ID_CHECK_MODE = 1001 |
|||
MOTION_SWITCHER_API_ID_SELECT_MODE = 1002 |
|||
MOTION_SWITCHER_API_ID_RELEASE_MODE = 1003 |
|||
MOTION_SWITCHER_API_ID_SET_SILENT = 1004 |
|||
MOTION_SWITCHER_API_ID_GET_SILENT = 1005 |
|||
|
|||
# """ |
|||
# " error code |
|||
# """ |
|||
# # client side |
|||
# SPORT_ERR_CLIENT_POINT_PATH = 4101 |
|||
# # server side |
|||
# SPORT_ERR_SERVER_OVERTIME = 4201 |
|||
# SPORT_ERR_SERVER_NOT_INIT = 4202 |
|||
@ -0,0 +1,51 @@ |
|||
import json |
|||
|
|||
from ...rpc.client import Client |
|||
from .motion_switcher_api import * |
|||
|
|||
""" |
|||
" class MotionSwitcherClient |
|||
""" |
|||
class MotionSwitcherClient(Client): |
|||
def __init__(self): |
|||
super().__init__(MOTION_SWITCHER_SERVICE_NAME, False) |
|||
|
|||
|
|||
def Init(self): |
|||
# set api version |
|||
self._SetApiVerson(MOTION_SWITCHER_API_VERSION) |
|||
|
|||
# regist api |
|||
self._RegistApi(MOTION_SWITCHER_API_ID_CHECK_MODE, 0) |
|||
self._RegistApi(MOTION_SWITCHER_API_ID_SELECT_MODE, 0) |
|||
self._RegistApi(MOTION_SWITCHER_API_ID_RELEASE_MODE, 0) |
|||
self._RegistApi(MOTION_SWITCHER_API_ID_SET_SILENT, 0) |
|||
self._RegistApi(MOTION_SWITCHER_API_ID_GET_SILENT, 0) |
|||
|
|||
# 1001 |
|||
def CheckMode(self): |
|||
p = {} |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(MOTION_SWITCHER_API_ID_CHECK_MODE, parameter) |
|||
if code == 0: |
|||
return code, json.loads(data) |
|||
else: |
|||
return code, None |
|||
|
|||
# 1002 |
|||
def SelectMode(self, nameOrAlias): |
|||
p = {} |
|||
p["name"] = nameOrAlias |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(MOTION_SWITCHER_API_ID_SELECT_MODE, parameter) |
|||
|
|||
return code, None |
|||
|
|||
# 1003 |
|||
def ReleaseMode(self): |
|||
p = {} |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(MOTION_SWITCHER_API_ID_RELEASE_MODE, parameter) |
|||
|
|||
return code, None |
|||
|
|||
@ -0,0 +1,290 @@ |
|||
import time |
|||
from typing import Any, Callable |
|||
from threading import Thread, Event |
|||
|
|||
from cyclonedds.domain import Domain, DomainParticipant |
|||
from cyclonedds.internal import dds_c_t |
|||
from cyclonedds.pub import DataWriter |
|||
from cyclonedds.sub import DataReader |
|||
from cyclonedds.topic import Topic |
|||
from cyclonedds.qos import Qos |
|||
from cyclonedds.core import DDSException, Listener |
|||
from cyclonedds.util import duration |
|||
from cyclonedds.internal import dds_c_t, InvalidSample |
|||
|
|||
# for channel config |
|||
from .channel_config import ChannelConfigAutoDetermine, ChannelConfigHasInterface |
|||
|
|||
# for singleton |
|||
from ..utils.singleton import Singleton |
|||
from ..utils.bqueue import BQueue |
|||
|
|||
|
|||
""" |
|||
" class ChannelReader |
|||
""" |
|||
|
|||
""" |
|||
" class Channel |
|||
""" |
|||
class Channel: |
|||
|
|||
""" |
|||
" internal class __Reader |
|||
""" |
|||
class __Reader: |
|||
def __init__(self): |
|||
self.__reader = None |
|||
self.__handler = None |
|||
self.__queue = None |
|||
self.__queueEnable = False |
|||
self.__threadEvent = None |
|||
self.__threadReader = None |
|||
|
|||
def Init(self, participant: DomainParticipant, topic: Topic, qos: Qos = None, handler: Callable = None, queueLen: int = 0): |
|||
if handler is None: |
|||
self.__reader = DataReader(participant, topic, qos) |
|||
else: |
|||
self.__handler = handler |
|||
if queueLen > 0: |
|||
self.__queueEnable = True |
|||
self.__queue = BQueue(queueLen) |
|||
self.__threadEvent = Event() |
|||
self.__threadReader = Thread(target=self.__ChannelReaderThreadFunc, name="ch_reader", daemon=True) |
|||
self.__threadReader.start() |
|||
self.__reader = DataReader(participant, topic, qos, Listener(on_data_available=self.__OnDataAvailable)) |
|||
|
|||
def Read(self, timeout: float = None): |
|||
sample = None |
|||
try: |
|||
if timeout is None: |
|||
sample = self.__reader.take_one() |
|||
else: |
|||
sample = self.__reader.take_one(timeout=duration(seconds=timeout)) |
|||
except DDSException as e: |
|||
print("[Reader] catch DDSException msg:", e.msg) |
|||
except TimeoutError as e: |
|||
print("[Reader] take sample timeout") |
|||
except: |
|||
print("[Reader] take sample error") |
|||
|
|||
return sample |
|||
|
|||
def Close(self): |
|||
if self.__reader is not None: |
|||
del self.__reader |
|||
|
|||
if self.__queueEnable: |
|||
self.__threadEvent.set() |
|||
self.__queue.Interrupt() |
|||
self.__queue.Clear() |
|||
self.__threadReader.join() |
|||
|
|||
def __OnDataAvailable(self, reader: DataReader): |
|||
samples = [] |
|||
try: |
|||
samples = reader.take(1) |
|||
except DDSException as e: |
|||
print("[Reader] catch DDSException error. msg:", e.msg) |
|||
return |
|||
except TimeoutError as e: |
|||
print("[Reader] take sample timeout") |
|||
return |
|||
except: |
|||
print("[Reader] take sample error") |
|||
return |
|||
|
|||
if samples is None: |
|||
return |
|||
|
|||
# check invalid sample |
|||
sample = samples[0] |
|||
if isinstance(sample, InvalidSample): |
|||
return |
|||
|
|||
# do sample |
|||
if self.__queueEnable: |
|||
self.__queue.Put(sample) |
|||
else: |
|||
self.__handler(sample) |
|||
|
|||
def __ChannelReaderThreadFunc(self): |
|||
while not self.__threadEvent.is_set(): |
|||
sample = self.__queue.Get() |
|||
if sample is not None: |
|||
self.__handler(sample) |
|||
|
|||
""" |
|||
" internal class __Writer |
|||
""" |
|||
class __Writer: |
|||
def __init__(self): |
|||
self.__writer = None |
|||
self.__publication_matched_count = 0 |
|||
|
|||
def Init(self, participant: DomainParticipant, topic: Topic, qos: Qos = None): |
|||
self.__writer = DataWriter(participant, topic, qos, Listener(on_publication_matched=self.__OnPublicationMatched)) |
|||
time.sleep(0.2) |
|||
|
|||
def Write(self, sample: Any, timeout: float = None): |
|||
waitsec = 0.0 if timeout is None else timeout |
|||
|
|||
# check publication_matched_count |
|||
while waitsec > 0.0 and self.__publication_matched_count == 0: |
|||
time.sleep(0.1) |
|||
waitsec = waitsec - 0.1 |
|||
# print(time.time()) |
|||
|
|||
# check waitsec |
|||
if timeout is not None and waitsec <= 0.0: |
|||
return False |
|||
|
|||
try: |
|||
self.__writer.write(sample) |
|||
except DDSException as e: |
|||
print("[Writer] catch DDSException error. msg:", e.msg) |
|||
return False |
|||
except Exception as e: |
|||
print("[Writer] write sample error. msg:", e.args()) |
|||
return False |
|||
|
|||
return True |
|||
|
|||
def Close(self): |
|||
if self.__writer is not None: |
|||
del self.__writer |
|||
|
|||
def __OnPublicationMatched(self, writer: DataWriter, status: dds_c_t.publication_matched_status): |
|||
self.__publication_matched_count = status.current_count |
|||
|
|||
|
|||
# channel __init__ |
|||
def __init__(self, participant: DomainParticipant, name: str, type: Any, qos: Qos = None): |
|||
self.__reader = self.__Reader() |
|||
self.__writer = self.__Writer() |
|||
self.__participant = participant |
|||
self.__topic = Topic(self.__participant, name, type, qos) |
|||
|
|||
def SetWriter(self, qos: Qos = None): |
|||
self.__writer.Init(self.__participant, self.__topic, qos) |
|||
|
|||
def SetReader(self, qos: Qos = None, handler: Callable = None, queueLen: int = 0): |
|||
self.__reader.Init(self.__participant, self.__topic, qos, handler, queueLen) |
|||
|
|||
def Write(self, sample: Any, timeout: float = None): |
|||
return self.__writer.Write(sample, timeout) |
|||
|
|||
def Read(self, timeout: float = None): |
|||
return self.__reader.Read(timeout) |
|||
|
|||
def CloseReader(self): |
|||
self.__reader.Close() |
|||
|
|||
def CloseWriter(self): |
|||
self.__writer.Close() |
|||
|
|||
|
|||
""" |
|||
" class ChannelFactory |
|||
""" |
|||
class ChannelFactory(Singleton): |
|||
__domain = None |
|||
__participant = None |
|||
__qos = None |
|||
|
|||
def __init__(self): |
|||
super().__init__() |
|||
|
|||
def Init(self, id: int, networkInterface: str = None, qos: Qos = None): |
|||
config = None |
|||
# choose config |
|||
if networkInterface is None: |
|||
config = ChannelConfigAutoDetermine |
|||
else: |
|||
config = ChannelConfigHasInterface.replace('$__IF_NAME__$', networkInterface) |
|||
|
|||
try: |
|||
self.__domain = Domain(id, config) |
|||
except DDSException as e: |
|||
print("[ChannelFactory] create domain error. msg:", e.msg) |
|||
return False |
|||
except: |
|||
print("[ChannelFactory] create domain error.") |
|||
return False |
|||
|
|||
try: |
|||
self.__participant = DomainParticipant(id) |
|||
except DDSException as e: |
|||
print("[ChannelFactory] create domain participant error. msg:", e.msg) |
|||
return False |
|||
except: |
|||
print("[ChannelFactory] create domain participant error") |
|||
return False |
|||
|
|||
self.__qos = qos |
|||
|
|||
return True |
|||
|
|||
def CreateChannel(self, name: str, type: Any): |
|||
return Channel(self.__participant, name, type, self.__qos) |
|||
|
|||
def CreateSendChannel(self, name: str, type: Any): |
|||
channel = self.CreateChannel(name, type) |
|||
channel.SetWriter(None) |
|||
return channel |
|||
|
|||
def CreateRecvChannel(self, name: str, type: Any, handler: Callable = None, queueLen: int = 0): |
|||
channel = self.CreateChannel(name, type) |
|||
channel.SetReader(None, handler, queueLen) |
|||
return channel |
|||
|
|||
|
|||
""" |
|||
" class ChannelPublisher |
|||
""" |
|||
class ChannelPublisher: |
|||
def __init__(self, name: str, type: Any): |
|||
factory = ChannelFactory() |
|||
self.__channel = factory.CreateChannel(name, type) |
|||
self.__inited = False |
|||
|
|||
def Init(self): |
|||
if not self.__inited: |
|||
self.__channel.SetWriter(None) |
|||
self.__inited = True |
|||
|
|||
def Close(self): |
|||
self.__channel.CloseWriter() |
|||
self.__inited = False |
|||
|
|||
def Write(self, sample: Any, timeout: float = None): |
|||
return self.__channel.Write(sample, timeout) |
|||
|
|||
""" |
|||
" class ChannelSubscriber |
|||
""" |
|||
class ChannelSubscriber: |
|||
def __init__(self, name: str, type: Any): |
|||
factory = ChannelFactory() |
|||
self.__channel = factory.CreateChannel(name, type) |
|||
self.__inited = False |
|||
|
|||
def Init(self, handler: Callable = None, queueLen: int = 0): |
|||
if not self.__inited: |
|||
self.__channel.SetReader(None, handler, queueLen) |
|||
self.__inited = True |
|||
|
|||
def Close(self): |
|||
self.__channel.CloseReader() |
|||
self.__inited = False |
|||
|
|||
def Read(self, timeout: float = None): |
|||
return self.__channel.Read(timeout) |
|||
|
|||
""" |
|||
" function ChannelFactoryInitialize. used to intialize channel everenment. |
|||
""" |
|||
def ChannelFactoryInitialize(id: int = 0, networkInterface: str = None): |
|||
factory = ChannelFactory() |
|||
if not factory.Init(id, networkInterface): |
|||
raise Exception("channel factory init error.") |
|||
@ -0,0 +1,25 @@ |
|||
ChannelConfigHasInterface = '''<?xml version="1.0" encoding="UTF-8" ?> |
|||
<CycloneDDS> |
|||
<Domain Id="any"> |
|||
<General> |
|||
<Interfaces> |
|||
<NetworkInterface name="$__IF_NAME__$" priority="default" multicast="default"/> |
|||
</Interfaces> |
|||
</General> |
|||
<Tracing> |
|||
<Verbosity>config</Verbosity> |
|||
<OutputFile>/tmp/cdds.LOG</OutputFile> |
|||
</Tracing> |
|||
</Domain> |
|||
</CycloneDDS>''' |
|||
|
|||
ChannelConfigAutoDetermine = '''<?xml version="1.0" encoding="UTF-8" ?> |
|||
<CycloneDDS> |
|||
<Domain Id="any"> |
|||
<General> |
|||
<Interfaces> |
|||
<NetworkInterface autodetermine=\"true\" priority=\"default\" multicast=\"default\" /> |
|||
</Interfaces> |
|||
</General> |
|||
</Domain> |
|||
</CycloneDDS>''' |
|||
@ -0,0 +1,34 @@ |
|||
from enum import Enum |
|||
|
|||
""" |
|||
" Enum ChannelType |
|||
""" |
|||
class ChannelType(Enum): |
|||
SEND = 0 |
|||
RECV = 1 |
|||
|
|||
""" |
|||
" function GetClientChannelName |
|||
""" |
|||
def GetClientChannelName(serviceName: str, channelType: ChannelType): |
|||
name = "rt/api/" + serviceName |
|||
|
|||
if channelType == ChannelType.SEND: |
|||
name += "/request" |
|||
else: |
|||
name += "/response" |
|||
|
|||
return name |
|||
|
|||
""" |
|||
" function GetClientChannelName |
|||
""" |
|||
def GetServerChannelName(serviceName: str, channelType: ChannelType): |
|||
name = "rt/api/" + serviceName |
|||
|
|||
if channelType == ChannelType.SEND: |
|||
name += "/response" |
|||
else: |
|||
name += "/request" |
|||
|
|||
return name |
|||
@ -0,0 +1,24 @@ |
|||
""" |
|||
" service name |
|||
""" |
|||
AUDIO_SERVICE_NAME = "voice" |
|||
|
|||
""" |
|||
" service api version |
|||
""" |
|||
AUDIO_API_VERSION = "1.0.0.0" |
|||
|
|||
""" |
|||
" api id |
|||
""" |
|||
ROBOT_API_ID_AUDIO_TTS = 1001 |
|||
ROBOT_API_ID_AUDIO_ASR = 1002 |
|||
ROBOT_API_ID_AUDIO_START_PLAY = 1003 |
|||
ROBOT_API_ID_AUDIO_STOP_PLAY = 1004 |
|||
ROBOT_API_ID_AUDIO_GET_VOLUME = 1005 |
|||
ROBOT_API_ID_AUDIO_SET_VOLUME = 1006 |
|||
ROBOT_API_ID_AUDIO_SET_RGB_LED = 1010 |
|||
|
|||
""" |
|||
" error code |
|||
""" |
|||
@ -0,0 +1,62 @@ |
|||
import json |
|||
|
|||
from ...rpc.client import Client |
|||
from .g1_audio_api import * |
|||
|
|||
""" |
|||
" class SportClient |
|||
""" |
|||
class AudioClient(Client): |
|||
def __init__(self): |
|||
super().__init__(AUDIO_SERVICE_NAME, False) |
|||
self.tts_index = 0 |
|||
|
|||
def Init(self): |
|||
# set api version |
|||
self._SetApiVerson(AUDIO_API_VERSION) |
|||
|
|||
# regist api |
|||
self._RegistApi(ROBOT_API_ID_AUDIO_TTS, 0) |
|||
self._RegistApi(ROBOT_API_ID_AUDIO_ASR, 0) |
|||
self._RegistApi(ROBOT_API_ID_AUDIO_START_PLAY, 0) |
|||
self._RegistApi(ROBOT_API_ID_AUDIO_STOP_PLAY, 0) |
|||
self._RegistApi(ROBOT_API_ID_AUDIO_GET_VOLUME, 0) |
|||
self._RegistApi(ROBOT_API_ID_AUDIO_SET_VOLUME, 0) |
|||
self._RegistApi(ROBOT_API_ID_AUDIO_SET_RGB_LED, 0) |
|||
|
|||
## API Call ## |
|||
def TtsMaker(self, text: str, speaker_id: int): |
|||
self.tts_index += self.tts_index |
|||
p = {} |
|||
p["index"] = self.tts_index |
|||
p["text"] = text |
|||
p["speaker_id"] = speaker_id |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(ROBOT_API_ID_AUDIO_TTS, parameter) |
|||
return code |
|||
|
|||
def GetVolume(self): |
|||
p = {} |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(ROBOT_API_ID_AUDIO_GET_VOLUME, parameter) |
|||
if code == 0: |
|||
return code, json.loads(data) |
|||
else: |
|||
return code, None |
|||
|
|||
def SetVolume(self, volume: int): |
|||
p = {} |
|||
p["volume"] = volume |
|||
# p["name"] = 'volume' |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(ROBOT_API_ID_AUDIO_SET_VOLUME, parameter) |
|||
return code |
|||
|
|||
def LedControl(self, R: int, G: int, B: int): |
|||
p = {} |
|||
p["R"] = R |
|||
p["G"] = G |
|||
p["B"] = B |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(ROBOT_API_ID_AUDIO_SET_RGB_LED, parameter) |
|||
return code |
|||
@ -0,0 +1,32 @@ |
|||
""" |
|||
" service name |
|||
""" |
|||
LOCO_SERVICE_NAME = "loco" |
|||
|
|||
|
|||
""" |
|||
" service api version |
|||
""" |
|||
LOCO_API_VERSION = "1.0.0.0" |
|||
|
|||
|
|||
""" |
|||
" api id |
|||
""" |
|||
ROBOT_API_ID_LOCO_GET_FSM_ID = 7001 |
|||
ROBOT_API_ID_LOCO_GET_FSM_MODE = 7002 |
|||
ROBOT_API_ID_LOCO_GET_BALANCE_MODE = 7003 |
|||
ROBOT_API_ID_LOCO_GET_SWING_HEIGHT = 7004 |
|||
ROBOT_API_ID_LOCO_GET_STAND_HEIGHT = 7005 |
|||
ROBOT_API_ID_LOCO_GET_PHASE = 7006 # deprecated |
|||
|
|||
ROBOT_API_ID_LOCO_SET_FSM_ID = 7101 |
|||
ROBOT_API_ID_LOCO_SET_BALANCE_MODE = 7102 |
|||
ROBOT_API_ID_LOCO_SET_SWING_HEIGHT = 7103 |
|||
ROBOT_API_ID_LOCO_SET_STAND_HEIGHT = 7104 |
|||
ROBOT_API_ID_LOCO_SET_VELOCITY = 7105 |
|||
ROBOT_API_ID_LOCO_SET_ARM_TASK = 7106 |
|||
|
|||
""" |
|||
" error code |
|||
""" |
|||
@ -0,0 +1,127 @@ |
|||
import json |
|||
|
|||
from ...rpc.client import Client |
|||
from .g1_loco_api import * |
|||
|
|||
""" |
|||
" class SportClient |
|||
""" |
|||
class LocoClient(Client): |
|||
def __init__(self): |
|||
super().__init__(LOCO_SERVICE_NAME, False) |
|||
self.first_shake_hand_stage_ = -1 |
|||
|
|||
def Init(self): |
|||
# set api version |
|||
self._SetApiVerson(LOCO_API_VERSION) |
|||
|
|||
# regist api |
|||
self._RegistApi(ROBOT_API_ID_LOCO_GET_FSM_ID, 0) |
|||
self._RegistApi(ROBOT_API_ID_LOCO_GET_FSM_MODE, 0) |
|||
self._RegistApi(ROBOT_API_ID_LOCO_GET_BALANCE_MODE, 0) |
|||
self._RegistApi(ROBOT_API_ID_LOCO_GET_SWING_HEIGHT, 0) |
|||
self._RegistApi(ROBOT_API_ID_LOCO_GET_STAND_HEIGHT, 0) |
|||
self._RegistApi(ROBOT_API_ID_LOCO_GET_PHASE, 0) # deprecated |
|||
|
|||
self._RegistApi(ROBOT_API_ID_LOCO_SET_FSM_ID, 0) |
|||
self._RegistApi(ROBOT_API_ID_LOCO_SET_BALANCE_MODE, 0) |
|||
self._RegistApi(ROBOT_API_ID_LOCO_SET_SWING_HEIGHT, 0) |
|||
self._RegistApi(ROBOT_API_ID_LOCO_SET_STAND_HEIGHT, 0) |
|||
self._RegistApi(ROBOT_API_ID_LOCO_SET_VELOCITY, 0) |
|||
self._RegistApi(ROBOT_API_ID_LOCO_SET_ARM_TASK, 0) |
|||
|
|||
# 7101 |
|||
def SetFsmId(self, fsm_id: int): |
|||
p = {} |
|||
p["data"] = fsm_id |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(ROBOT_API_ID_LOCO_SET_FSM_ID, parameter) |
|||
return code |
|||
|
|||
# 7102 |
|||
def SetBalanceMode(self, balance_mode: int): |
|||
p = {} |
|||
p["data"] = balance_mode |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(ROBOT_API_ID_LOCO_SET_BALANCE_MODE, parameter) |
|||
return code |
|||
|
|||
# 7104 |
|||
def SetStandHeight(self, stand_height: float): |
|||
p = {} |
|||
p["data"] = stand_height |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(ROBOT_API_ID_LOCO_SET_STAND_HEIGHT, parameter) |
|||
return code |
|||
|
|||
# 7105 |
|||
def SetVelocity(self, vx: float, vy: float, omega: float, duration: float = 1.0): |
|||
p = {} |
|||
velocity = [vx,vy,omega] |
|||
p["velocity"] = velocity |
|||
p["duration"] = duration |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(ROBOT_API_ID_LOCO_SET_VELOCITY, parameter) |
|||
return code |
|||
|
|||
# 7106 |
|||
def SetTaskId(self, task_id: float): |
|||
p = {} |
|||
p["data"] = task_id |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(ROBOT_API_ID_LOCO_SET_ARM_TASK, parameter) |
|||
return code |
|||
|
|||
def Damp(self): |
|||
self.SetFsmId(1) |
|||
|
|||
def Start(self): |
|||
self.SetFsmId(200) |
|||
|
|||
def Squat2StandUp(self): |
|||
self.SetFsmId(706) |
|||
|
|||
def Lie2StandUp(self): |
|||
self.SetFsmId(702) |
|||
|
|||
def Sit(self): |
|||
self.SetFsmId(3) |
|||
|
|||
def StandUp2Squat(self): |
|||
self.SetFsmId(706) |
|||
|
|||
def ZeroTorque(self): |
|||
self.SetFsmId(0) |
|||
|
|||
def StopMove(self): |
|||
self.SetVelocity(0., 0., 0.) |
|||
|
|||
def HighStand(self): |
|||
UINT32_MAX = (1 << 32) - 1 |
|||
self.SetStandHeight(UINT32_MAX) |
|||
|
|||
def LowStand(self): |
|||
UINT32_MIN = 0 |
|||
self.SetStandHeight(UINT32_MIN) |
|||
|
|||
def Move(self, vx: float, vy: float, vyaw: float, continous_move: bool = False): |
|||
duration = 864000.0 if continous_move else 1 |
|||
self.SetVelocity(vx, vy, vyaw, duration) |
|||
|
|||
def BalanceStand(self, balance_mode: int): |
|||
self.SetBalanceMode(balance_mode) |
|||
|
|||
def WaveHand(self, turn_flag: bool = False): |
|||
self.SetTaskId(1 if turn_flag else 0) |
|||
|
|||
def ShakeHand(self, stage: int = -1): |
|||
if stage == 0: |
|||
self.first_shake_hand_stage_ = False |
|||
self.SetTaskId(2) |
|||
elif stage == 1: |
|||
self.first_shake_hand_stage_ = True |
|||
self.SetTaskId(3) |
|||
else: |
|||
self.first_shake_hand_stage_ = not self.first_shake_hand_stage_ |
|||
return self.SetTaskId(3 if self.first_shake_hand_stage_ else 2) |
|||
|
|||
@ -0,0 +1,19 @@ |
|||
""" |
|||
" service name |
|||
""" |
|||
OBSTACLES_AVOID_SERVICE_NAME = "obstacles_avoid" |
|||
|
|||
|
|||
""" |
|||
" service api version |
|||
""" |
|||
OBSTACLES_AVOID_API_VERSION = "1.0.0.2" |
|||
|
|||
|
|||
""" |
|||
" api id |
|||
""" |
|||
OBSTACLES_AVOID_API_ID_SWITCH_SET = 1001 |
|||
OBSTACLES_AVOID_API_ID_SWITCH_GET = 1002 |
|||
OBSTACLES_AVOID_API_ID_MOVE = 1003 |
|||
OBSTACLES_AVOID_API_ID_USE_REMOTE_COMMAND_FROM_API = 1004 |
|||
@ -0,0 +1,60 @@ |
|||
import json |
|||
|
|||
from ...rpc.client import Client |
|||
from .obstacles_avoid_api import * |
|||
|
|||
|
|||
""" |
|||
" class ObstaclesAvoidClient |
|||
""" |
|||
class ObstaclesAvoidClient(Client): |
|||
def __init__(self): |
|||
super().__init__(OBSTACLES_AVOID_SERVICE_NAME, False) |
|||
|
|||
def Init(self): |
|||
# set api version |
|||
self._SetApiVerson(OBSTACLES_AVOID_API_VERSION) |
|||
# regist api |
|||
self._RegistApi(OBSTACLES_AVOID_API_ID_SWITCH_SET, 0) |
|||
self._RegistApi(OBSTACLES_AVOID_API_ID_SWITCH_GET, 0) |
|||
self._RegistApi(OBSTACLES_AVOID_API_ID_MOVE, 0) |
|||
self._RegistApi(OBSTACLES_AVOID_API_ID_USE_REMOTE_COMMAND_FROM_API, 0) |
|||
|
|||
# 1001 |
|||
def SwitchSet(self, on: bool): |
|||
p = {} |
|||
p["enable"] = on |
|||
parameter = json.dumps(p) |
|||
|
|||
code, data = self._Call(OBSTACLES_AVOID_API_ID_SWITCH_SET, parameter) |
|||
return code |
|||
|
|||
# 1002 |
|||
def SwitchGet(self): |
|||
p = {} |
|||
parameter = json.dumps(p) |
|||
|
|||
code, data = self._Call(OBSTACLES_AVOID_API_ID_SWITCH_GET, parameter) |
|||
if code == 0: |
|||
d = json.loads(data) |
|||
return code, d["enable"] |
|||
else: |
|||
return code, None |
|||
|
|||
# 1003 |
|||
def Move(self, vx: float, vy: float, vyaw: float): |
|||
p = {} |
|||
p["x"] = vx |
|||
p["y"] = vy |
|||
p["yaw"] = vyaw |
|||
p["mode"] = 0 |
|||
parameter = json.dumps(p) |
|||
code = self._CallNoReply(OBSTACLES_AVOID_API_ID_MOVE, parameter) |
|||
return code |
|||
|
|||
def UseRemoteCommandFromApi(self, isRemoteCommandsFromApi: bool): |
|||
p = {} |
|||
p["is_remote_commands_from_api"] = isRemoteCommandsFromApi |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(OBSTACLES_AVOID_API_ID_USE_REMOTE_COMMAND_FROM_API, parameter) |
|||
return code |
|||
@ -0,0 +1,25 @@ |
|||
""" |
|||
" service name |
|||
""" |
|||
ROBOT_STATE_SERVICE_NAME = "robot_state" |
|||
|
|||
|
|||
""" |
|||
" service api version |
|||
""" |
|||
ROBOT_STATE_API_VERSION = "1.0.0.1" |
|||
|
|||
|
|||
""" |
|||
" api id |
|||
""" |
|||
ROBOT_STATE_API_ID_SERVICE_SWITCH = 1001 |
|||
ROBOT_STATE_API_ID_REPORT_FREQ = 1002 |
|||
ROBOT_STATE_API_ID_SERVICE_LIST = 1003 |
|||
|
|||
|
|||
""" |
|||
" error code |
|||
""" |
|||
ROBOT_STATE_ERR_SERVICE_SWITCH = 5201 |
|||
ROBOT_STATE_ERR_SERVICE_PROTECTED = 5202 |
|||
@ -0,0 +1,84 @@ |
|||
import json |
|||
|
|||
from ...rpc.client import Client |
|||
from ...rpc.internal import * |
|||
from .robot_state_api import * |
|||
|
|||
|
|||
""" |
|||
" class ServiceState |
|||
""" |
|||
class ServiceState: |
|||
def __init__(self, name: str = None, status: int = None, protect: bool = None): |
|||
self.name = name |
|||
self.status = status |
|||
self.protect = protect |
|||
|
|||
""" |
|||
" class RobotStateClient |
|||
""" |
|||
class RobotStateClient(Client): |
|||
def __init__(self): |
|||
super().__init__(ROBOT_STATE_SERVICE_NAME, False) |
|||
|
|||
def Init(self): |
|||
# set api version |
|||
self._SetApiVerson(ROBOT_STATE_API_VERSION) |
|||
# regist api |
|||
self._RegistApi(ROBOT_STATE_API_ID_SERVICE_SWITCH, 0) |
|||
self._RegistApi(ROBOT_STATE_API_ID_REPORT_FREQ, 0) |
|||
self._RegistApi(ROBOT_STATE_API_ID_SERVICE_LIST, 0) |
|||
|
|||
def ServiceList(self): |
|||
p = {} |
|||
parameter = json.dumps(p) |
|||
|
|||
code, data = self._Call(ROBOT_STATE_API_ID_SERVICE_LIST, parameter) |
|||
|
|||
if code != 0: |
|||
return code, None |
|||
|
|||
lst = [] |
|||
|
|||
d = json.loads(data) |
|||
for t in d: |
|||
s = ServiceState() |
|||
s.name = t["name"] |
|||
s.status = t["status"] |
|||
s.protect = t["protect"] |
|||
lst.append(s) |
|||
|
|||
return code, lst |
|||
|
|||
|
|||
def ServiceSwitch(self, name: str, switch: bool): |
|||
p = {} |
|||
p["name"] = name |
|||
p["switch"] = int(switch) |
|||
parameter = json.dumps(p) |
|||
|
|||
code, data = self._Call(ROBOT_STATE_API_ID_SERVICE_SWITCH, parameter) |
|||
|
|||
if code != 0: |
|||
return code |
|||
|
|||
d = json.loads(data) |
|||
|
|||
status = d["status"] |
|||
|
|||
if status == 5: |
|||
return ROBOT_STATE_ERR_SERVICE_PROTECTED |
|||
|
|||
if status != 0 and status != 1: |
|||
return ROBOT_STATE_ERR_SERVICE_SWITCH |
|||
|
|||
return code |
|||
|
|||
def SetReportFreq(self, interval: int, duration: int): |
|||
p = {} |
|||
p["interval"] = interval |
|||
p["duration"] = duration |
|||
parameter = json.dumps(p) |
|||
|
|||
code, data = self._Call(ROBOT_STATE_API_ID_REPORT_FREQ, p) |
|||
return code |
|||
@ -0,0 +1,74 @@ |
|||
""" |
|||
" service name |
|||
""" |
|||
SPORT_SERVICE_NAME = "sport" |
|||
|
|||
|
|||
""" |
|||
" service api version |
|||
""" |
|||
SPORT_API_VERSION = "1.0.0.1" |
|||
|
|||
|
|||
""" |
|||
" api id |
|||
""" |
|||
SPORT_API_ID_DAMP = 1001 |
|||
SPORT_API_ID_BALANCESTAND = 1002 |
|||
SPORT_API_ID_STOPMOVE = 1003 |
|||
SPORT_API_ID_STANDUP = 1004 |
|||
SPORT_API_ID_STANDDOWN = 1005 |
|||
SPORT_API_ID_RECOVERYSTAND = 1006 |
|||
SPORT_API_ID_EULER = 1007 |
|||
SPORT_API_ID_MOVE = 1008 |
|||
SPORT_API_ID_SIT = 1009 |
|||
SPORT_API_ID_RISESIT = 1010 |
|||
SPORT_API_ID_SWITCHGAIT = 1011 |
|||
SPORT_API_ID_TRIGGER = 1012 |
|||
SPORT_API_ID_BODYHEIGHT = 1013 |
|||
SPORT_API_ID_FOOTRAISEHEIGHT = 1014 |
|||
SPORT_API_ID_SPEEDLEVEL = 1015 |
|||
SPORT_API_ID_HELLO = 1016 |
|||
SPORT_API_ID_STRETCH = 1017 |
|||
SPORT_API_ID_TRAJECTORYFOLLOW = 1018 |
|||
SPORT_API_ID_CONTINUOUSGAIT = 1019 |
|||
SPORT_API_ID_CONTENT = 1020 |
|||
SPORT_API_ID_WALLOW = 1021 |
|||
SPORT_API_ID_DANCE1 = 1022 |
|||
SPORT_API_ID_DANCE2 = 1023 |
|||
SPORT_API_ID_GETBODYHEIGHT = 1024 |
|||
SPORT_API_ID_GETFOOTRAISEHEIGHT = 1025 |
|||
SPORT_API_ID_GETSPEEDLEVEL = 1026 |
|||
SPORT_API_ID_SWITCHJOYSTICK = 1027 |
|||
SPORT_API_ID_POSE = 1028 |
|||
SPORT_API_ID_SCRAPE = 1029 |
|||
SPORT_API_ID_FRONTFLIP = 1030 |
|||
SPORT_API_ID_FRONTJUMP = 1031 |
|||
SPORT_API_ID_FRONTPOUNCE = 1032 |
|||
SPORT_API_ID_WIGGLEHIPS = 1033 |
|||
SPORT_API_ID_GETSTATE = 1034 |
|||
SPORT_API_ID_ECONOMICGAIT = 1035 |
|||
SPORT_API_ID_HEART = 1036 |
|||
ROBOT_SPORT_API_ID_DANCE3 = 1037 |
|||
ROBOT_SPORT_API_ID_DANCE4 = 1038 |
|||
ROBOT_SPORT_API_ID_HOPSPINLEFT = 1039 |
|||
ROBOT_SPORT_API_ID_HOPSPINRIGHT = 1040 |
|||
|
|||
ROBOT_SPORT_API_ID_LEFTFLIP = 1042 |
|||
ROBOT_SPORT_API_ID_BACKFLIP = 1044 |
|||
ROBOT_SPORT_API_ID_FREEWALK = 1045 |
|||
ROBOT_SPORT_API_ID_FREEBOUND = 1046 |
|||
ROBOT_SPORT_API_ID_FREEJUMP = 1047 |
|||
ROBOT_SPORT_API_ID_FREEAVOID = 1048 |
|||
ROBOT_SPORT_API_ID_WALKSTAIR = 1049 |
|||
ROBOT_SPORT_API_ID_WALKUPRIGHT = 1050 |
|||
ROBOT_SPORT_API_ID_CROSSSTEP = 1051 |
|||
|
|||
""" |
|||
" error code |
|||
""" |
|||
# client side |
|||
SPORT_ERR_CLIENT_POINT_PATH = 4101 |
|||
# server side |
|||
SPORT_ERR_SERVER_OVERTIME = 4201 |
|||
SPORT_ERR_SERVER_NOT_INIT = 4202 |
|||
@ -0,0 +1,446 @@ |
|||
import json |
|||
|
|||
from ...rpc.client import Client |
|||
from .sport_api import * |
|||
|
|||
""" |
|||
" SPORT_PATH_POINT_SIZE |
|||
""" |
|||
SPORT_PATH_POINT_SIZE = 30 |
|||
|
|||
|
|||
""" |
|||
" class PathPoint |
|||
""" |
|||
class PathPoint: |
|||
def __init__(self, timeFromStart: float, x: float, y: float, yaw: float, vx: float, vy: float, vyaw: float): |
|||
self.timeFromStart = timeFromStart |
|||
self.x = x |
|||
self.y = y |
|||
self.yaw = yaw |
|||
self.vx = vx |
|||
self.vy = vy |
|||
self.vyaw = vyaw |
|||
|
|||
|
|||
""" |
|||
" class SportClient |
|||
""" |
|||
class SportClient(Client): |
|||
def __init__(self, enableLease: bool = False): |
|||
super().__init__(SPORT_SERVICE_NAME, enableLease) |
|||
|
|||
|
|||
def Init(self): |
|||
# set api version |
|||
self._SetApiVerson(SPORT_API_VERSION) |
|||
|
|||
# regist api |
|||
self._RegistApi(SPORT_API_ID_DAMP, 0) |
|||
self._RegistApi(SPORT_API_ID_BALANCESTAND, 0) |
|||
self._RegistApi(SPORT_API_ID_STOPMOVE, 0) |
|||
self._RegistApi(SPORT_API_ID_STANDUP, 0) |
|||
self._RegistApi(SPORT_API_ID_STANDDOWN, 0) |
|||
self._RegistApi(SPORT_API_ID_RECOVERYSTAND, 0) |
|||
self._RegistApi(SPORT_API_ID_EULER, 0) |
|||
self._RegistApi(SPORT_API_ID_MOVE, 0) |
|||
self._RegistApi(SPORT_API_ID_SIT, 0) |
|||
self._RegistApi(SPORT_API_ID_RISESIT, 0) |
|||
self._RegistApi(SPORT_API_ID_SWITCHGAIT, 0) |
|||
self._RegistApi(SPORT_API_ID_TRIGGER, 0) |
|||
self._RegistApi(SPORT_API_ID_BODYHEIGHT, 0) |
|||
self._RegistApi(SPORT_API_ID_FOOTRAISEHEIGHT, 0) |
|||
self._RegistApi(SPORT_API_ID_SPEEDLEVEL, 0) |
|||
self._RegistApi(SPORT_API_ID_HELLO, 0) |
|||
self._RegistApi(SPORT_API_ID_STRETCH, 0) |
|||
self._RegistApi(SPORT_API_ID_TRAJECTORYFOLLOW, 0) |
|||
self._RegistApi(SPORT_API_ID_CONTINUOUSGAIT, 0) |
|||
# self._RegistApi(SPORT_API_ID_CONTENT, 0) |
|||
self._RegistApi(SPORT_API_ID_WALLOW, 0) |
|||
self._RegistApi(SPORT_API_ID_DANCE1, 0) |
|||
self._RegistApi(SPORT_API_ID_DANCE2, 0) |
|||
# self._RegistApi(SPORT_API_ID_GETBODYHEIGHT, 0) |
|||
# self._RegistApi(SPORT_API_ID_GETFOOTRAISEHEIGHT, 0) |
|||
# self._RegistApi(SPORT_API_ID_GETSPEEDLEVEL, 0) |
|||
self._RegistApi(SPORT_API_ID_SWITCHJOYSTICK, 0) |
|||
self._RegistApi(SPORT_API_ID_POSE, 0) |
|||
self._RegistApi(SPORT_API_ID_SCRAPE, 0) |
|||
self._RegistApi(SPORT_API_ID_FRONTFLIP, 0) |
|||
self._RegistApi(SPORT_API_ID_FRONTJUMP, 0) |
|||
self._RegistApi(SPORT_API_ID_FRONTPOUNCE, 0) |
|||
self._RegistApi(SPORT_API_ID_WIGGLEHIPS, 0) |
|||
self._RegistApi(SPORT_API_ID_GETSTATE, 0) |
|||
self._RegistApi(SPORT_API_ID_ECONOMICGAIT, 0) |
|||
self._RegistApi(SPORT_API_ID_HEART, 0) |
|||
|
|||
self._RegistApi(ROBOT_SPORT_API_ID_LEFTFLIP, 0) |
|||
self._RegistApi(ROBOT_SPORT_API_ID_BACKFLIP, 0) |
|||
self._RegistApi(ROBOT_SPORT_API_ID_FREEWALK, 0) |
|||
self._RegistApi(ROBOT_SPORT_API_ID_FREEBOUND, 0) |
|||
self._RegistApi(ROBOT_SPORT_API_ID_FREEJUMP, 0) |
|||
self._RegistApi(ROBOT_SPORT_API_ID_FREEAVOID, 0) |
|||
self._RegistApi(ROBOT_SPORT_API_ID_WALKSTAIR, 0) |
|||
self._RegistApi(ROBOT_SPORT_API_ID_WALKUPRIGHT, 0) |
|||
self._RegistApi(ROBOT_SPORT_API_ID_CROSSSTEP, 0) |
|||
|
|||
# 1001 |
|||
def Damp(self): |
|||
p = {} |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(SPORT_API_ID_DAMP, parameter) |
|||
return code |
|||
|
|||
# 1002 |
|||
def BalanceStand(self): |
|||
p = {} |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(SPORT_API_ID_BALANCESTAND, parameter) |
|||
return code |
|||
|
|||
# 1003 |
|||
def StopMove(self): |
|||
p = {} |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(SPORT_API_ID_STOPMOVE, parameter) |
|||
return code |
|||
|
|||
# 1004 |
|||
def StandUp(self): |
|||
p = {} |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(SPORT_API_ID_STANDUP, parameter) |
|||
return code |
|||
|
|||
# 1005 |
|||
def StandDown(self): |
|||
p = {} |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(SPORT_API_ID_STANDDOWN, parameter) |
|||
return code |
|||
|
|||
# 1006 |
|||
def RecoveryStand(self): |
|||
p = {} |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(SPORT_API_ID_RECOVERYSTAND, parameter) |
|||
return code |
|||
|
|||
# 1007 |
|||
def Euler(self, roll: float, pitch: float, yaw: float): |
|||
p = {} |
|||
p["x"] = roll |
|||
p["y"] = pitch |
|||
p["z"] = yaw |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(SPORT_API_ID_EULER, parameter) |
|||
return code |
|||
|
|||
# 1008 |
|||
def Move(self, vx: float, vy: float, vyaw: float): |
|||
p = {} |
|||
p["x"] = vx |
|||
p["y"] = vy |
|||
p["z"] = vyaw |
|||
parameter = json.dumps(p) |
|||
code = self._CallNoReply(SPORT_API_ID_MOVE, parameter) |
|||
return code |
|||
|
|||
# 1009 |
|||
def Sit(self): |
|||
p = {} |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(SPORT_API_ID_SIT, parameter) |
|||
return code |
|||
|
|||
#1010 |
|||
def RiseSit(self): |
|||
p = {} |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(SPORT_API_ID_RISESIT, parameter) |
|||
return code |
|||
|
|||
# 1011 |
|||
def SwitchGait(self, t: int): |
|||
p = {} |
|||
p["data"] = t |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(SPORT_API_ID_SWITCHGAIT, parameter) |
|||
return code |
|||
|
|||
# 1012 |
|||
def Trigger(self): |
|||
p = {} |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(SPORT_API_ID_TRIGGER, parameter) |
|||
return code |
|||
|
|||
# 1013 |
|||
def BodyHeight(self, height: float): |
|||
p = {} |
|||
p["data"] = height |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(SPORT_API_ID_BODYHEIGHT, parameter) |
|||
return code |
|||
|
|||
# 1014 |
|||
def FootRaiseHeight(self, height: float): |
|||
p = {} |
|||
p["data"] = height |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(SPORT_API_ID_FOOTRAISEHEIGHT, parameter) |
|||
return code |
|||
|
|||
# 1015 |
|||
def SpeedLevel(self, level: int): |
|||
p = {} |
|||
p["data"] = level |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(SPORT_API_ID_SPEEDLEVEL, parameter) |
|||
return code |
|||
|
|||
# 1016 |
|||
def Hello(self): |
|||
p = {} |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(SPORT_API_ID_HELLO, parameter) |
|||
return code |
|||
|
|||
# 1017 |
|||
def Stretch(self): |
|||
p = {} |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(SPORT_API_ID_STRETCH, parameter) |
|||
return code |
|||
|
|||
# 1018 |
|||
def TrajectoryFollow(self, path: list): |
|||
l = len(path) |
|||
if l != SPORT_PATH_POINT_SIZE: |
|||
return SPORT_ERR_CLIENT_POINT_PATH |
|||
|
|||
path_p = [] |
|||
for i in range(l): |
|||
point = path[i] |
|||
p = {} |
|||
p["t_from_start"] = point.timeFromStart |
|||
p["x"] = point.x |
|||
p["y"] = point.y |
|||
p["yaw"] = point.yaw |
|||
p["vx"] = point.vx |
|||
p["vy"] = point.vy |
|||
p["vyaw"] = point.vyaw |
|||
path_p.append(p) |
|||
|
|||
parameter = json.dumps(path_p) |
|||
code = self._CallNoReply(SPORT_API_ID_TRAJECTORYFOLLOW, parameter) |
|||
return code |
|||
|
|||
# 1019 |
|||
def ContinuousGait(self, flag: int): |
|||
p = {} |
|||
p["data"] = flag |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(SPORT_API_ID_CONTINUOUSGAIT, parameter) |
|||
return code |
|||
|
|||
# # 1020 |
|||
# def Content(self): |
|||
# p = {} |
|||
# parameter = json.dumps(p) |
|||
# code, data = self._Call(SPORT_API_ID_CONTENT, parameter) |
|||
# return code |
|||
|
|||
# 1021 |
|||
def Wallow(self): |
|||
p = {} |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(SPORT_API_ID_WALLOW, parameter) |
|||
return code |
|||
|
|||
# 1022 |
|||
def Dance1(self): |
|||
p = {} |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(SPORT_API_ID_DANCE1, parameter) |
|||
return code |
|||
|
|||
# 1023 |
|||
def Dance2(self): |
|||
p = {} |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(SPORT_API_ID_DANCE2, parameter) |
|||
return code |
|||
|
|||
# 1025 |
|||
def GetFootRaiseHeight(self): |
|||
p = {} |
|||
parameter = json.dumps(p) |
|||
|
|||
code, data = self._Call(SPORT_API_ID_GETFOOTRAISEHEIGHT, parameter) |
|||
|
|||
if code == 0: |
|||
d = json.loads(data) |
|||
return code, d["data"] |
|||
else: |
|||
return code, None |
|||
|
|||
|
|||
# 1026 |
|||
def GetSpeedLevel(self): |
|||
p = {} |
|||
parameter = json.dumps(p) |
|||
|
|||
code, data = self._Call(SPORT_API_ID_GETSPEEDLEVEL, parameter) |
|||
|
|||
if code == 0: |
|||
d = json.loads(data) |
|||
return code, d["data"] |
|||
else: |
|||
return code, None |
|||
|
|||
# 1027 |
|||
def SwitchJoystick(self, on: bool): |
|||
p = {} |
|||
p["data"] = on |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(SPORT_API_ID_SWITCHJOYSTICK, parameter) |
|||
return code |
|||
|
|||
# 1028 |
|||
def Pose(self, flag: bool): |
|||
p = {} |
|||
p["data"] = flag |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(SPORT_API_ID_POSE, parameter) |
|||
return code |
|||
|
|||
# 1029 |
|||
def Scrape(self): |
|||
p = {} |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(SPORT_API_ID_SCRAPE, parameter) |
|||
return code |
|||
|
|||
# 1030 |
|||
def FrontFlip(self): |
|||
p = {} |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(SPORT_API_ID_FRONTFLIP, parameter) |
|||
return code |
|||
|
|||
# 1031 |
|||
def FrontJump(self): |
|||
p = {} |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(SPORT_API_ID_FRONTJUMP, parameter) |
|||
return code |
|||
|
|||
# 1032 |
|||
def FrontPounce(self): |
|||
p = {} |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(SPORT_API_ID_FRONTPOUNCE, parameter) |
|||
return code |
|||
|
|||
# 1033 |
|||
def WiggleHips(self): |
|||
p = {} |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(SPORT_API_ID_WIGGLEHIPS, parameter) |
|||
return code |
|||
|
|||
# 1034 |
|||
def GetState(self, keys: list): |
|||
parameter = json.dumps(keys) |
|||
code, data = self._Call(SPORT_API_ID_GETSTATE, parameter) |
|||
if code == 0: |
|||
return code, json.loads(data) |
|||
else: |
|||
return code, None |
|||
|
|||
# 1035 |
|||
def EconomicGait(self, flag: bool): |
|||
p = {} |
|||
p["data"] = flag |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(SPORT_API_ID_ECONOMICGAIT, parameter) |
|||
return code |
|||
|
|||
# 1036 |
|||
def Heart(self): |
|||
p = {} |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(SPORT_API_ID_HEART, parameter) |
|||
return code |
|||
|
|||
# 1042 |
|||
def LeftFlip(self): |
|||
p = {} |
|||
p["data"] = True |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(ROBOT_SPORT_API_ID_LEFTFLIP, parameter) |
|||
return code |
|||
|
|||
# 1044 |
|||
def BackFlip(self): |
|||
p = {} |
|||
p["data"] = True |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(ROBOT_SPORT_API_ID_BACKFLIP, parameter) |
|||
return code |
|||
|
|||
# 1045 |
|||
def FreeWalk(self, flag: bool): |
|||
p = {} |
|||
p["data"] = True |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(ROBOT_SPORT_API_ID_FREEWALK, parameter) |
|||
return code |
|||
|
|||
# 1046 |
|||
def FreeBound(self, flag: bool): |
|||
p = {} |
|||
p["data"] = flag |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(ROBOT_SPORT_API_ID_FREEBOUND, parameter) |
|||
return code |
|||
|
|||
# 1047 |
|||
def FreeJump(self, flag: bool): |
|||
p = {} |
|||
p["data"] = flag |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(ROBOT_SPORT_API_ID_FREEJUMP, parameter) |
|||
return code |
|||
|
|||
# 1048 |
|||
def FreeAvoid(self, flag: bool): |
|||
p = {} |
|||
p["data"] = flag |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(ROBOT_SPORT_API_ID_FREEAVOID, parameter) |
|||
return code |
|||
|
|||
# 1049 |
|||
def WalkStair(self, flag: bool): |
|||
p = {} |
|||
p["data"] = flag |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(ROBOT_SPORT_API_ID_WALKSTAIR, parameter) |
|||
return code |
|||
|
|||
# 1050 |
|||
def WalkUpright(self, flag: bool): |
|||
p = {} |
|||
p["data"] = flag |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(ROBOT_SPORT_API_ID_WALKUPRIGHT, parameter) |
|||
return code |
|||
|
|||
# 1051 |
|||
def CrossStep(self, flag: bool): |
|||
p = {} |
|||
p["data"] = flag |
|||
parameter = json.dumps(p) |
|||
code, data = self._Call(ROBOT_SPORT_API_ID_CROSSSTEP, parameter) |
|||
return code |
|||
@ -0,0 +1,16 @@ |
|||
""" |
|||
" service name |
|||
""" |
|||
VIDEO_SERVICE_NAME = "videohub" |
|||
|
|||
|
|||
""" |
|||
" service api version |
|||
""" |
|||
VIDEO_API_VERSION = "1.0.0.1" |
|||
|
|||
|
|||
""" |
|||
" api id |
|||
""" |
|||
VIDEO_API_ID_GETIMAGESAMPLE = 1001 |
|||
@ -0,0 +1,23 @@ |
|||
import json |
|||
|
|||
from ...rpc.client import Client |
|||
from .video_api import * |
|||
|
|||
|
|||
""" |
|||
" class VideoClient |
|||
""" |
|||
class VideoClient(Client): |
|||
def __init__(self): |
|||
super().__init__(VIDEO_SERVICE_NAME, False) |
|||
|
|||
|
|||
def Init(self): |
|||
# set api version |
|||
self._SetApiVerson(VIDEO_API_VERSION) |
|||
# regist api |
|||
self._RegistApi(VIDEO_API_ID_GETIMAGESAMPLE, 0) |
|||
|
|||
# 1001 |
|||
def GetImageSample(self): |
|||
return self._CallBinary(VIDEO_API_ID_GETIMAGESAMPLE, []) |
|||
@ -0,0 +1,21 @@ |
|||
""" |
|||
" service name |
|||
""" |
|||
VUI_SERVICE_NAME = "vui" |
|||
|
|||
|
|||
""" |
|||
" service api version |
|||
""" |
|||
VUI_API_VERSION = "1.0.0.1" |
|||
|
|||
|
|||
""" |
|||
" api id |
|||
""" |
|||
VUI_API_ID_SETSWITCH = 1001 |
|||
VUI_API_ID_GETSWITCH = 1002 |
|||
VUI_API_ID_SETVOLUME = 1003 |
|||
VUI_API_ID_GETVOLUME = 1004 |
|||
VUI_API_ID_SETBRIGHTNESS = 1005 |
|||
VUI_API_ID_GETBRIGHTNESS = 1006 |
|||
Some files were not shown because too many files changed in this diff
Write
Preview
Loading…
Cancel
Save
Reference in new issue