Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion example/python/stand_go2.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import time
import sys
import platform
import numpy as np

from unitree_sdk2py.core.channel import ChannelPublisher, ChannelSubscriber
Expand Down Expand Up @@ -29,7 +30,8 @@
if __name__ == '__main__':

if len(sys.argv) <2:
ChannelFactoryInitialize(1, "lo")
interface = "lo0" if platform.system() == "Darwin" else "lo"
ChannelFactoryInitialize(1, interface)
else:
ChannelFactoryInitialize(0, sys.argv[1])

Expand Down
76 changes: 76 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,82 @@ Note:


# Installation

## macOS (Python Simulator only)

The C++ simulator cannot run natively on macOS because `unitree_sdk2` ships a pre-compiled Linux static library (`libunitree_sdk2.a`). The Python simulator uses all cross-platform dependencies and works natively on macOS.

### 1. Prerequisites
```bash
brew install python@3.11 cmake
```

### 2. Python dependencies
```bash
pip3 install mujoco pygame numpy cyclonedds
```

If `pip install cyclonedds` fails, build from source:
```bash
git clone https://github.com/eclipse-cyclonedds/cyclonedds.git
cd cyclonedds && mkdir build && cd build
cmake .. -DCMAKE_INSTALL_PREFIX=$HOME/cyclonedds-install
make -j4 && make install
export CYCLONEDDS_HOME=$HOME/cyclonedds-install
pip3 install cyclonedds
```

If `pip install pygame` fails (SDL2 missing):
```bash
brew install sdl2
pip3 install pygame
```

### 3. Install unitree_sdk2_python
```bash
git clone https://github.com/unitreerobotics/unitree_sdk2_python.git
cd unitree_sdk2_python
pip3 install -e .
```

### 4. Configure for G1
In `simulate_python/config.py`, set:
```python
ROBOT = "g1"
USE_JOYSTICK = 0 # Set to 1 if you have a gamepad
```

The loopback interface is auto-detected (`lo0` on macOS, `lo` on Linux).

### 5. Run

On macOS, MuJoCo requires `mjpython` (installed with the `mujoco` package) instead of `python3` for GUI rendering. A convenience script is provided:
```bash
cd simulate_python
./run_sim.sh
```
Or manually:
```bash
cd simulate_python
DYLD_LIBRARY_PATH=$(.venv/bin/python3 -c 'import sysconfig; print(sysconfig.get_config_var("LIBDIR"))') \
../.venv/bin/mjpython unitree_mujoco.py
```
You should see the MuJoCo viewer open with the G1 robot loaded.

In a new terminal, run the G1 test (uses `unitree_hg` IDL):
```bash
cd simulate_python
../.venv/bin/python3 test/test_g1.py
```

The program will output the robot's IMU and position data, and each motor will output 0.5Nm torque.

**Note:** The existing `test/test_unitree_sdk2.py` uses the `unitree_go` IDL (for Go2/B2/H1). For G1, use `test/test_g1.py` which uses the `unitree_hg` IDL.

---

## Linux

## C++ Simulator (simulate)
### 1. Dependencies

Expand Down
8 changes: 5 additions & 3 deletions simulate_python/config.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
ROBOT = "go2" # Robot name, "go2", "b2", "b2w", "h1", "go2w", "g1"
import platform

ROBOT = "g1" # Robot name, "go2", "b2", "b2w", "h1", "go2w", "g1"
ROBOT_SCENE = "../unitree_robots/" + ROBOT + "/scene.xml" # Robot scene
DOMAIN_ID = 1 # Domain id
INTERFACE = "lo" # Interface
INTERFACE = "lo0" if platform.system() == "Darwin" else "lo" # Interface (lo0 on macOS, lo on Linux)

USE_JOYSTICK = 1 # Simulate Unitree WirelessController using a gamepad
USE_JOYSTICK = 0 # Simulate Unitree WirelessController using a gamepad
JOYSTICK_TYPE = "xbox" # support "xbox" and "switch" gamepad layout
JOYSTICK_DEVICE = 0 # Joystick number

Expand Down
21 changes: 21 additions & 0 deletions simulate_python/run_sim.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/usr/bin/env bash
# Launch the MuJoCo simulator on macOS using mjpython.
# mjpython is required because macOS needs GUI operations on the main thread.
#
# Usage: ./run_sim.sh

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
VENV_DIR="$SCRIPT_DIR/../.venv"

if [ ! -f "$VENV_DIR/bin/mjpython" ]; then
echo "Error: mjpython not found. Set up the venv first:"
echo " cd $(dirname "$SCRIPT_DIR") && uv venv --python 3.11 .venv && uv pip install mujoco pygame numpy"
exit 1
fi

