Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
30b996d
fix(docker): link script
juelg May 5, 2026
fce0ff6
feat: zed cli rgb snapshot
juelg May 5, 2026
2f7fe19
fix(teleop): depth in cameras
juelg May 5, 2026
9d44dc5
chore(quest): simplify right/left swapping logic
juelg May 5, 2026
4af54bb
impr(robotiq): physical reset only in constructor
juelg May 5, 2026
2e78021
feat: robotiq in teleop script
juelg May 12, 2026
7b18891
impr(gripper): improved change detection
juelg May 12, 2026
785acb1
feat(zed): add option to record both eyes
juelg May 12, 2026
07a8d3d
feat(inference): add initial inference loop script
juelg May 8, 2026
f36b8e1
style: format inference franka script
juelg May 14, 2026
a31bcc3
Merge remote-tracking branch 'origin/juelg/inference' into juelg/hard…
juelg May 14, 2026
844d78b
fix(franka): inference script after testing
juelg May 14, 2026
006ccaf
Merge remote-tracking branch 'origin/master' into juelg/hardware-fixes
juelg May 18, 2026
ac4836f
feat: sim inference
juelg May 18, 2026
fc401e6
fix: patch always open gripper observation
juelg May 18, 2026
103f572
Merge remote-tracking branch 'origin/master' into juelg/hardware-fixes
juelg May 19, 2026
f9c0c9a
feat: reconnect and keyboard control
juelg May 19, 2026
e945ccb
feat: optionally record
juelg May 19, 2026
2081f00
feat: added mp4 video converter from recording
juelg May 19, 2026
1c7a9cb
feat(mp4): added joint display
juelg May 19, 2026
d2e35b7
fix: printing and sim sleep
juelg May 19, 2026
82f1ace
feat: action chunking optionally in inference script
juelg May 19, 2026
ce16d0a
feat: support for relative actions
juelg May 19, 2026
80f3657
feat: inference add success button
juelg May 19, 2026
74a5b76
fix(storage wrapper): flush keeps last to avoid success problems
juelg May 19, 2026
6d795ae
fix(mp4): shows joint actions if available
juelg May 19, 2026
8bc34f2
fix(fr3): torque discontinuity while reset
juelg May 19, 2026
8eaa1a7
fix(hw camera): stop camera before stop polling thread
juelg May 19, 2026
26f549f
feat: using input instead of pynput
juelg May 19, 2026
fe4b353
feat: add limited absolute action wrapper
juelg May 20, 2026
0aa68c9
feat: add limited absolute actions to inference script
juelg May 20, 2026
bb40618
stash: latest version
juelg May 21, 2026
71e0a72
stash: add notes
juelg May 21, 2026
aeb7569
fix: left/right wrist camera bug
juelg May 21, 2026
2cb2169
fix: docker dependency
juelg May 22, 2026
76a4dc0
fix(inference): image resize method to match conversion
juelg May 22, 2026
8e289a2
Merge branch 'master' into juelg/hardware-fixes
juelg Jun 27, 2026
a6748a5
chore: cleanup unused codes and comments
juelg Jun 27, 2026
11abe81
docs: added inference
juelg Jun 27, 2026
600b257
style: fix format
juelg Jun 27, 2026
fe159ba
fix: resolve ruff lint errors
juelg Jun 27, 2026
45c172d
fix: resolve mypy type errors
juelg Jun 27, 2026
6fa19a5
docs: added python headers to readme
juelg Jun 27, 2026
5f14c3a
fix: resolve pytest failures and StorageWrapper hang
juelg Jun 27, 2026
432b7a1
style: fix gym warning and path obj in cli
juelg Jun 28, 2026
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
9 changes: 9 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -1 +1,10 @@
**/build/
real*
test*
sim*
transfer_cube
single_pick*
ball_maze
candy
*.parquet
*venv*
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ PYSRC = python
CPPSRC = src
COMPILE_MODE = Release
LINT_EXCLUDE_RUFF = --exclude examples/teleop/SimPublisher
LINT_EXCLUDE_MYPY = 'build|examples/teleop/SimPublisher'
LINT_EXCLUDE_MYPY = 'build|examples/teleop/SimPublisher|examples/inference/franka.py'

