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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ RPent is built upon three core design principles: **service-oriented, standardiz
<ul style="margin-left: 0; padding-left: 16px;">
<li>Franka</li>
<li>SO-101</li>
<li>reBot DevArm (RobStride) ✅</li>
</ul>
</td>
</tr>
Expand Down Expand Up @@ -177,6 +178,7 @@ See [SETUP_ROBOCASA.zh.md](docs/SETUP_ROBOCASA.zh.md) for the full RoboCasa365 +
## Documentation

- [Adding a new environment](https://rpent.readthedocs.io/en/latest/rst_source/extending/new_env.html) — plug a new simulator / robot into the runner ([中文](https://rpent.readthedocs.io/zh-cn/latest/rst_source/extending/new_env.html)).
- [reBot DevArm (RobStride)](docs/source-en/rst_source/usage/rebot_robstride.rst) — safe SocketCAN setup, passive validation, and guarded motion tools.
- [RoboCasa setup](docs/SETUP_ROBOCASA.zh.md) — RoboCasa365 + RLDX-1 install and run guide.
- [`docs/`](docs/README.md) — local Sphinx build and preview instructions.

Expand Down
1 change: 1 addition & 0 deletions docs/source-en/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ Welcome to RPent
RoboCasa <rst_source/usage/robocasa>
Franka <rst_source/usage/franka>
SO-101 <rst_source/usage/so101>
reBot DevArm (RobStride) <rst_source/usage/rebot_robstride>

.. toctree::
:maxdepth: 2
Expand Down
22 changes: 14 additions & 8 deletions docs/source-en/rst_source/development/add_robot.rst
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ For a new env ``myenv``, the file layout is:
.. code-block:: text

robots/myenv/
__init__.py # entry point — get_env_spec() / get_toolkit() factories
__init__.py # get_env_spec / get_runtime / get_toolkit factories
runtime.py # process/client lifecycle behind EnvRuntime
env_client.py # MyEnvClient — agent-side RPC stub (§1)
prompt_bundle.py # system()/user() prompt factories (§2)
toolkit.py # MyEnvToolkit + primitives + tool schemas (§3)
Expand All @@ -73,7 +74,7 @@ For a new env ``myenv``, the file layout is:

``__init__.py`` is the package's entry point. The registry in
``rpent/envs/base.py`` lazily imports ``robots.<name>`` on demand and calls its
two factories:
three factories:

.. code-block:: python

Expand All @@ -85,6 +86,10 @@ two factories:
def get_env_spec() -> EnvSpec:
return EnvSpec(name="myenv", prompts=PromptBundle(system=system_prompt, user=user_prompt))

def get_runtime(*, args, output_dir, dashboard=None):
from robots.myenv.runtime import MyEnvRuntime
return MyEnvRuntime(args=args, output_dir=output_dir, dashboard=dashboard)

def get_toolkit(*, primitives_kwargs: dict[str, Any], video_path: str | None = None):
from robots.myenv.toolkit import MyEnvToolkit
return MyEnvToolkit(primitives_kwargs=primitives_kwargs, video_path=video_path)
Expand Down Expand Up @@ -150,13 +155,14 @@ Wrap the facade in a dispatcher and serve over ``SocketRpcServer``:
print(json.dumps({"event": "transport_ready", "kind": "socket",
"host": host, "port": bound_port}), flush=True)

The ``transport_ready`` event on stdout is required —
``start_env_server()`` in ``rpent/cli/main.py`` blocks until it sees it.
The ``transport_ready`` event on stdout is required — the selected
``EnvRuntime`` waits for it before constructing the client.

``rpent/cli/main.py`` currently imports ``LiberoEnvClient`` and the LIBERO env_server
script path directly. Adding a new env means either branching on
``args.env_name`` to pick the client class + driver script, or factoring those
two callsites out behind a per-env helper.
The environment's ``EnvRuntime`` owns server startup/attach, client
construction, and shutdown. ``rpent/cli/main.py`` calls ``get_runtime`` and
contains no robot-specific branching. See ``robots/libero/runtime.py`` for a
model-backed simulator and ``robots/rebot_robstride/runtime.py`` for a physical
robot without a VLA process.

2. ``prompt_bundle.py``
-----------------------
Expand Down
19 changes: 10 additions & 9 deletions docs/source-en/rst_source/development/architecture.rst
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ The code that implements the framework is split cleanly by concern:
robots/
libero/ # LIBERO env_client / env_server / vla_server /
# toolkit / prompt_bundle. The reference env.
rebot_robstride/ # Physical reBot driver over motorbridge + SocketCAN.
(robocasa/) # RoboCasa driver (see scripts/run_robocasa.sh).
(franka/) # Franka driver — in progress.
(so101/) # SO-101 driver — in progress.
Expand All @@ -105,15 +106,14 @@ The runner (``rpent/cli/main.py``)
use day-to-day).
2. Creates the per-run scratch directory (``--output-dir`` or an
auto-generated one under ``runs/``).
3. Spawns the **env_server** as a subprocess and waits for its
``transport_ready`` JSON event on stdout. That event carries the
host/port the socket RPC is listening on; ``rpent/cli/main.py`` records
the endpoint under ``<output_dir>/`` so the client can find it.
4. Spawns (or attaches to) the **vla_server** the same way, using
``--vla-endpoint`` when reusing a running instance.
5. Builds the **toolkit** for the chosen env via the env's
``get_toolkit(primitives_kwargs=...)`` factory, wiring in the env
client and the VLA client.
3. Builds the chosen environment's **EnvRuntime** through
``get_runtime(args=..., output_dir=...)``.
4. Calls ``runtime.start()``. The runtime starts or attaches to the processes
that environment needs, waits for ``transport_ready``, builds its clients,
and returns the environment toolkit. LIBERO starts env + VLA processes;
physical reBot control starts only its single-owner hardware server.
5. Keeps process/client details outside the runner, so adding a robot never
adds an ``if env_name == ...`` branch to ``main.py``.
6. Builds the **cerebrum** via ``rpent.cerebrum.base.build_cerebrum``,
selecting one of ``api_loop.py`` / ``claude_code.py`` /
``codex.py`` based on ``--cerebrum``.
Expand All @@ -137,6 +137,7 @@ factories the package exposes:

