Skip to content

Commit 2d58ad5

Browse files
committed
feat(sim): sim config support for async and realtime mode
- added frequency flag to sim config - pybind for sim config - support for async sim - fixed realtime mode in sim
1 parent 171414f commit 2d58ad5

File tree

9 files changed

+67
-24
lines changed

9 files changed

+67
-24
lines changed

python/rcs/_core/sim.pyi

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ __all__ = [
1616
"Sim",
1717
"SimCameraConfig",
1818
"SimCameraSet",
19+
"SimConfig",
1920
"SimGripper",
2021
"SimGripperConfig",
2122
"SimGripperState",
@@ -86,8 +87,10 @@ class Sim:
8687
def __init__(self, mjmdl: int, mjdata: int) -> None: ...
8788
def _start_gui_server(self, id: str) -> None: ...
8889
def _stop_gui_server(self) -> None: ...
90+
def get_config(self) -> SimConfig: ...
8991
def is_converged(self) -> bool: ...
9092
def reset(self) -> None: ...
93+
def set_config(self, cfg: SimConfig) -> bool: ...
9194
def step(self, k: int) -> None: ...
9295
def step_until_convergence(self) -> None: ...
9396

@@ -106,6 +109,13 @@ class SimCameraSet:
106109
@property
107110
def _sim(self) -> Sim: ...
108111

112+
class SimConfig:
113+
async_control: bool
114+
frequency: int
115+
max_convergence_steps: int
116+
realtime: bool
117+
def __init__(self) -> None: ...
118+
109119
class SimGripper(rcs._core.common.Gripper):
110120
def __init__(self, sim: Sim, cfg: SimGripperConfig) -> None: ...
111121
def get_parameters(self) -> SimGripperConfig: ...

python/rcs/envs/creators.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ def __call__( # type: ignore
4747
robot_cfg: rcs.sim.SimRobotConfig,
4848
collision_guard: bool = False,
4949
gripper_cfg: rcs.sim.SimGripperConfig | None = None,
50+
sim_cfg: rcs.sim.SimConfig | None = None,
5051
hand_cfg: rcs.sim.SimTilburgHandConfig | None = None,
5152
cameras: dict[str, SimCameraConfig] | None = None,
5253
max_relative_movement: float | tuple[float, float] | None = None,
@@ -75,7 +76,7 @@ def __call__( # type: ignore
7576
Returns:
7677
gym.Env: The configured simulation environment for the FR3 robot.
7778
"""
78-
simulation = sim.Sim(robot_cfg.mjcf_scene_path)
79+
simulation = sim.Sim(robot_cfg.mjcf_scene_path, sim_cfg)
7980

8081
ik = rcs.common.Pin(
8182
robot_cfg.kinematic_model_path,
@@ -137,6 +138,7 @@ def __call__( # type: ignore
137138
cameras: dict[str, SimCameraConfig] | None = None,
138139
hand_cfg: rcs.sim.SimTilburgHandConfig | None = None,
139140
gripper_cfg: rcs.sim.SimGripperConfig | None = None,
141+
sim_cfg: rcs.sim.SimConfig | None = None,
140142
random_pos_args: dict | None = None,
141143
) -> gym.Env:
142144
mode = "gripper"
@@ -170,6 +172,7 @@ def __call__( # type: ignore
170172
collision_guard=False,
171173
gripper_cfg=_gripper_cfg,
172174
hand_cfg=_hand_cfg,
175+
sim_cfg=sim_cfg,
173176
cameras=cameras,
174177
max_relative_movement=(0.2, np.deg2rad(45)) if delta_actions else None,
175178
relative_to=RelativeTo.LAST_STEP,
@@ -213,8 +216,12 @@ def __call__( # type: ignore
213216
translation=np.array([0.0, 0.0, 0.1034]), # type: ignore
214217
rotation=np.array([[0.707, 0.707, 0], [-0.707, 0.707, 0], [0, 0, 1]]), # type: ignore
215218
)
219+
sim_cfg = sim.SimConfig()
220+
sim_cfg.realtime = False
221+
sim_cfg.async_control = True
222+
sim_cfg.frequency = 30 # in Hz
216223

217-
return SimTaskEnvCreator()(robot_cfg, render_mode, control_mode, delta_actions, cameras)
224+
return SimTaskEnvCreator()(robot_cfg, render_mode, control_mode, delta_actions, cameras, sim_cfg=sim_cfg)
218225

219226

220227
class FR3LabDigitGripperPickUpSimEnvCreator(EnvCreator):

python/rcs/envs/sim.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@
1414
from rcs.envs.utils import default_sim_robot_cfg, default_sim_tilburg_hand_cfg
1515

1616
import rcs
17+
import time
1718
from rcs import sim
19+
from rcs.utils import SimpleFrameRate
1820

1921
logger = logging.getLogger(__name__)
2022
logger.setLevel(logging.INFO)
@@ -33,7 +35,7 @@ def __init__(self, env: gym.Env, simulation: sim.Sim):
3335

3436

3537
class RobotSimWrapper(gym.Wrapper):
36-
def __init__(self, env, simulation: sim.Sim, sim_wrapper: Type[SimWrapper] | None = None):
38+
def __init__(self, env, simulation: sim.Sim, sim_wrapper: SimWrapper | None = None):
3739
self.sim_wrapper = sim_wrapper
3840
if sim_wrapper is not None:
3941
env = sim_wrapper(env, simulation)
@@ -42,10 +44,20 @@ def __init__(self, env, simulation: sim.Sim, sim_wrapper: Type[SimWrapper] | Non
4244
assert isinstance(self.unwrapped.robot, sim.SimRobot), "Robot must be a sim.SimRobot instance."
4345
self.sim_robot = cast(sim.SimRobot, self.unwrapped.robot)
4446
self.sim = simulation
47+
cfg = self.sim.get_config()
48+
self.frame_rate = SimpleFrameRate(1/cfg.frequency, "RobotSimWrapper")
4549

4650
def step(self, action: dict[str, Any]) -> tuple[dict[str, Any], float, bool, bool, dict]:
4751
_, _, _, _, info = super().step(action)
48-
self.sim.step_until_convergence()
52+
cfg = self.sim.get_config()
53+
if cfg.async_control:
54+
self.sim.step(round(1/cfg.frequency / self.sim.model.opt.timestep))
55+
if cfg.realtime:
56+
self.frame_rate.frame_rate = 1/cfg.frequency
57+
self.frame_rate()
58+
59+
else:
60+
self.sim.step_until_convergence()
4961
state = self.sim_robot.get_state()
5062
info["collision"] = state.collision
5163
info["ik_success"] = state.ik_success
@@ -348,7 +360,7 @@ def reset(
348360
class RandomCubePos(SimWrapper):
349361
"""Wrapper to randomly place cube in the lab environments."""
350362

351-
def __init__(self, env: gym.Env, simulation: sim.Sim, include_rotation: bool = False):
363+
def __init__(self, env: gym.Env, simulation: sim.Sim, include_rotation: bool = True):
352364
super().__init__(env, simulation)
353365
self.include_rotation = include_rotation
354366

@@ -361,7 +373,7 @@ def reset(
361373
iso_cube = np.array([0.498, 0.0, 0.226])
362374
iso_cube_pose = rcs.common.Pose(translation=np.array(iso_cube), rpy_vector=np.array([0, 0, 0])) # type: ignore
363375
iso_cube = self.unwrapped.robot.to_pose_in_world_coordinates(iso_cube_pose).translation()
364-
pos_z = 0.826
376+
pos_z = 0.0288 / 2
365377
pos_x = iso_cube[0] + np.random.random() * 0.2 - 0.1
366378
pos_y = iso_cube[1] + np.random.random() * 0.2 - 0.1
367379

python/rcs/sim/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
SimTilburgHand,
1010
SimTilburgHandConfig,
1111
SimTilburgHandState,
12+
SimConfig,
1213
)
1314
from rcs.sim.sim import Sim, gui_loop
1415

@@ -25,4 +26,5 @@
2526
"SimTilburgHandState",
2627
"gui_loop",
2728
"SimCameraConfig",
29+
"SimConfig",
2830
]

python/rcs/sim/sim.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import mujoco.viewer
1313
from rcs._core.sim import GuiClient as _GuiClient
1414
from rcs._core.sim import Sim as _Sim
15-
from rcs.sim import egl_bootstrap
15+
from rcs.sim import egl_bootstrap, SimConfig
1616
from rcs.utils import SimpleFrameRate
1717

1818
egl_bootstrap.bootstrap()
@@ -42,7 +42,7 @@ def gui_loop(gui_uuid: str, close_event):
4242

4343

4444
class Sim(_Sim):
45-
def __init__(self, mjmdl: str | PathLike):
45+
def __init__(self, mjmdl: str | PathLike, cfg: SimConfig = None):
4646
mjmdl = Path(mjmdl)
4747
if mjmdl.suffix == ".xml":
4848
self.model = mj.MjModel.from_xml_path(str(mjmdl))
@@ -58,6 +58,8 @@ def __init__(self, mjmdl: str | PathLike):
5858
self._gui_client: Optional[_GuiClient] = None
5959
self._gui_process: Optional[mp.context.SpawnProcess] = None
6060
self._stop_event: Optional[EventClass] = None
61+
if cfg is not None:
62+
self.set_config(cfg)
6163

6264
def close_gui(self):
6365
if self._stop_event is not None:

python/rcs/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def __call__(self):
3636
sleep(sleep_time)
3737
if self._last_print is None or perf_counter() - self._last_print > 10:
3838
self._last_print = perf_counter()
39-
logger.info(f"FPS {self.loop_name}: {1 / (perf_counter() - self.t)}")
39+
logger.debug(f"FPS {self.loop_name}: {1 / (perf_counter() - self.t)}")
4040

4141
self.t = perf_counter()
4242

src/pybind/rcs.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,18 @@ PYBIND11_MODULE(_core, m) {
487487
.def_readonly("last_width", &rcs::sim::SimGripperState::last_width)
488488
.def_readonly("collision", &rcs::sim::SimGripperState::collision);
489489

490+
py::class_<rcs::sim::SimConfig>(
491+
sim, "SimConfig")
492+
.def(py::init<>())
493+
.def_readwrite("async_control",
494+
&rcs::sim::SimConfig::async_control)
495+
.def_readwrite("realtime",
496+
&rcs::sim::SimConfig::realtime)
497+
.def_readwrite("frequency",
498+
&rcs::sim::SimConfig::frequency)
499+
.def_readwrite("max_convergence_steps",
500+
&rcs::sim::SimConfig::max_convergence_steps);
501+
490502
py::class_<rcs::sim::Sim, std::shared_ptr<rcs::sim::Sim>>(sim, "Sim")
491503
.def(py::init([](long m, long d) {
492504
return std::make_shared<rcs::sim::Sim>((mjModel *)m, (mjData *)d);
@@ -495,10 +507,14 @@ PYBIND11_MODULE(_core, m) {
495507
.def("step_until_convergence", &rcs::sim::Sim::step_until_convergence,
496508
py::call_guard<py::gil_scoped_release>())
497509
.def("is_converged", &rcs::sim::Sim::is_converged)
510+
.def("set_config", &rcs::sim::Sim::set_config, py::arg("cfg"))
511+
.def("get_config", &rcs::sim::Sim::get_config)
498512
.def("step", &rcs::sim::Sim::step, py::arg("k"))
499513
.def("reset", &rcs::sim::Sim::reset)
500514
.def("_start_gui_server", &rcs::sim::Sim::start_gui_server, py::arg("id"))
501515
.def("_stop_gui_server", &rcs::sim::Sim::stop_gui_server);
516+
517+
502518
py::class_<rcs::sim::SimGripper, rcs::common::Gripper,
503519
std::shared_ptr<rcs::sim::SimGripper>>(sim, "SimGripper")
504520
.def(py::init<std::shared_ptr<rcs::sim::Sim>,

src/sim/sim.cpp

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,12 @@ bool get_last_return_value(ConditionCallback cb) {
2828

2929
Sim::Sim(mjModel* m, mjData* d) : m(m), d(d), renderer(m){};
3030

31-
bool Sim::set_config(const Config& cfg) {
32-
if (cfg.async) {
33-
/* Not implemented :) */
34-
return false;
35-
}
31+
bool Sim::set_config(const SimConfig& cfg) {
3632
this->cfg = cfg;
3733
return true;
3834
}
3935

40-
Config Sim::get_config() { return this->cfg; }
36+
SimConfig Sim::get_config() { return this->cfg; }
4137

4238
void Sim::invoke_callbacks() {
4339
for (int i = 0; i < std::size(this->callbacks); ++i) {
@@ -115,9 +111,6 @@ void Sim::step(size_t k) {
115111
this->invoke_callbacks();
116112
mj_step2(this->m, this->d);
117113
this->invoke_rendering_callbacks();
118-
if (this->cfg.realtime) {
119-
std::this_thread::sleep_for(std::chrono::milliseconds(10));
120-
}
121114
}
122115
}
123116

src/sim/sim.h

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,11 @@ class Renderer {
2626
std::unordered_map<std::string, mjrContext*> ctxs;
2727
};
2828

29-
struct Config {
30-
bool async = false;
29+
struct SimConfig {
30+
bool async_control = false;
3131
bool realtime = false;
32-
int max_convergence_steps = 5000;
32+
int frequency = 30; // in Hz
33+
int max_convergence_steps = 500;
3334
};
3435

3536
struct Callback {
@@ -56,7 +57,7 @@ struct RenderingCallback {
5657

5758
class Sim {
5859
private:
59-
Config cfg;
60+
SimConfig cfg;
6061
std::vector<Callback> callbacks;
6162
std::vector<ConditionCallback> any_callbacks;
6263
std::vector<ConditionCallback> all_callbacks;
@@ -74,8 +75,8 @@ class Sim {
7475
mjModel* m;
7576
mjData* d;
7677
Sim(mjModel* m, mjData* d);
77-
bool set_config(const Config& cfg);
78-
Config get_config();
78+
bool set_config(const SimConfig& cfg);
79+
SimConfig get_config();
7980
bool is_converged();
8081
void step_until_convergence();
8182
void step(size_t k);

0 commit comments

Comments
 (0)