Skip to content

hylo-env-lerobot-robot

Hylo environment for real robots through LeRobot's Robot interface (SO-100/101, Koch, LeKiwi, ROS 2 plugins...), plus a simulated arm for bring-up without hardware

Repository hylo-serve, adapters/hylo-env-lerobot-robot. Licence: Apache-2.0. Depends on: hylo-core, numpy>=1.24.

Registered entry points
Group Name Object
agents bringup.wave hylo.envs.lerobot_robot.bringup:BringupWave
envs lerobot.robot hylo.envs.lerobot_robot.env:LeRobotRobotEnvironment
Module Summary
hylo.envs.lerobot_robot
hylo.envs.lerobot_robot.bringup Bring-up policy: small sinusoidal moves around the pose the arm is in, joint by joint
hylo.envs.lerobot_robot.env A real robot as a Hylo Environment, through LeRobot's Robot interface (S3)
hylo.envs.lerobot_robot.sim_arm SimulatedArm: a stand-in with LeRobot's Robot interface, for bringing up a workcell without hardware

hylo.envs.lerobot_robot

META module-attribute

META = {'contracts': {'envs': 'hylo.env/v0.1', 'agents': 'hylo.agent/v0.1'}, 'maturity': 'implemented', 'plugins': {'envs/lerobot.robot': {'maturity': 'needs-hardware', 'notes': 'runs with the simulated arm (sim_arm); no real arm connected yet'}}}

hylo.envs.lerobot_robot.bringup

Bring-up policy: small sinusoidal moves around the pose the arm is in, joint by joint.

Not a task policy. It checks the chain (policy server -> workcell -> adapter -> motors) with motions that stay within a few degrees of the measured pose, so the shield's limits and the robot's own limits are never near. The motion is a function of executed steps (counted in on_step), not of policy calls: under asynchronous execution the runtime drops the actions that went stale during inference, and a chunk indexed by steps stays continuous with the one before it.

BringupWave

BringupWave(amplitude: float = 5.0, period_steps: int = 30, horizon: int = 15, joint: int | None = 0)

Bases: AgentBase

spec

spec() -> AgentSpec

reset

reset(ctx) -> None

on_step

on_step(result) -> None

act

act(observation: Observation) -> ActionChunk

hylo.envs.lerobot_robot.env

A real robot as a Hylo Environment, through LeRobot's Robot interface (S3).

Any LeRobot robot works (SO-100/101 and Koch followers, LeKiwi, OpenArm, ...; ROS 2 robots through LeRobot's third-party plugins such as lerobot_robot_ros2_zenoh), and so does SimulatedArm for bring-up without hardware.

What the adapter adds on top of LeRobot's Robot: - an EnvSpec: observation keys grouped into named state vectors (default: joint_pos and gripper), cameras renamed, an action box with explicit limits and semantics (joint_pos in the robot's units, continuous gripper), the control rate, and real_robot=True so the SafetyShield aborts on any modification; - reset: connect if needed, move to the home pose by interpolation (home_steps small steps, each within the shield's limits), then hand over. The scene is the operator's job: the workcell waits for "reset_done"; - success is never claimed (None): the operator's verdict or a calibrated judge labels the episode; - hold_action: the current measured position (the robot stays where it is), used by the workcell runtime when the policy is late; stop(): hold immediately (e-stop path, in addition to the robot's own hardware e-stop).

env:
  type: lerobot.robot
  remote: {runtime: robot}            # the profile's runtime: the venv with lerobot and the robot SDK
  params:
    robot: {type: so101_follower, params: {port: /dev/ttyACM0, id: arm1, max_relative_target: 10}}
    cameras: {front: agentview, wrist: wrist}
    action_low: [-110, -100, -100, -100, -160, 0]
    action_high: [110, 100, 100, 100, 160, 100]
    home: [0, -90, 90, 0, 0, 10]
    control_hz: 30

ROBOT_MODULES module-attribute

ROBOT_MODULES = {'so100_follower': 'so_follower', 'so101_follower': 'so_follower'}

LeRobotRobotEnvironment

LeRobotRobotEnvironment(robot: dict[str, Any] | Any, cameras: dict[str, str] | None = None, state_groups: dict[str, list[str]] | None = None, action_low: list[float] | None = None, action_high: list[float] | None = None, control_mode: str = 'joint_pos', gripper: str | None = 'continuous', control_hz: float = 30.0, home: list[float] | None = None, home_steps: int = 60, real_robot: bool = True, name: str | None = None)

robot instance-attribute

robot = make_robot(robot)

cam_map instance-attribute

cam_map = dict(cameras) if cameras is not None else {k: k for k, v in feats.items() if isinstance(v, tuple)}

state_groups instance-attribute

state_groups = state_groups

action_keys instance-attribute

action_keys = list(self.robot.action_features)

home instance-attribute

home = None if home is None else np.asarray(home, float)

name instance-attribute

name = name or f"lerobot.{getattr(self.robot, 'name', type(self.robot).__name__)}"

spec

spec() -> EnvSpec

reset

reset(task: TaskInstance) -> Observation

step

step(action: Action) -> StepResult

hold_action

hold_action(last: ndarray | None) -> np.ndarray

stop

stop() -> None

close

close() -> None

make_robot

make_robot(spec: dict[str, Any] | Any) -> Any

hylo.envs.lerobot_robot.sim_arm

SimulatedArm: a stand-in with LeRobot's Robot interface, for bringing up a workcell without hardware.

Five revolute joints and a gripper, in degrees and 0-100 like LeRobot's SO follower, with first-order motor dynamics (each joint closes a fraction of the gap to its target per step), a per-step limit on the commanded change (LeRobot's max_relative_target) and an overhead camera that draws the arm's projected joint positions. It never claims success: on a real robot the operator (or a calibrated judge) decides.

JOINTS module-attribute

JOINTS = ('shoulder_pan', 'shoulder_lift', 'elbow_flex', 'wrist_flex', 'wrist_roll')

SimulatedArm

SimulatedArm(id: str = 'sim', gain: float = 0.35, max_relative_target: float | None = 10.0, camera: str | None = 'front', image_size: int = 96, seed: int = 0)

name class-attribute instance-attribute

name = 'sim_arm'

pos instance-attribute

pos = np.zeros(len(JOINTS) + 1)

target instance-attribute

target = self.pos.copy()

rng instance-attribute

rng = np.random.default_rng(seed)

observation_features property

observation_features: dict[str, Any]

action_features property

action_features: dict[str, Any]

is_connected property

is_connected: bool

is_calibrated property

is_calibrated: bool

connect

connect(calibrate: bool = True) -> None

calibrate

calibrate() -> None

configure

configure() -> None

get_observation

get_observation() -> dict[str, Any]

send_action

send_action(action: dict[str, Any]) -> dict[str, Any]

disconnect

disconnect() -> None