# robots/myenv/__init__.py
def get_env_spec() -> EnvSpec: ...
def get_runtime(*, args, output_dir, dashboard=None): ...
def get_toolkit(*, primitives_kwargs, video_path=None): ...

There is **no central list** of envs. Dropping a package under
Expand Down
181 changes: 181 additions & 0 deletions docs/source-en/rst_source/usage/rebot_robstride.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
reBot DevArm (RobStride)
========================

RPent can control the seven-motor reBot DevArm B601-RS directly through
``motorbridge`` and a classic SocketCAN adapter. The hardware server owns the
CAN interface exclusively and exposes guarded joint, gripper, stop, and state
operations to every RPent cerebrum.

Install
-------

Install the optional hardware dependencies in the active RPent environment:

.. code-block:: bash

uv sync --active --inexact --extra rebot-robstride

Bring up the CAN interface before starting RPent. The server checks this state
but never invokes ``sudo`` itself:

.. code-block:: bash

sudo ip link set can0 up type can bitrate 1000000
ip -details link show can0

Do not run another motorbridge gateway or controller against ``can0`` while the
RPent environment server is active. The server must be the sole owner of host
ID ``0xFD``.

Passive hardware check
----------------------

Verify all seven motors before enabling torque:

.. code-block:: bash

python scripts/check_rebot_robstride.py

The check enables RobStride active fault reporting, reads ``mechPos``
(``0x7019``), and inspects status plus fault/warning reports. It never clears
faults, selects a control mode, enables torque, or sends a target. On exit it
still issues ``disable_all`` before releasing SocketCAN. Runtime velocity
feedback is estimated from timestamped position samples because ``mechVel``
scaling is not consistent across the tested RobStride firmware variants.

Configuration
-------------

