Skip to content

Commit c7e81ed

Browse files
authored
Merge pull request #340 from RobotControlStack/fix/sleep-bottom-of-stack
fix(hw): move rate limit to bottom of stack
2 parents 4b75055 + 7113c0c commit c7e81ed

10 files changed

Lines changed: 51 additions & 20 deletions

File tree

‎examples/inference/franka.py‎

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
from rcs.envs.base import ControlMode, RelativeTo
1616
from rcs.envs.configs import EmptyWorldFR3Duo
1717
from rcs.envs.storage_wrapper import StorageWrapper
18-
from rcs.utils import SimpleFrameRate
1918

2019
# from rcs_duobench.tasks.bin_sort import BinSortEnvConfig
2120
from vlagents.client import RemoteAgent
@@ -133,7 +132,6 @@ def __init__(self, env: gym.Env, cfg: InferenceConfig):
133132
cfg.jpeg_encoding,
134133
cfg.image_size,
135134
)
136-
self.frame_rate = SimpleFrameRate(self._cfg.fps)
137135
self._action_buffer = []
138136

139137
def submit_command(self, command: str) -> None:
@@ -298,9 +296,6 @@ def loop(self):
298296

299297
obs_dict = self.obs_rcs2agents(obs)
300298

301-
if ROBOT_INSTANCE == RobotPlatform.HARDWARE:
302-
self.frame_rate()
303-
304299

305300
def command_loop(controller: ModelInference) -> None:
306301
prompt = "Command [e=start, r=record, s=success/reset, q=stop/reset, o=reload, x=exit]: "
@@ -393,6 +388,7 @@ def get_env(cfg: InferenceConfig) -> gym.Env:
393388
# interpolation window of the controllers must match the rate at which we stream actions
394389
hw_cfg.robot_cfgs["left"].policy_rate = cfg.fps
395390
hw_cfg.robot_cfgs["right"].policy_rate = cfg.fps
391+
hw_cfg.frequency = cfg.fps
396392
hw_cfg.gripper_cfgs["left"].serial_number = ROBOTIQ_SERIAL["left"]
397393
hw_cfg.gripper_cfgs["right"].serial_number = ROBOTIQ_SERIAL["right"]
398394
env_rel = env_creator.create_env(hw_cfg)

‎examples/teleop/franka.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ def get_env():
159159
# interpolation window of the controllers must match the rate at which we stream actions
160160
hw_cfg.robot_cfgs["left"].policy_rate = RECORD_FPS
161161
hw_cfg.robot_cfgs["right"].policy_rate = RECORD_FPS
162+
hw_cfg.frequency = RECORD_FPS
162163
hw_cfg.gripper_cfgs["left"].serial_number = ROBOTIQ_SERIAL["left"] # type: ignore
163164
hw_cfg.gripper_cfgs["right"].serial_number = ROBOTIQ_SERIAL["right"] # type: ignore
164165
env_rel = env_creator.create_env(hw_cfg)

‎extensions/rcs_fr3/src/rcs_fr3/creators.py‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,8 @@ class FR3HardwareEnvCreatorConfig:
161161
camera_cfgs: dict[str, HardwareCameraCreatorConfig] | None = None
162162
max_relative_movement: float | tuple[float, float] | None = None
163163
relative_to: RelativeTo = RelativeTo.LAST_STEP
164+
frequency: float | None = None
165+
"""Control frequency in Hz, rate limits env.step(). None disables rate limiting."""
164166
wrapper_cfg: WrapperConfig = field(default_factory=WrapperConfig)
165167

166168

@@ -173,6 +175,8 @@ class FR3MultiHardwareEnvCreatorConfig:
173175
max_relative_movement: float | tuple[float, float] | None = None
174176
relative_to: RelativeTo = RelativeTo.LAST_STEP
175177
robot_to_shared_base_frame: dict[str, rcs.common.Pose] | None = None
178+
frequency: float | None = None
179+
"""Control frequency in Hz, rate limits env.step(). None disables rate limiting."""
176180
wrapper_cfg: WrapperConfig = field(default_factory=WrapperConfig)
177181

178182