# CPP
cppcheckformat:
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Traditional robotics middleware (like ROS/ROS2) and complex motion planning pipe
* **Zero ROS Overhead:** No complex message-passing, middleware, or network configuration required. Run natively in Python with a lightweight C++ backend.
* **Frictionless Sim-to-Real:** Train your Reinforcement Learning or VLA policies in our MuJoCo Gymnasium wrapper, and deploy the *exact same code* directly to physical hardware.
* **Synchronous Execution:** Optimized specifically for the highly parallelized, synchronous data collection required by modern ML workflows.
* **Ready-to-Use Apps:** Ships with pre-built applications for data collection via teleoperation and remote model inference via [vlagents](https://github.com/RobotControlStack/vlagents).
* **Ready-to-Use Apps:** Ships with pre-built applications for data collection via teleoperation and remote model inference via [vlagents](https://github.com/RobotControlStack/vlagents). See [examples/teleop/README.md](examples/teleop/README.md) and [examples/inference/README.md](examples/inference/README.md).

## 🧩 Wrapper-Based Architecture

Expand Down Expand Up @@ -132,7 +132,7 @@ pip install rcs-core

### From Source

Make sure that common build tools (i.e., `build-essential`) and a C++ compiler like `gcc` or `clang` are installed on your system/conda/docker.
Make sure that common build tools (i.e., `build-essential`), python headers and a C++ compiler like `gcc` or `clang` are installed on your system/conda/docker.

*RCS works best in Python 3.11, and all extensions have been tested to work in 3.11.*

Expand Down Expand Up @@ -196,6 +196,7 @@ For full documentation, including advanced installation, modular usage, and API
Useful quick-reference pages:
- **[RCS Conventions](https://robotcontrolstack.org/user_guide/conventions)** for quaternion order, frames, Euler angles, and gripper semantics
- **[Sim Scene Configuration](https://robotcontrolstack.org/user_guide/scene_configuration)** for `SimEnvCreatorConfig`, scene frames, and example setup patterns
- **[Apps](https://robotcontrolstack.org/apps/index)** for the teleoperation and inference example entry points
- **[libfranka Version Info](https://robotcontrolstack.org/extensions/libfranka_versions)** for the currently pinned `rcs_fr3` and `rcs_panda` `libfranka` versions and local-install guidance

## 🤝 Contribution
Expand Down
7 changes: 6 additions & 1 deletion docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
libglfw3-dev \
libpoco-dev \
ninja-build \
liburdfdom-dev \
&& rm -rf /var/lib/apt/lists/*

RUN curl -LsSf https://astral.sh/uv/install.sh | sh
Expand Down Expand Up @@ -53,7 +54,11 @@ RUN chmod +x /usr/local/bin/link-editable-source \
&& uv pip install --no-build-isolation /opt/rcs-src/extensions/rcs_fr3 \
&& uv pip install /opt/rcs-src/extensions/rcs_realsense \
&& uv pip install /opt/rcs-src/extensions/rcs_robotiq2f85 \
&& uv pip install /opt/rcs-src/extensions/rcs_zed
&& uv pip install /opt/rcs-src/extensions/rcs_zed \
&& uv pip install /opt/rcs-src/examples/teleop/SimPublisher \
&& uv pip install /opt/rcs-src/2f85-python-driver \
&& uv pip install /opt/rcs-src/vlagents \
&& uv pip install /opt/rcs-src/rcs_duobench

WORKDIR /workspace/robot-control-stack

Expand Down
3 changes: 2 additions & 1 deletion docker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Notes:
- `~/zed_models` is mounted into `/usr/local/zed/resources` to match the direct `docker run` setup.
- `/dev/dri` is masked inside the container so host Mesa/AMD render nodes do not override the NVIDIA runtime devices.
- NVIDIA PRIME/GLX environment variables are exported to bias OpenGL/EGL selection toward the NVIDIA stack when using X11 forwarding.
- Python source changes are picked up from the mounted repo, including `extensions/rcs_zed`.
- The simulator still bootstraps EGL for offscreen MuJoCo camera rendering; set `RCS_MUJOCO_DISABLE_EGL=1` only if you intentionally want to disable that path.
- Python source changes are picked up from the mounted repo.
- If you change C++ code in `rcs` or `rcs_fr3`, rebuild the image.
- For non-GPU hosts, comment out the GPU-related lines in `docker/compose/dev.yml`.
26 changes: 11 additions & 15 deletions docker/link-editable-source.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,21 @@ fi

SITE_PACKAGES="$(python -c 'import sysconfig; print(sysconfig.get_paths()["purelib"])')"

link_mixed_package() {
link_compiled_package() {
src_dir="$1"
dst_dir="$2"
keep_dir_name="${3:-}"

if [ ! -d "$src_dir" ] || [ ! -d "$dst_dir" ]; then
return
fi

# Replace only the Python sources from the mounted repo and keep compiled
# artifacts that were installed into site-packages during image build.
for path in "$src_dir"/* "$src_dir"/.[!.]* "$src_dir"/..?*; do
[ -e "$path" ] || continue
name="$(basename "$path")"
if [ -n "$keep_dir_name" ] && [ "$name" = "$keep_dir_name" ]; then
continue
fi
rm -rf "$dst_dir/$name"
cp -as "$path" "$dst_dir/$name"
done
tmp_keep="$(mktemp -d)"
find "$dst_dir" -maxdepth 1 \( -name '_core*.so' -o -name 'lib*.so*' \) -exec mv {} "$tmp_keep/" \;
rm -rf "$dst_dir"
mkdir -p "$dst_dir"
cp -as "$src_dir/." "$dst_dir/"
find "$tmp_keep" -maxdepth 1 -type f -exec mv {} "$dst_dir/" \;
rmdir "$tmp_keep"
}

link_pure_python_package() {
Expand All @@ -44,8 +39,9 @@ link_pure_python_package() {
ln -s "$src_dir" "$dst_dir"
}

link_mixed_package "$REPO_ROOT/python/rcs" "$SITE_PACKAGES/rcs" "_core"
link_mixed_package "$REPO_ROOT/extensions/rcs_fr3/src/rcs_fr3" "$SITE_PACKAGES/rcs_fr3" "_core"
link_compiled_package "$REPO_ROOT/python/rcs" "$SITE_PACKAGES/rcs"
link_compiled_package "$REPO_ROOT/extensions/rcs_fr3/src/rcs_fr3" "$SITE_PACKAGES/rcs_fr3"
link_pure_python_package "$REPO_ROOT/extensions/rcs_realsense/src/rcs_realsense" "$SITE_PACKAGES/rcs_realsense"
link_pure_python_package "$REPO_ROOT/extensions/rcs_robotiq2f85/src/rcs_robotiq2f85" "$SITE_PACKAGES/rcs_robotiq2f85"
link_pure_python_package "$REPO_ROOT/extensions/rcs_zed/src/rcs_zed" "$SITE_PACKAGES/rcs_zed"
link_pure_python_package "$REPO_ROOT/vlagents" "$SITE_PACKAGES/vlagents"
22 changes: 22 additions & 0 deletions docs/apps/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Apps

RCS ships with ready-to-use applications for common operator workflows such as robot teleoperation and remote policy inference.

## Teleoperation

Use the Franka teleoperation app when you want to collect demonstrations or directly control a robot from an operator interface.

- Example guide: [examples/teleop/README.md](../../examples/teleop/README.md)
- Main script: [examples/teleop/franka.py](../../examples/teleop/franka.py)

The current example focuses on Franka teleoperation with Meta Quest 3 and GELLO-based setups.

## Inference

Use the Franka inference app when you want to run a remote policy server, for example through `vlagents`, against an RCS hardware environment.

- Example guide: [examples/inference/README.md](../../examples/inference/README.md)
- Main script: [examples/inference/franka.py](../../examples/inference/franka.py)
- Example config: [examples/inference/franka.json](../../examples/inference/franka.json)

The inference example expects a compatible policy server to already be running and explains the runtime keyboard commands and config fields in its README.
7 changes: 7 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ getting_started/index
user_guide/index
```

```{toctree}
:maxdepth: 2
:caption: Apps

apps/index
```

```{toctree}
:maxdepth: 2
:caption: API
Expand Down
79 changes: 79 additions & 0 deletions examples/inference/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Franka Inference Example

This folder contains a hardware-oriented inference example for running a `vlagents` policy server against the Franka duo setup in [franka.py](franka.py) with configuration from [franka.json](franka.json).

## Policy Server

Before starting `franka.py`, make sure a `vlagents` policy server is already running. The example connects to a remote agent with:

- `vlagents_host`
- `vlagents_port`
- `vlagents_model`

The policy server setup and supported launch commands are documented in:

- [RobotControlStack/vlagents](https://github.com/RobotControlStack/vlagents)
- [vlagents/README.md](../../vlagents/README.md)

Typical server startup looks like:

```shell
python -m vlagents start-server lerobot --port 20000 --host 0.0.0.0 --kwargs '{"policy_name": "act", "checkpoint_path": "<path to pretrained_model>", "n_action_steps": 1}'
```

For other policies such as `pi05` or `xvla`, use the matching startup command from the `vlagents` README and make sure the values in `franka.json` point at that server.

## Config File

[franka.json](franka.json) is an example config, not a universal default. You should review and usually change these values before running inference:

- `vlagents_host`: Hostname or IP address where the policy server is running.
- `vlagents_port`: Port exposed by the policy server.
- `vlagents_model`: Agent id passed to `vlagents`, for example `lerobot`.
- `instruction`: Natural-language task instruction sent to the policy on reset.
- `robot_keys`: Robot ordering used to pack observations and unpack actions. The script assumes one 8-value action block per robot in this order: `7` joint values plus `1` gripper value.
- `jpeg_encoding`: Whether observations are sent to the policy server using JPEG-compressed images.
- `on_same_machine`: Set this according to whether the policy server runs on the same machine as the control process.
- `fps`: Control loop target frequency used by the local rate limiter.
- `record_path`: Output directory used when recording episodes.
- `n_action_steps`: If `null`, the script requests one action per control step. If set to an integer greater than `0`, the script buffers that many actions from each policy response chunk.
- `max_rel_mov_joints`: Maximum allowed relative joint movement per step when running in joint control mode.
- `max_rel_mov_cart`: Maximum allowed relative Cartesian translation and rotation per step when running in Cartesian modes.

The current `franka.py` example also contains hardware-specific constants for robot IPs, camera serials, gripper serials, and frame mappings. Those live in the script itself, so update [franka.py](franka.py) if your hardware setup differs.

## Runtime Keys

When [franka.py](franka.py) is running, it waits for keyboard input on stdin. The active commands are:

- `e`: Start an episode without recording.
- `r`: Start an episode and begin recording to `record_path`.
- `s`: Mark the current episode as successful and reset the environment.
- `q`: Stop the current episode and reset the environment.
- `o`: Reload `franka.json`, reconnect the `vlagents` client, reset the environment, and clear any buffered actions.
- `x`: Exit the program.

## Observation And Action Mapping

The script translates RCS observations to the `vlagents` `Obs` format as follows:

- Every camera frame in `obs["frames"]` is converted to RGB and resized to `224x224`.
- State is built by iterating through `robot_keys` in order and concatenating each robot's `joints` and `gripper` values.

Action decoding is also order-dependent:

- For each robot in `robot_keys`, the script reads `8` values from the policy action vector.
- Values `0:7` become the robot joint command.
- Value `7:8` becomes the robot gripper command.

That means `robot_keys` must match the policy's expected robot ordering exactly.

## Running

After the policy server is up and `franka.json` is configured, run:

```shell
python examples/inference/franka.py
```

If the policy server is unreachable, the script will keep retrying connection until it becomes available or you exit.
20 changes: 20 additions & 0 deletions examples/inference/franka.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"vlagents_host": "localhost",
"vlagents_port": 20000,
"vlagents_model": "lerobot",
"instruction": "use the left arm to place the white cube in the white bowl; use the right arm to place the black cube in the black bowl",
"robot_keys": [
"left",
"right"
],
"jpeg_encoding": true,
"on_same_machine": false,
"fps": 30,
"record_path": "inference_recordings_bin_sort_duobench_xvla_bin_sort_real_2026-05-20_23-25-47_040000",
"n_action_steps": 30,
"max_rel_mov_joints": 0.08726646259971647,
"max_rel_mov_cart": [
0.5,
1.5707963267948966
]
}
Loading
Loading