Copy the example and review every raw-motor limit and gain for your arm:

.. code-block:: bash

cp robots/rebot_robstride/config/rebot_robstride.example.yaml rebot.yaml

The default B601-RS mapping is:

.. list-table::
:header-rows: 1

* - Motor IDs
- Model
- Role
* - 1–3
- ``rs-06``
- shoulder/base arm joints
* - 4–6
- ``rs-00``
- wrist arm joints
* - 7
- ``rs-00``
- gripper

Gripper ``open_position`` and ``closed_position`` intentionally default to
null. Calibrate the installed gripper before setting them. RPent refuses every
gripper command while either endpoint is missing.

Safety-critical overrides have hard ceilings: control is 10–200 Hz, feedback
is at least 5 Hz, each parameter-read timeout is at most 100 ms, motion is at
most 60 seconds, and settlement is at most 5 seconds. The nominal
motion-plus-settlement budget is at most 65 seconds. All initial/final feedback
overhead counts against a separate 68-second server deadline, which leaves
seven seconds before the 75-second motion RPC timeout for fail-closed disable
and response delivery. Heartbeat timeout is 0.25–5 seconds, joint/gripper
velocity is at most 1 rad/s, and MIT gains are capped at ``kp=200`` / ``kd=20``.
Configuration that exceeds these bounds is rejected before opening SocketCAN.

Run
---

Start a physical-agent run with an explicit natural-language instruction:

.. code-block:: bash

python rpent/cli/main.py \
--env rebot_robstride \
--env-config rebot.yaml \
--instruction "Read the current joint state and wait" \
--cerebrum api \
--model anthropic:claude-opus-4-8

The arm starts disabled. An agent must call ``get_robot_state`` and then
``enable_arm`` before any motion. ``enable_arm`` first validates raw joint
limits and near-zero startup velocity, clears faults, verifies the resulting
operation-status replies and cached detailed reports, selects MIT mode, enables
only the six arm motors, and immediately holds the observed pose. Repeated
``enable_arm`` calls are idempotent and do not discard an enabled gripper's
state. The gripper remains disabled until a calibrated gripper command
explicitly enables it.

Available robot tools
---------------------

- ``get_robot_state`` — fresh positions, estimated velocities, latest available
operation-status flags, and actively reported raw fault/warning cache for all
motors.
- ``enable_arm`` — explicit fault-clear, mode selection, enable, and pose hold.
- ``move_joints`` — six raw-motor-radian targets through a bounded minimum-jerk
trajectory with final read-back evidence.
- ``set_gripper`` / ``open_gripper`` / ``close_gripper`` — calibrated,
normalized gripper control.
- ``stop_motion`` — torque-holding software stop; rejects later motion.
- ``reset_stop`` — clears only the software-stop latch; never enables motors.
- ``emergency_stop`` — disables all motors immediately.

Safety limits
-------------

``move_joints`` rejects non-finite values, targets outside the configured joint
limits, malformed six-joint vectors, and durations above the configured hard
maximum. Requested durations are stretched by the 1.875 peak derivative of the
minimum-jerk profile so the actual setpoint velocity respects each configured
cap. During motion the driver sends a hold or waypoint to every physically
enabled motor before feedback, including motors in the subsystem that is not
moving. Those commands produce operation-status replies; active reporting
updates the detailed fault/warning cache. The driver aborts and disables all
motors on missing/nonzero operation status, a nonzero raw fault/warning report,
transport error, excessive tracking error, excessive measured velocity, or the
68-second server deadline. ``motorbridge`` 0.4.9 does not expose a timestamp for
its detailed type-21 fault-report cache, so that raw cache is not described as a
synchronously queried sample. Completion requires consecutive
position-and-velocity settlement samples. Gripper results use the same
``target``/``final``/``max_error``/``reached`` evidence contract.