@@ -185,7 +189,7 @@ def create_env(self, cfg: FR3HardwareEnvCreatorConfig) -> gym.Env:
185189
)
186190
robot = hw.Franka(cfg.robot_cfg, ik)
187191

188-
env: gym.Env = HardwareEnv()
192+
env: gym.Env = HardwareEnv(frequency=cfg.frequency)
189193
env = RobotWrapper(env, robot, cfg.control_mode, home_on_reset=cfg.wrapper_cfg.home_on_reset)
190194
env = FR3HW(env)
191195
if isinstance(cfg.gripper_cfg, rcs.hand.tilburg_hand.THConfig):
@@ -229,6 +233,7 @@ def create_env(self, cfg: FR3MultiHardwareEnvCreatorConfig) -> gym.Env:
229233
camera_cfgs=None,
230234
max_relative_movement=cfg.max_relative_movement,
231235
relative_to=cfg.relative_to,
236+
frequency=cfg.frequency,
232237
wrapper_cfg=cfg.wrapper_cfg,
233238
)
234239
)

‎extensions/rcs_panda/src/rcs_panda/creators.py‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ class PandaHardwareEnvCreatorConfig:
119119
camera_cfgs: dict[str, HardwareCameraCreatorConfig] | None = None
120120
max_relative_movement: float | tuple[float, float] | None = None
121121
relative_to: RelativeTo = RelativeTo.LAST_STEP
122+
frequency: float | None = None
123+
"""Control frequency in Hz, rate limits env.step(). None disables rate limiting."""
122124
wrapper_cfg: WrapperConfig = field(default_factory=WrapperConfig)
123125

124126

@@ -131,6 +133,8 @@ class PandaMultiHardwareEnvCreatorConfig:
131133
max_relative_movement: float | tuple[float, float] | None = None
132134
relative_to: RelativeTo = RelativeTo.LAST_STEP
133135
robot_to_shared_base_frame: dict[str, rcs.common.Pose] | None = None
136+
frequency: float | None = None
137+
"""Control frequency in Hz, rate limits env.step(). None disables rate limiting."""
134138
wrapper_cfg: WrapperConfig = field(default_factory=WrapperConfig)
135139

136140

@@ -143,7 +147,7 @@ def create_env(self, cfg: PandaHardwareEnvCreatorConfig) -> gym.Env:
143147
)
144148
robot = hw.Franka(cfg.robot_cfg, ik)
145149

146-
env: gym.Env = HardwareEnv()
150+
env: gym.Env = HardwareEnv(frequency=cfg.frequency)
147151
env = RobotWrapper(env, robot, cfg.control_mode, home_on_reset=cfg.wrapper_cfg.home_on_reset)
148152
env = PandaHW(env)
149153
if isinstance(cfg.gripper_cfg, rcs.hand.tilburg_hand.THConfig):
@@ -185,6 +189,7 @@ def create_env(self, cfg: PandaMultiHardwareEnvCreatorConfig) -> gym.Env:
185189
camera_cfgs=None,
186190
max_relative_movement=cfg.max_relative_movement,
187191
relative_to=cfg.relative_to,
192+
frequency=cfg.frequency,
188193
wrapper_cfg=cfg.wrapper_cfg,
189194
)
190195
)

‎extensions/rcs_so101/src/rcs_so101/creators.py‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ class SO101HardwareEnvCreatorConfig:
8585
camera_cfgs: dict[str, HardwareCameraCreatorConfig] | None = None
8686
max_relative_movement: float | tuple[float, float] | None = None
8787
relative_to: RelativeTo = RelativeTo.LAST_STEP
88+
frequency: float | None = None
89+
"""Control frequency in Hz, rate limits env.step(). None disables rate limiting."""
8890
wrapper_cfg: WrapperConfig = field(default_factory=WrapperConfig)
8991

9092

@@ -96,7 +98,7 @@ def create_env(self, cfg: SO101HardwareEnvCreatorConfig) -> gym.Env:
9698
urdf=cfg.robot_cfg.kinematic_model_path.endswith(".urdf"),
9799
)
98100
robot = SO101(cfg=cfg.robot_cfg, ik=ik)
99-
env: gym.Env = HardwareEnv()
101+
env: gym.Env = HardwareEnv(frequency=cfg.frequency)
100102
env = RobotWrapper(env, robot, cfg.control_mode, home_on_reset=cfg.wrapper_cfg.home_on_reset)
101103