# mjpython needs libpython on DYLD_LIBRARY_PATH when using uv-managed Python
PYTHON_LIBDIR="$("$VENV_DIR/bin/python3" -c 'import sysconfig; print(sysconfig.get_config_var("LIBDIR"))')"
export DYLD_LIBRARY_PATH="$PYTHON_LIBDIR${DYLD_LIBRARY_PATH:+:$DYLD_LIBRARY_PATH}"

cd "$SCRIPT_DIR"
exec "$VENV_DIR/bin/mjpython" unitree_mujoco.py "$@"
62 changes: 62 additions & 0 deletions simulate_python/test/test_g1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import time
import platform
from unitree_sdk2py.core.channel import ChannelFactoryInitialize
from unitree_sdk2py.core.channel import ChannelPublisher, ChannelSubscriber
from unitree_sdk2py.idl.default import unitree_go_msg_dds__SportModeState_
from unitree_sdk2py.idl.default import unitree_hg_msg_dds__LowState_
from unitree_sdk2py.idl.default import unitree_hg_msg_dds__LowCmd_
from unitree_sdk2py.idl.unitree_go.msg.dds_ import SportModeState_
from unitree_sdk2py.idl.unitree_hg.msg.dds_ import LowState_
from unitree_sdk2py.idl.unitree_hg.msg.dds_ import LowCmd_
from unitree_sdk2py.utils.crc import CRC

NUM_MOTORS = 29


def HighStateHandler(msg: SportModeState_):
print("Position: ", msg.position)


def LowStateHandler(msg: LowState_):
print("IMU state: ", msg.imu_state)


if __name__ == "__main__":
interface = "lo0" if platform.system() == "Darwin" else "lo"
ChannelFactoryInitialize(1, interface)

high_state_suber = ChannelSubscriber("rt/sportmodestate", SportModeState_)
low_state_suber = ChannelSubscriber("rt/lowstate", LowState_)

high_state_suber.Init(HighStateHandler, 10)
low_state_suber.Init(LowStateHandler, 10)

low_cmd_puber = ChannelPublisher("rt/lowcmd", LowCmd_)
low_cmd_puber.Init()
crc = CRC()

cmd = unitree_hg_msg_dds__LowCmd_()
cmd.mode_pr = 0
cmd.mode_machine = 0

for i in range(NUM_MOTORS):
cmd.motor_cmd[i].mode = 0x01 # (PMSM) mode
cmd.motor_cmd[i].q = 0.0
cmd.motor_cmd[i].kp = 0.0
cmd.motor_cmd[i].dq = 0.0
cmd.motor_cmd[i].kd = 0.0
cmd.motor_cmd[i].tau = 0.0

while True:
for i in range(NUM_MOTORS):
cmd.motor_cmd[i].q = 0.0
cmd.motor_cmd[i].kp = 0.0
cmd.motor_cmd[i].dq = 0.0
cmd.motor_cmd[i].kd = 0.0
cmd.motor_cmd[i].tau = 0.5

cmd.crc = crc.Crc(cmd)

# Publish message
low_cmd_puber.Write(cmd)
time.sleep(0.002)
4 changes: 3 additions & 1 deletion simulate_python/test/test_unitree_sdk2.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import time
import platform
from unitree_sdk2py.core.channel import ChannelFactoryInitialize
from unitree_sdk2py.core.channel import ChannelPublisher, ChannelSubscriber
from unitree_sdk2py.idl.default import unitree_go_msg_dds__SportModeState_
Expand All @@ -21,7 +22,8 @@ def LowStateHandler(msg: LowState_):


if __name__ == "__main__":
ChannelFactoryInitialize(1, "lo")
interface = "lo0" if platform.system() == "Darwin" else "lo"
ChannelFactoryInitialize(1, interface)
hight_state_suber = ChannelSubscriber("rt/sportmodestate", SportModeState_)
low_state_suber = ChannelSubscriber("rt/lowstate", LowState_)

Expand Down
8 changes: 8 additions & 0 deletions simulate_python/unitree_mujoco.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import time
import sys
import os
import platform
import mujoco
import mujoco.viewer
from threading import Thread
Expand All @@ -9,6 +12,11 @@

import config

if platform.system() == "Darwin" and "MJPYTHON_BIN" not in os.environ:
print("Error: On macOS, MuJoCo requires 'mjpython' for GUI rendering.")
print("Run with: mjpython unitree_mujoco.py")
sys.exit(1)


locker = threading.Lock()

Expand Down
4 changes: 2 additions & 2 deletions simulate_python/unitree_sdk2py_bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -300,8 +300,8 @@ def SetupJoystick(self, device_id=0, js_type="xbox"):
self.joystick = pygame.joystick.Joystick(device_id)
self.joystick.init()
else:
print("No gamepad detected.")
sys.exit()
print("Warning: No gamepad detected. Continuing without joystick support.")
return

if js_type == "xbox":
self.axis_id = {
Expand Down