Getting started¶
This page gets a first episode running with plain gymnasium. lerobot is not
needed for any of it — see Using it with lerobot if
you want the plugin path.
Requirements¶
- Python 3.10–3.13
mujoco>=3.0.0,<4.0.0(verified against 3.10.0),gymnasium,numpy— all installed automatically
Apple Silicon runs natively; there are a few platform quirks worth knowing before you hit them, collected in Limitations and troubleshooting.
Install¶
For development against a checkout of the repository:
git clone https://github.com/isaka1022/so101-sim2real.git
cd so101-sim2real
pip install -e packages/lerobot_env_so101
Your first episode¶
Importing the package registers the environment ID with gymnasium, so the
import is required even though the name is never referenced directly:
import gymnasium as gym
import numpy as np
import lerobot_env_so101 # registers the env
env = gym.make("lerobot_env_so101/SO101PickCube-v0", render_mode="rgb_array")
obs, info = env.reset(seed=0)
for _ in range(100):
action = env.action_space.sample() * 0.1 # keep deltas small — see below
obs, reward, terminated, truncated, info = env.step(action)
if terminated or truncated:
obs, info = env.reset()
env.close()
Why * 0.1 and not a raw sample
With the default action_scale=1.0, an action component of 1.0 is added
to the end-effector target as 1.0 metres, while the whole reachable
workspace spans at most 0.6 m. Sampling the raw action space therefore
behaves closer to bang-bang control than to a proportional delta. For
anything beyond a smoke test, set action_scale explicitly — see
Action deltas default to unscaled metres.
Configuring the environment¶
gym.make accepts the following keyword arguments:
| Argument | Default | Effect |
|---|---|---|
action_scale |
1.0 |
Metres per unit of position action. Affects dx, dy, dz only; grasp is absolute and unaffected. |
image_obs |
False |
Adds a pixels.front camera view to the observation dict. |
reward_type |
"sparse" |
"sparse" or "dense". |
random_block_position |
False |
Randomizes the block's starting position on reset. |
render_mode |
"rgb_array" |
"rgb_array" or "human". |
render_spec |
GymRenderingSpec() |
Frame height, width, and camera used for rendering. |
seed |
0 |
Seeds the environment's own RNG. |
control_dt |
0.1 |
Seconds per control step (10 Hz). |
physics_dt |
0.002 |
MuJoCo integration step. |
A practical configuration for RL, with a scaled action and dense reward:
The full signature is in the API reference.
Observations and actions at a glance¶
The action space is Box([-1, -1, -1, 0], 1.0, (4,), float32). Note the
asymmetric lower bound: the three position deltas run [-1, 1], while grasp
runs [0, 1] because it is an absolute target rather than a delta — 0 fully
closed, 1 fully open. The reasoning behind this shape is in
Action space.
The observation is a dict whose contents depend on image_obs:
agent_pos is [qpos(5), qvel(5), gripper_pose(1), ee_pos(3)], where
gripper_pose is normalized to [-1, 1] (a different convention from the
grasp action, which is [0, 1]). environment_state is the block's
position.
image_obs=True removes environment_state
Turning on pixel observations does not simply add a camera view — it also
drops environment_state from the dict, on the assumption that a
pixel-based policy should infer the block position from the image rather
than read it directly. If you want both, you will need to override
_compute_observation().
Rendering¶
render_mode="rgb_array" returns frames you can write to a video yourself.
render_mode="human" opens a MuJoCo viewer — on macOS this interacts with the
mjpython launcher in a way that is easy to get wrong; see
macOS.
Next steps¶
- Using it with lerobot — plugin discovery and the CLI
- Action space — what each dimension does and why there are four
- Limitations and troubleshooting — read before filing a bug