102104
gripper = SO101Gripper(robot._hf_robot, robot)

‎extensions/rcs_ur5e/src/rcs_ur5e/creators.py‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ class UR5eHardwareEnvCreatorConfig:
9999
camera_cfgs: dict[str, HardwareCameraCreatorConfig] | None = None
100100
max_relative_movement: float | tuple[float, float] | None = None
101101
relative_to: RelativeTo = RelativeTo.LAST_STEP
102+
frequency: float | None = None
103+
"""Control frequency in Hz, rate limits env.step(). None disables rate limiting."""
102104
wrapper_cfg: WrapperConfig = field(default_factory=WrapperConfig)
103105

104106

@@ -110,7 +112,7 @@ def create_env(self, cfg: UR5eHardwareEnvCreatorConfig) -> gym.Env:
110112
urdf=cfg.robot_cfg.kinematic_model_path.endswith(".urdf"),
111113
)
112114
robot = UR5e(cfg.robot_cfg, ik)
113-
env: gym.Env = HardwareEnv()
115+
env: gym.Env = HardwareEnv(frequency=cfg.frequency)
114116
env = RobotWrapper(env, robot, cfg.control_mode, home_on_reset=cfg.wrapper_cfg.home_on_reset)
115117

116118
if cfg.gripper_cfg is not None:

‎extensions/rcs_xarm7/src/rcs_xarm7/creators.py‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ class XArm7HardwareEnvCreatorConfig:
9191
hand_cfg: THConfig | None = None
9292
max_relative_movement: float | tuple[float, float] | None = None
9393
relative_to: RelativeTo = RelativeTo.LAST_STEP
94+
frequency: float | None = None
95+
"""Control frequency in Hz, rate limits env.step(). None disables rate limiting."""
9496
wrapper_cfg: WrapperConfig = field(default_factory=WrapperConfig)
9597

9698

@@ -105,7 +107,7 @@ def create_env(self, cfg: XArm7HardwareEnvCreatorConfig) -> gym.Env:
105107
urdf=cfg.robot_cfg.kinematic_model_path.endswith(".urdf"),
106108
)
107109
robot = XArm7(cfg=cfg.robot_cfg, ik=ik)
108-
env: gym.Env = HardwareEnv()
110+
env: gym.Env = HardwareEnv(frequency=cfg.frequency)
109111
env = RobotWrapper(env, robot, cfg.control_mode, home_on_reset=cfg.wrapper_cfg.home_on_reset)
110112

111113
camera_set = _create_hardware_camera_set(cfg.camera_cfgs)

‎extensions/rcs_yam/src/rcs_yam/creators.py‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ class YamHardwareEnvCreatorConfig:
8888
camera_cfgs: dict[str, HardwareCameraCreatorConfig] | None = None
8989
max_relative_movement: float | tuple[float, float] | None = None
9090
relative_to: RelativeTo = RelativeTo.LAST_STEP
91+
frequency: float | None = None
92+
"""Control frequency in Hz, rate limits env.step(). None disables rate limiting."""
9193
wrapper_cfg: WrapperConfig = field(default_factory=WrapperConfig)
9294

9395

@@ -99,7 +101,7 @@ def create_env(self, cfg: YamHardwareEnvCreatorConfig) -> gym.Env:
99101
urdf=cfg.robot_cfg.kinematic_model_path.endswith(".urdf"),
100102
)
101103
robot = Yam(cfg.robot_cfg, ik)
102-
env: gym.Env = HardwareEnv()
104+
env: gym.Env = HardwareEnv(frequency=cfg.frequency)
103105
env = RobotWrapper(env, robot, cfg.control_mode, home_on_reset=cfg.wrapper_cfg.home_on_reset)
104106