``robot.stop_motion``, ``robot.emergency_stop``, heartbeats, and ``shutdown``
use a priority RPC path that bypasses the serialized motion-command lock.
Every admitted operation captures a monotonic stop generation; a stop
invalidates that generation permanently, so a later ``reset_stop`` cannot
resurrect an older enable or trajectory. Feedback and multi-joint command
batches release the I/O lock between motor operations, so an emergency stop
waits behind at most one in-flight motorbridge call before disable begins. A
runtime worker sends periodic heartbeats; if the agent process disappears while
torque is enabled, the server calls emergency stop after
``heartbeat_timeout_s``.
Soft stop holds both the arm and an enabled gripper at freshly measured
positions. If ``disable_all`` itself fails, state reports ``disable_failed=true``
and ``reset_stop`` remains blocked until a later emergency-stop retry confirms
that torque was removed.

The pickle-framed hardware RPC server accepts loopback binds only. Do not expose
it through a TCP proxy or bind it to a LAN address.

``emergency_stop`` removes torque; an unsupported arm may fall under gravity.
Support the arm and keep the physical emergency stop accessible during initial
hardware validation.

Normal shutdown always calls ``disable_all``; there is no unattended ``hold``
shutdown mode. If disable fails, ``close`` does not close the controller or erase
the physically uncertain enabled/``disable_failed`` state, allowing a later
emergency-stop or close retry. The heartbeat protects loss of the agent process
while the hardware server remains alive. A hard crash or ``SIGKILL`` of the
hardware server cannot be proven fail-safe. Hardware validation on the B601
firmware found RobStride ``0x7028 canTimeout`` present but disabled by default;
setting it to ``20000`` both at runtime and with saved parameters did not produce
a post-enable ``mode_state 2 -> 0`` transition during 21.5 seconds of host
silence in MIT mode. A manual disable was still required. Do not treat
``0x7028`` or motorbridge ``set_can_timeout_ms`` as a verified motor watchdog on
this platform. Never operate unattended, and keep the physical emergency stop
reachable.

The first implementation provides guarded joint-space control. It does not
claim collision avoidance, Cartesian planning, or perception-based grasping.
10 changes: 9 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ Documentation = "https://rpent.readthedocs.io/en/latest/"
rlinf = [
"rlinf @ git+https://github.com/jx-qiu/RLinf.git@feature/physicalagent",
]
rebot-robstride = [
"motorbridge>=0.4.9; sys_platform == 'linux'",
"PyYAML>=6.0",
]

[tool.uv]
prerelease = "allow"
Expand All @@ -58,9 +62,13 @@ include-package-data = true

[tool.setuptools.packages.find]
where = ["."]
include = ["rpent*"]
include = ["rpent*", "robots*"]
exclude = ["tests*", "docs*", "examples*", "docker*", "toolkits*"]

[tool.setuptools.package-data]
"robots.libero" = ["README.md", "guides/*.md", "prompts/*.md"]
"robots.rebot_robstride" = ["config/*.yaml"]

[tool.ruff]
line-length = 88
indent-width = 4
Expand Down
18 changes: 16 additions & 2 deletions robots/libero/__init__.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
"""LIBERO environment extension."""

from __future__ import annotations

from typing import Any

from rpent.envs.prompt_bundle import PromptBundle
from rpent.envs.env_spec import EnvSpec
from robots.libero.prompt_bundle import (
system_prompt,
user_prompt,
)
from rpent.envs.env_spec import EnvSpec
from rpent.envs.prompt_bundle import PromptBundle


def validate_args(args: Any, parser: Any) -> None:
"""Reject incomplete LIBERO invocations during CLI parsing."""
if args.suite is None or args.task is None:
parser.error("--suite and --task are required for --env libero")


def get_env_spec() -> EnvSpec:
Expand All @@ -26,6 +33,13 @@ def get_env_spec() -> EnvSpec:
)


def get_runtime(*, args: Any, output_dir: str, dashboard: Any = None):
"""Return the LIBERO process lifecycle adapter."""
from robots.libero.runtime import LiberoRuntime

return LiberoRuntime(args=args, output_dir=output_dir, dashboard=dashboard)


def get_toolkit(
*,
primitives_kwargs: dict[str, Any],
Expand Down
Loading