105107
if cfg.gripper_cfg is not None:
@@ -127,6 +129,8 @@ class YamMultiHardwareEnvCreatorConfig:
127129
max_relative_movement: float | tuple[float, float] | None = None
128130
relative_to: RelativeTo = RelativeTo.LAST_STEP
129131
robot_to_shared_base_frame: dict[str, rcs.common.Pose] | None = None
132+
frequency: float | None = None
133+
"""Control frequency in Hz, rate limits env.step(). None disables rate limiting."""
130134
wrapper_cfg: WrapperConfig = field(default_factory=WrapperConfig)
131135

132136

@@ -144,6 +148,7 @@ def create_env(self, cfg: YamMultiHardwareEnvCreatorConfig) -> gym.Env:
144148
camera_cfgs=None,
145149
max_relative_movement=cfg.max_relative_movement,
146150
relative_to=cfg.relative_to,
151+
frequency=cfg.frequency,
147152
wrapper_cfg=cfg.wrapper_cfg,
148153
)
149154
)

‎python/rcs/envs/base.py‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,28 @@ def reset(
211211
class HardwareEnv(BaseEnv):
212212
PLATFORM = RobotPlatform.HARDWARE
213213

214+
def __init__(self, frequency: float | None = None) -> None:
215+
"""
216+
Args:
217+
frequency: Control frequency in Hz. Each env step is rate limited such that
218+
the loop runs at this frequency, analogous to `SimConfig.frequency` in realtime sim.
219+
None disables rate limiting.
220+
"""
221+
super().__init__()
222+
assert frequency is not None and frequency > 0, "frequency must be set to a positive value"
223+
self.frame_rate = SimpleFrameRate(frequency, "Hardware Loop")
224+
225+
def step(self, action: dict[str, Any]) -> tuple[dict[str, Any], float, bool, bool, dict]:
226+
ret = super().step(action)
227+
self.frame_rate()
228+
return ret
229+
230+
def reset(
231+
self, *, seed: int | None = None, options: dict[str, Any] | None = None
232+
) -> tuple[dict[str, Any], dict[str, Any]]:
233+
self.frame_rate.reset()
234+
return super().reset(seed=seed, options=options)
235+
214236

215237
class SimEnv(BaseEnv):
216238
PLATFORM = RobotPlatform.SIMULATION

‎python/rcs/operator/interface.py‎

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
from rcs._core.common import RobotPlatform
1212
from rcs.envs.base import ArmWithGripper, ControlMode, RelativeTo
1313
from rcs.sim.sim import Sim
14-
from rcs.utils import SimpleFrameRate
1514

1615
logger = logging.getLogger(__name__)
1716

@@ -121,9 +120,6 @@ def _translate_keys(self, actions):
121120
return translated
122121

123122
def environment_step_loop(self):
124-
rate_limiter = SimpleFrameRate(
125-
self.env_frequency if self.robot_platform == RobotPlatform.HARDWARE else None, "env loop"
126-
)
127123

128124
# 0. Initial Reset to get current positions for untracked robots
129125
self._last_obs, _ = self.env.reset()
@@ -180,7 +176,6 @@ def environment_step_loop(self):
180176

181177
self._last_obs, _, _, _, _ = self.env.step(hold_actions)
182178
self.operator.set_camera(self._last_obs)
183-
rate_limiter()
184179
continue
185180

186181
for controller in cmds.reset_origin_to_current:
@@ -202,11 +197,8 @@ def environment_step_loop(self):
202197
self._last_obs, _, _, _, _ = self.env.step(actions)
203198
self.operator.set_camera(self._last_obs)
204199

205-
rate_limiter()
206-
207200
def sync_robot_to_operator(self, duration: float = 3.0):
208201
print(f"Command: Syncing robot to operator (duration: {duration}s)...")
209-
rate_limiter = SimpleFrameRate(self.env_frequency, "sync loop")
210202
num_steps = int(duration * self.env_frequency)
211203

212204
# 1. Capture the initial state for interpolation
@@ -239,6 +231,5 @@ def sync_robot_to_operator(self, duration: float = 3.0):
239231

240232
self._last_obs, _, _, _, _ = self.env.step(interp_actions)
241233
self.operator.set_camera(self._last_obs)
242-
rate_limiter()
243234

244235
print("Sync Complete.")

0 commit comments

Comments
 (0)